형식 맞추기

팀으로 진행한다면 팀에서 합의한 규칙을 정하고 팀원 모두가 그 규칙을 따라야 한다.

형식을 맞추는 목적

코드 형식은 중요하다! 너무 중요하다!
'돌아가는 코드'가 개발자의 1차원적인 의무일 수도 있지만, 사실상 오늘 구현한 기능은 다음 버전에서 바뀔 확률은 아주 높다.
그런데 오늘 구현한 코드의 가독성은 앞으로 바뀔 코드의 품질에 거대한 영향을 준다.
코드가 지속적으로 바뀌어도 처음 잡아놓은 스타일과 가독성 수준은 이후에도 계속 영향을 끼친다.

적절한 행 길이를 유지하라.

개념은 빈 행(줄바꿈)으로 분리하라

일련의 행 묶음은 생각 하나를 표현한다.
생각 사이사이는 빈 행을 넣어 분리해야 마땅하다.

세로 밀집도

수직거리

이 함수에서 호출하는 다른 함수를 찾기 위해 미로같은 코드를 뒤진 경험이 있는가?
이 조각, 저 조각이 어디에 있는지 찾고 기억하는 데에 시간과 노력이 너무 많이 든다.
-> 서로 밀접한 개념은 세로로 가까이 둬야한다.

팀 규칙

프로그래머라면 각자 선호하는 규칙이 있다. 하지만 해당 팀에 속한다면 팀의 규칙에 따라야한다.
소프트웨어 시스템의 스타일은 일관적이고 매끄러워야 한다.
여러 스타일이 섞인 시스템은 독자에게 신뢰감을 주지 않는다.

주석

잘 달린 주석은 어떤 정보보다 유용하다. 하지만 경솔하고 근거없는 주석은 코드를 이해하기 어렵게 만든다
사실 주석이 필요한 코드는 실패다. 코드만으로 의도를 표현해야 한다. 이를 실패했기 때문에, 주석을 다는 것이다.

코드만이 정확한 정보를 제공하는 유일한 출처다

주석은 나쁜 코드를 보완하지 못한다

엉망인 코드에 주석을 다는 것보다 엉망인 코드를 손보는 것이 훨씬 좋다.

코드로 의도를 표현하라

코드만으로 의도를 설명하기 어려운 경우가 존재한다.

// 직원이 복지 혜택을 받을 자격이 있는지 검사한다
if((employee.flags & HOURLY_FLAG) && (employee.age > 65)){
    ...
}
VS
if(employee.isEligibleForFullBenefits())

좋은 주석

어떤 주석은 필요하거나 유익하다.

법적인 주석

정보를 제공하는 주석

의도를 설명하는 주석

의미를 명료하게 밝히는 주석

a.compareTo(a) == 0; // a==a
a.compareTo(b) != 0; // a!=b

결과를 경고하는 주석

  1. //여유 시간이 충분하지 않으면 실행하지 마십시오.
    최근에는 @Ignore("실행이 너무 오래걸린다.") 라는 Anotation을 이용해 테스트 케이스를 꺼버리지만, 이전에는 주석을 달았다.

  2. //해당 객체는 스레드에 안전하지 못하다.
    // 따라서 각 인스턴스를 독립적으로 생성해야 한다.

TODO 주석

실제로 나도 자주 사용한다.
요즘 IDE는 TODO 주석을 찾아주는 기능을 제공해서 잊어버릴 염려는 없다. 그래도 너무 많은 TODO 주석은 좋지 않다.

중요성을 강조하는 주석

//여기서 a라는 객체는 정말 중요하다. a의 역할은 이며 ~으로 사용해야 한다.

나쁜 주석

위에서 언급되지 않은 주석들이 나쁜 주석이다.
좋지 않은 코드/실패한 코드를 커버하기 위한 주석 들이 대다수다.

주절거리는 주석

같은 이야기를 중복하는 주석

오해할 여지가 있는 주석

의무적으로 다는 주석

  • 코드로도 충분히 이해할 수 있지만, 의무적으로 주석을 남겨 더 헷갈릴 여지를 주는 주석

반복하지 마라

소프트웨어에서 중복은 모든 악의 근원이다.

  • 관계형 DB에서도 정규화를 통해 중복을 제거한다.
  • OOP에서도 부모 클래스를 통해 중복을 없앤다.
  • 이렇듯 다양한 프로그래밍 방법에서 중복을 없애기 위한 여러 시도가 있다.

구조적 프로그래밍

모든 함수와 함수 내 모든 블록에 입구와 출구가 하나만 존재해야 한다고 말한다.
return 문이 하나여야 한다.

함수를 어떻게 짜죠?

논문이나 기사를 쓸 때도, 생각을 기록한 후에 읽기 좋게 다듬는다. 초안은 대게 어수선하다.
함수 또한 마찬가지다.

  • 처음에는 길고 복잡하다.
  • 인수 목록도 아주 길다.
  • 중복 코드, 루프도 많다.
  • 이름도 즉흥적으로 짓는다.
  • 하지만 코드를 빠짐없이 테스트하는 Unit Text Case를 작성한다*

그런 후, 코드를 다듬고, 함수를 만들고, 이름을 바꾸고, 중복을 제거하고
메서드를 줄이고, 전체 클래스를 쪼갠다. 이러한 작업을 하더라도 단위 테스트는 항상 통과한다.

결론

함수는 동사며, 클래스는 명사다.
정말 잘하는 프로그래머는 시스템을 프로그램이 아니라 이야기로 여긴다.
프로그래밍 언어라는 수단을 사용해 좀 더 풍부하고 표현력이 강한 언어를 만들어 이야기를 풀어간다.

규칙을 지킨다면 길이가 짧고 이름이 좋은 체계 잡힌 함수가 나온다

서술적인 이름을 사용하라

이름은 길어도 된다. 길고 서술적인 이름이 짧고 어려운 이름보다 좋다
이름을 정하느라 시간을 들여도 괜찮다.
서술적인 이름을 사용하면 개발자 머릿속에서도 설계가 뚜렷해지므로 코드를 개선하기 쉬워진다.

함수 인수

함수에서 이상적인 인수 개수는 0개다.
테스트 관점에서 보면 인수는 더 어렵다. 인수가 없는 함수와 있는 함수들을 검증하는 것을 상상해봐도 인수가 없는 것이 더 간단하다.
최선은 인수가 0개이며, 차선은 1개 이다.

많이 쓰는 단항 형식

인수를 1개 넘기는 이유로 가장 흔한 경우는 2가지이다.

  1. 인수에 질문을 던지는 경우다.
    ex) boolean fileExists("MyFile")
  2. 인수를 뭔가로 변환해 결과를 반환하는 경우다.
    ex) InputStream fileOpen("MyFile") 은 String 형의 파일 이름을 InputStream으로 변환한다.

아주 유용한 단항 함수 형식은 이벤트 이다.
이벤트 함수는 입력 인수만 있다.

플래그 인수

함수로 부울값을 넘기면 그 함수가 여러 가지를 처리한다는 의미이므로 별로다.

인수 목록

String.format 메서드가 가장 좋은 예

동사와 키워드

함수와 인수는 동사/명사 쌍을 이뤄야 한다.
ex) write(name:) or writeField(name: )

부수 효과를 일으키지 마라

부수 효과는 거짓말이다. 함수에서 한 가지를 하겠다고 약속하고선 다른 짓도 하니까!
때로는 예상치 못하게 클래스 변수를 수정한다. 때로는 함수로 넘어온 인수나 시스템 전역변수를 수정한다.

출력 인수

일반적으로 우리는 인수를 함수 입력으로 해석한다.

public void appendFooter(StringBuffer report)

appendFooter(s); -> report.appendFooter(); 로 수정

명령과 조회를 분리하라

함수는 뭔가를 수행하거나 뭔가에 답하거나 둘 중 하나만 해야 한다.
객체 상태를 변경하거나, 객체 정보를 반환하거나 둘 중 하나다.

ex) public boolean set(String attribute, String value);
attribute인 속성값을 찾아 value로 설정 후, true/false를 반환한다.
-> 사용 시 if(set("username", "unclebob"))...
호출 코드만 봐서는 어떤 동작을 하는지 모호하다.

-> 변경
if (attributeExists("username")){
setAttribute("username", "unclebob");
}

오류 코드보다 예외를 사용하라

명령 함수에서 오류 코드를 반환하는 방식은 명령/조회 분리 규칙을 미묘하게 위반한다.

try/catch 블록 뽑아내기

코드 구조에 혼란을 일으키며, 정상 동작과 오류 처리 동작을 뒤섰는다.
따로 뽑는게 좋다.

try{
    deletePageAndAllReferences(page);
}catch(Exception e){
    logError(e);
}
...
private void deletePageAndAllReferences(Page page) throws Exception{
    ddeletePage(page);
    registry.deleteReference(page.name);
    configKeys.deleteKey(page.name.makeKey());
}

오류 처리도 한가지 작업이다.

+ Recent posts