함수를 작성하는데 지켜야할 몇가지를 공유합니다.
Edit me

루틴: 한가지 목적을 위해서 호출할 수 있는 개별 메서드나 프로시저(Function)

복잡성을 줄인다. 루틴을 작성할 때는 루틴에 관해 생각해야겠지만 일단 작성하고 나면 루틴의 내부 동작 방식을 몰라도 해당 루틴의 구현 사항에 신경 쓰지 않고 루틴을 사용할 수 있다. 내부 루프나 조건문이 깊게 중첩되어 있다면 루틴을 서브 루틴으로 나누어야한다.
이해하기 쉬운 중간 단계의 추상화를 도입한다.

if( node<> null) then
    while(node.next <> null) do
        node = node.next
        leafName = node.name
    end while
else
    leafName = “”
end

위 코드는 다음 루틴으로 대체할 수 있다.

leafName = GetLeafName(node)

중복 코드를 피한다.

모든 루틴의 길이를 짧게 만들기 위해서? 아니다. 코드를 루틴으로 작성해야 하는 타당한 이유가 많지만, 이 이유는 불필요하다. 사실 크기가 큰 하나의 루틴내에서 처리하는 것이 좋은 경우도 있다.

루틴 수준의 설계

루틴은 강한 응집성(cohesion)으로 설계되어야한다. 루틴내의 연산들이 얼마나 강하게 연결되어 있는지를 나타낸다. Cosine()과 같은 함수는 전체 루틴이 한 가지 기능만 수행하므로 응집성이 강하다. CosineAndTan()과 같은 함수는 한 가지 이상의 작업을 수행하기 때문에 응집성이 약하다. 응집성이 높을수록 코드의 오류가 적다.

450개의 루틴을 대상으로 한 연구에서 응집성이 높은 루틴의의 50%는 오류가 없었던 반면, 응집성이 낮은 루틴에서는 18%만이 오류가 없었다.

또 다른 연구에서는 응집도 대비 결합도(여기서 결합도는 Coupling의 의미이다) 비율이 가장 높은 루틴이 가장 낮은 비율의 루틴보다 7배나 많았으며 변경 비용도 20배나 더 들었다.

좋은 루틴 이름

루틴이 하는 모든 것을 표현하라.

루틴이 전체 보고서를 계산하고 출력파일을 연다면 ComputeReportTotals()는 적절한 이름이라고 보기 어렵다. 그 대신 ComputeReportTotalsAndOpenOutputFile()이 적절하겠지만, 너무 길고 우스꽝스럽다. 루틴이 한가지 기능이 아니라 여러 부수적인 기능을 갖고 있다면 이름 짓는 것이 어려울 것이다. 더 나은 이름을 찾는 것 대신 루틴이 여러 부수적인 기능을 갖고 있지 않도록 루틴의 역할을 좀 더 분명하게 해야 한다.

의미가 없거나 모호하거나 뚜렷한 특징이 없는 동사를 사용하지 말라.

어떤 동사는 포괄적이고 유연해서 많은 뜻을 포함하고 있다. HandleCalculation(), PerformServices(), OutputUSer(), ProcessInput(), DealWithOutput()과 같은 이름은 무슨 일을 하는지 말해주지 않는다. 루틴이 계산이나 서비스, 입력과 출력에 관련된 무언가를 하고 있다는 정도만 알려준다. 예외적으로 동사 ‘Handle’은 이벤트를 처리한다는 기술적인 의미로 사용될 때가 있다. 이는 루틴이 처리하는 연산이 모호해서 동사를 모호하게 사용하기도 한다. 이름 짓기 어렵다면 루틴의 목적이 불분명하다는 점이 원인.

루틴 이름을 숫자만으로 구분하지 말것

루틴 이름의 길이에 신경 쓰지 마라.

연구에 의하면 적절한 길이는 9~15자이다. 루틴이 하는 일은 변수보다 복잡하기 때문에 이름도 좀 더 복잡하고 길다. 전반적으로 루틴 이름은 “명료함”에 초점을 맞춰야하고, 따라서 이름의 길이에 제약을 받지 않고 이해하기 쉽게 이름을 지어야한다.

함수 이름을 지을 때는 리턴값에 대해 설명하라

함수는 값을 리턴하므로 리턴 값에 대한 내용이 이름에 포함되어야한다. cos(), customId.Next(), printer.IsReady(), pen.CurrentColor()는 함수가 리턴하는 것을 정확하게 보여주고 있기 때문에 좋은 이름이다.

프로시저의 이름을 지을때 확실한 의미가 있는 동사를 객체 이름과 함께 사용하라.

기능적 응집성을 갖는 프로시저는 일반적으로 하나의 객체에 대해서 한가지 연산을 수행한다. 프로시저가 무슨일을 하는지 반영해야 하기 때문에 객체에 대한 연산은 동사에 객체 이름을 붙여쓴 형태의 이름을 갖는다.

PrintDocument();
CalcMonthlyRevenues();
CheckOrderInfo();
RepaginateDocument(); //모두 좋은 프로시저 이름이다.

단, 객체 지향 언어에서는 객체 자체가 호출에 포함되어 있기 때문에 프로시저에 객체의 이름을 포함 시킬 필요가 없다.

docment.Print();
orderInfo.Check();
monthlyRevenues.Calc(); //객체 지향은 이렇게 표현할 수 있으면 더 좋다.

반의어를 정확하게 사용하라

add/remove, increment/decrement, insert/delete, show/hide, source/target…

공통적인 연산을 위한 규약을 만들어라.

id를 구하는 방법 employee.id.get(), employee.GetId(), employee(), employee.id() 통일해야함

루틴의 길이에 대한문제

루틴의 길이에 대한 수많은 연구가 수년동안 진행되었으며 그중 몇몇은 오늘날 적용이 가능하지만, 나머지는 요즘 환경에 맞지 않다.

  • 루틴의 크기와 오류가 반비례 관계를 갖는다는 것을 발견했다.
  • 구조적 복잡성과 데이터 양은 오류와 연관 관계가 있지만 루틴의 크기는 오류와 관련이 없다는 사실을 발견했다.
  • 큰 루틴(65줄이상)이 한줄당 개발 비용도 더 저렴하다는 것을 발견했다.
  • 작은 루틴은 큰 루틴보다 한줄당 오류의 수가 23%가 높았지만, 오류를 수정하는 비용은 2.4배 낮다는 것을 발견했다.
  • 루틴의 길이가 평균 100줄에서 150줄일때 코드가 변경될 확률이 가장 낮다는 사실을 발견했다.

결과적으로 200줄 이상은 연구결과가 없을 정도로 큰 길이의 루틴이므로 주의해야한다.

루틴 매개변수 처리

루틴 간의 인터페이스는 프로그램에서 가장 오류가 발생하기 쉬운 영역 중 하나이다. 한 연구에 따르면 전체 오류의 39%가 내부 인터페이스 오류 즉, 루틴끼리 서로 호출할 때 발생하는 오류였다.

매개변수를 입력-수정-출력 순서로 입력한다.

입력만 가능한 것을 첫 번째로, 입출력이 가능한 것을 두번째, 출력만 가능한 것을 세번째로 나열한다. 이는 변경되는 매개변수를 앞에 두는 C 라이브러리 규약과 상충한다. 어떤 방식이든 매개변수를 일관성 있게 정렬한다면 코드를 읽는 사람들에게 도움이 될 것이다.

고유한 in과 out 키워드의 사용을 고려한다.

C++의 경우 const 키워드로 구분 가능하다.

유사한 매개변수가 여러 루틴에서 사용된다면 해당 매개변수를 항상 같은 순서로 입력한다.

루틴의 매개변수를 연산을 위한 변수로 사용하지 않는다.

int Sample(int inputVal) {
inputVal = inputVal * CurrentMultiplier(inputVal);
inputVal = inputVal + CurrentAdder(inputVal);
..
return inputVal; // 여기서 inputVal은 더 이상 입력된 값을 담고 있지 않다.
}

매개변수에 대한 제약사항을 주석으로 작성한다.

루틴 매개변수의 수를 7개 정도로 제한한다.

한 연구에 따르면 사람들은 일반적으로 한 번에 7개 이상의 정보 묶음을 추적할 수 없다고한다.

매개변수에 사용할 입력 수정, 출력 이름 규약을 고려한다.

입력, 수정, 출력 매개 변수를 구분하는 것이 중요하다면 식별하기 위한 이름 규약을 작성한다. Input_, Modify_, Output_

루틴이 인터페이스 추상화를 유지할 수 있도록 변수나 객체를 전달한다.

주장1: 루틴이 필요로하는 3개의 구체적인 값(int, char와 같은 기본 데이터 타입)만 전달해야한다. 루틴끼리 연결을 최소화하고 결합을 줄이며 이해하기 쉽고 재사용이 가능하게 할 수 있다. 객체를 루틴에 전달하면 해당 객체를 사용하여 다른 데이터에도 접근할 수 있게 되므로 이는 결국 캡슐화 원칙에 어긋난다.

int Example(int a, int b, char c)
{
    ...
    return d;
}

주장2: 객체를 매개변수로 사용해야한다. 해당 루틴이 인터페이스를 바꾸지 않고도 매개변수로 전달된 객체의 다른 멤버를 통해 변경한다면 인터페이스가 더 안정감을 유지할 수 있다. 구체적인 값을 전달하는 것은 루틴이 사용하고 있는 구체적인 데이터요소를 노출해서 캡슐화 원칙에 어긋난다.

구체적인 값 3개가 루틴 호출이후에도 생성한 값을 계속 사용하고 있다면 객체 대신 구체적인 값을 전달하는 것이 맞고 루틴에 전달하는 매개변수의 목록을 자주 변경하고 그 매개 변수가 동일한 객체로부터 온 것이라면 구체적인 값 대신 객체를 전달해야한다.

class Package
{
    int a;
    int b;
    char c;
};

int Example(Package p)
{
    ...
    return d;
}

함수를 사용할 때 특별히 고려해야 할 사항

C++에서는 일반적으로 모든 루틴을 “함수”로 부르지만, void 리턴형을 갖는 함수는 의미론적으로 프로시저이다. 함수와 프로시저의 구분은 문법적인 구분과 의미론적인 구분이 있는데 의미론적인 구분을 따라야한다. 함수는 읽기 전용의 입력과 오직 하나의 값을 리턴(극단적 주장)을 받아야하며 프로시저는 입력, 수정, 출력 매개변수를 원하는 만큼 받을 수 있다.

인라인 루틴을 자주 사용하지 말라

c++에서는 인라인 루틴의 구현 코드를 헤더파일에 입력해야하는데 이렇게 되면 헤더 파일을 사용하는 모든 개발자에게 루틴이 노출되기 때문에 캡슐화를 위반하게 된다.