Output Parameter에 대한 고찰

1·2021년 7월 9일
9
post-thumbnail

Clean Code와 관련된 많은 책들과 많은 Programming Best Practice 관련 글에서 함수 Parameter로 전달된 객체의 내부를 변경시키거나 Output으로 활용하는 행위(Output Parameter)를 최대한 지양하라고 합니다.

그렇다면 정확하게 어떤 이유에서 Output Parameter를 지양하라고 하는지, 어떤 경우에 사용하는 것이 좋을지 간단히 정리해보았습니다. 😎

※ Java 언어를 기준으로 내용을 정리했으며 일부 프로그래밍 언어에서는 아래 내용이 맞지 않을 수 있습니다.

Output Parameter(출력 인수)란?

method의 값 반환 목적으로 method로 전달된 parameter를 사용하는 것.

위에 언급한 내용 외에도 특정 method에 전달된 객체 내부의 데이터를 변경하거나 추가하여 Parameter를 Output으로 사용하는 형태를 포괄적으로 아우러 말합니다.

Output parameter 의 특징

  1. Output Parameter는 null이 아니어야 한다.
  2. Output Parameter는 immutable object가 아니어야 한다.(변경 가능한 객체)
    (ex. Java의 String과 같은 Object는 내부 값 변경이 불가능해서 Output Parameter가 될 수 없습니다.)

Output parameter 를 쓰는 예시

  1. 객체를 수정하는 형태로 사용하는 Output Parameter 예시
void setSomeDataInObject(SomeObject param) {
   param.setName("hoho");
   param.changeState(ANOTHER_STATE);
   param.setData(myData);
}
  1. 객체에 데이터를 추가하는 형태로 사용하는 Output Parameter 예시
void addSomeDataToList(List<Data> list, Data data) {
     // ...
     list.add(data);
     // ...
}

Output Parameter 사용을 지양하는 이유

Output Parameter 사용을 지양해야 할 이유는 많이 있지만 크게 두 가지 정도로 정리해보았습니다.

1. 개발자가 놓치게 되는 Side effect가 발생할 확률이 높다.

Output Parameter를 사용하게 되면 일반적으로 객체 생성은 method 외부에서,
객체 내부 변경은 method 내부에서 일어나게 됩니다.

객체 생성 Scope != 객체 수정 Scope

이는 객체가 생성된 Scope와 변경되는 Scope가 다른 상황인데 이런 경우 개발자가 놓치게 되는 Side effect가 발생할 수 있습니다.

아래 예시는 Output parameter를 무분별하게 사용하여 Side effect가 발생할 확률이 높은 상황을 가정한 코드입니다.

void mainCode() {
    // do something.
    User user = new User();   // <-- 객체가 생성된 위치
    Data data = someComponent.getData();
    user.setName("hoho"); // <-- 객체의 이름이 변경된 위치
    
    setUserPropertyWithData(user, data);
    
    Result result = sendToSomeWhereOverTheRainbow(user);
    print(result);
}

// ..some codes here

// depth 1
void setUserPropertyWithData(User user, Data data) {
    Property property = anotherComponent.getUserProperty(data);
    setUserProperty(user, property);
}

// depth 2
void setUserProperty(User user, Property property) { // <-- Output Parameter가 사용된 method
    user.setName(property.getName()); //  <-- 개발자가 인지하지 못할 수 있는 수정!
    user.setAddress(property.getAddress());
}

위 코드를 보시면 mainCode method에서 사용자의 이름을 hoho라고 변경하였는데
하위 depth의 코드인 setUserProperty에서 사용자 이름 변경이 한번 더 일어나게 됩니다.

일반적으로 상위 Depth의 method만 보며 프로그램의 흐름을 파악하는 개발 특성상
개발자는 하위 Depth의 Code를 일일이 다 보지 않게 되고

user 객체의 nameproperty에 있는 name으로 변경될 것을 예측하기 어렵습니다.
(만약 Output Parameter가 무분별하게 사용되는 Lagacy 코드가 있다면 어디서 객체가 변경될지 모르기 때문에 하위 Depth Code들을 일일이 다 들여다 보며 개발해야할지도 몰라요 😥)

이렇게 예측하기 어려운 상황이 생기는 것 자체가 Side effect를 유발할 수 있는 위험 요소이기 때문에 Output Parameter 사용이 지양됩니다.

2. Code 가독성이 떨어진다.

첫 번째.
Output Parameter를 사용하는 패턴이 흔하지 않고 대부분 return되는 변수를 활용하는 식으로 프로그램을 작성하기 때문에 Output paramter를 사용하는건 코드 가독성이 떨어집니다.

Object output = someMethod(input);
// vs
someMethodWithOutputParameter(input, output);

두 번째.
아래 classifyFruitsToAppleAndBanana 는 Fruit(과일)을 Apple(사과)와 Banana(바나나)로 분류하는 코드입니다.

아마도 개발자는 분류된 Apple과 Banana를 한번에 return 할 수 없어서 Output Parameter사용을 고려하게 된 것 같습니다

아래 코드를 보시면 아시겠지만 실제 method의 선언부를 보지 않으면 몇 번째 Parameter가 Output Paramter인지 알아보기 어렵습니다. (마찬가지로 가독성이 떨어짐)

    // 요즘 IDE가 많이 좋아졌다고는 하나 아래 method의 선언부를 보지 않고서는
    // input과 output(사과, 바나나)을 알아보기 어렵다.
    classifyFruitsToAppleAndBanana(myBag, myGirlFriendBag, yourBag);
    
    // 실제 method 선언부
    void classifyFruitsToAppleAndBanana(List<Fruit> fruits, List<Fruit> apples, List<Fruit> banana) {
        for(Fruit fruit : fruits) {
             if (fruit.getType() == APPLE) apples.add(fruit);
             else if (fruit.getType() == BANANA) bananas.add(fruit);
        }
    }

Output Parameter가 적절히 사용된 경우

그렇다고 해서 Output Parameter를 무조건 쓰면 안되는 것은 아닙니다.
아래는 Output Parameter를 적절히 사용한 좋은 예입니다.

1. 성능이 중요한 경우

Collections.sort(myList);

Collectionssortshuffle 처럼 List와 같은 자료구조를 다루는 method는 일반적으로 성능이 중요합니다.

만약 Parameter로 전달된 myList를 변경하지 않기 위해(Output Parameter를 사용하지 않기 위해) 또 하나의 복사본 List를 생성한 후 sortshuffle 기능을 수행되게 되면 메모리적인 낭비, 성능적인 낭비가 생깁니다.

그래서 Collections.sort와 같이 성능이 중요한 method인 경우 일반적으로 Output Parameter를 많이 사용합니다.

이런 낭비를 없애고 성능을 끌어올려야 할 때는 Output Parameter의 사용을 고려해 보는 것도 나쁘지 않습니다.

2. 의도적으로 객체의 수정을 위임할 때

@RequestMapping(value="/profile")
public String getUserProfile(Model model) {
    model.addAttribute("name", "hoho");
    return "profile";
}

위 코드는 Spring Framework에서 JSP 페이지를 띄울 때 Controller에 작성하는 method의 예시입니다.

RequestMapping이라고 하는 Annotation으로 설정한 URL Path가 호출되게되면
getUserProfile이라는 method가 callback 형태로 호출되게 됩니다.

사용자는 이 getUserProfile method를 통해 model이라는 객체를 적절히 수정하는 코드를 작성하게 되는데

이렇게 Callback과 같은 형태로 객체의 수정을 의도적으로 위임하는 경우도 Output Parameter가 적절히 잘 사용된 예입니다.

주의사항
이렇게 전달받은 model객체는 가급적 위임받은 method내에서만 수정하여야 하며 method 내부 코드가 길어진다던지 하여 불가피하게 해당 로직을 별도로 분리해야 하는 경우, 해당 컴포넌트는 최대한 벗어나지 않도록 하는 것이 좋습니다.

Output Parameter의 나쁜 사례

본 포스팅 내용을 정리하면서 여기저기 자료를 많이 찾아보게 됐는데
많은 개발자분들이 실제로 Output Parameter 사용을 고려하고 있었습니다.

어떤 상황에서 Output Parameter를 고민했었는지 좋은 방법은 뭔지 정리했습니다.
(Stack overflow에 올라온 질문 글을 재구성하였습니다.)

1. Return을 여러개하고 싶어서

가독성이 좋지 않아 Output Parameter 사용이 지양된다. 는 내용에 사용되었던 예시입니다.

    classifyFruitsToAppleAndBanana(myBag, myGirlFriendBag, yourBag);
    
    // ... some code here
    
    void classifyFruitsToAppleAndBanana(List<Fruit> fruits, List<Fruit> apples, List<Fruit> banana) {
        for(Fruit fruit : fruits) {
             if (fruit.getType() == APPLE) apples.add(fruit);
             else if (fruit.getType() == BANANA) bananas.add(fruit);
        }
    }

사과와 바나나를 분류하고 그 결과를 Return 하고싶은데 Java라는 언어 특성상 Return 이 하나밖에 되지 않아 Output Parameter를 고려한 상황입니다.

이 때는 별도의 Conainer Class를 추가해서 사용합니다.

    ClassifiedFruits classifedFruits = ClassifiedFruits.of(myBag);
    
    myGirlFriendsBag.addAll(classifedFruits.getApples());
    yourBag.addAll(classifedFruits.getBannas());
    
    // ... some code here
    
    class ClassifiedFruits {
        private List<Fruit> apples;
        private List<Fruit> bananas;
        
        // 중략
        
        public static ClassifiedFruits of(List<Fruit> fruits) {
        	// 중략
            
           for(Fruit fruit : fruits) {
                if (fruit.getType() == APPLE) apples.add(fruit);
                else if (fruit.getType() == BANANA) bananas.add(fruit);
           }
          
           return new ClassifiedFruits(apples, bannas);
        }
    }

내 가방에 있는 과일을 종류별로 분류해서 Apple(사과)는 내 여자친구 가방에, Banana(바나나)는 이 글을 읽고 있는 독자분의 가방에 넣는다는 코드의 흐름이 잘 전달되고 심지어 유지보수성도 좋습니다!

만약 저기에 파인애플까지 분류해야 한다면 복숭아 까지 분류한다면?

Output Parameter를 쓴 경우에는 Parameter가 끊임없이 늘어날겁니다 😥
거기다가 Parameter가 변경되면 사용하고 있는 모든 Call 부분의 코드를 업데이트 해주어야합니다. 😥😥

하지만 별도 Container Class를 추가해서 사용하면 ClassifedFruits에 필드와 get method만 추가하면 돼서 수정도 간편합니다. 👍

리턴을 여러개 하는 좋은 방법은 아래 글에 많이 소개가 돼 있으니 참고하시면 좋을 것 같습니다!
https://www.baeldung.com/java-method-return-multiple-values

2. 객체를 이렇게 저렇게 좀 많이 수정하고 싶어서

특정 객체의 데이터를 수정하는 별도 함수를 작성한 경우입니다.
바로 예시를 봅시다.

class User {
  // 대충 사용자 관련된 코드
}

static void main() {
   User user = userManager.getUser();
   updateUserToPremium(user);
}

void updateUserToPremium(User user) {
   user.setExpireTime('9999-12-31');
   user.setSuperUser(true);
   user.addPoint(100000);
   // ... 대충 user 분께 해줄게 많은 코드
}

특정 사용자를 프리미엄 사용자로 업그레이드 시켜주는 코드입니다.
만료일자를 9999년으로 늘리고 super user로 설정하고 포인트를 10만점 부여하고 등등...
사용자에게 뭔가 해주고싶은게 많은 코드입니다.

사용자에게 Premium 권한을 부여하기 위해 필요한 세팅 작업을 별도 method로 분류한건 아주 좋은 의도였지만 아래처럼 한가지 스텝을 더 진행하면 더욱 괜찮아집니다.

class User {
    // ... 대충 사용자 관련된 코드
    void updatetoPremium() {
        user.setExpireTime('9999-12-31');
       	user.setSuperUser(true);
   	user.addPoint(100000);
   	// ... 대충 user 분께 해줄게 많은 코드
    }
}

static void main() {
   User user = userManager.getUser();
   user.updateToPremium();
}

객체지향이라는 패러다임도 잘 지키면서 그 안좋다는 Output Parameter도 사용하지 않았습니다.
어떻게 느끼실진 모르겠지만 코드가 더 그럴 듯(?) 해진 것 같네요.

※ 객체 내부의 값을 바꾸는 작업은 member method로!

참고자료

https://stackoverflow.com/questions/18097614/is-using-output-parameters-considered-bad-practice
https://stackoverflow.com/questions/27204827/clean-code-are-output-parameters-bad
http://www.javapractices.com/topic/TopicAction.do?Id=37
https://stackoverflow.com/questions/1403921/output-parameters-in-java
https://stackoverflow.com/questions/2824910/how-to-use-an-output-parameter-in-java
https://www.baeldung.com/java-method-return-multiple-values
기타 stack overflow의 많은 자료와 답변들

※ 이 글은 인터넷 여기저기를 돌아다니며 본 글과 개인적으로 생각한 것들을 같이 정리한 글입니다.
틀린 내용이 없도록 최대한 찾아보며 정리했으나
혹시라도 잘못된 부분이 있을 수 있으니 참고 부탁드립니다.
잘못된 내용이나 수정이 필요한 부분이 있을시 꼭 알려주세요. 감사합니다.

profile
1

2개의 댓글

comment-user-thumbnail
2021년 7월 12일

많은 도움이 되었습니다. 좋은 글 감사합니다.

1개의 답글