본문 바로가기
카테고리 없음

좋은 코드, 나쁜 코드 프로그래머의 코드 개선법

by kk님 2024. 3. 22.

https://product.kyobobook.co.kr/detail/S000061353995

좋은 코드, 나쁜 코드 | 톰 롱 - 교보문고

좋은 코드, 나쁜 코드 | 이 책의 가장 큰 특징은 나쁜 코드가 왜 나쁜 코드인지 설명하고, 나쁜 코드를 좋은 코드로 바꿔가는 과정을 직접 보여주는 것이다. 이를 통해 독자는 좋은 코드와 나쁜

product.kyobobook.co.kr

 
 
PR 리뷰를 하면서,
주관적이지 않은 지표가 있을까 고민하기 시작하게 되었다.
특히 설계에 관해서는 best practice가 있는지 궁금해서 책을 읽어보게 되었다.
 
 
책에서는 왜 좋은 코드를 작성해야 하는지를 먼저 설명한다.
당장은 개발하는 데만도 시간이 부족하더라도, 신경써서 작성하지 않으면 생산성이 떨어지기 때문이라고 강조를 하는데
 
프로젝트를 하면서 깨달았다.
처음에 바쁘다는 이유로, 혹은 이 정도로 괜찮겠지?하는 생각에
같이 이야기해보지 않으면
나중에는 거대한 문제 코드가 되어버릴 수 있다.
 
코드를 조립할 수 있는 조각에서 시작해야 하며,
상호간 영향을 줄 수 있는 요소는 (의존성)  최소여야 한다.
책은 이 부분부터 시작했다.  (번역서는 예시들이 재밌다)
 
 
CHAPTER 3
다른 개발자와 코드 계약
내 코드를 전부 읽지 않고도 코드를 이해할 수 있는 방법.
 
입력, 출력
 
코드를 이해하지 않고, 다른 개발자가 사용하는 부분 예를 들어 함수의 인자를 어떻게 넣어 주면 어떤 결과가 반환되는지를 제공할 수 있는 방법이 있을까? 예를 들어,
 
1. 타입스크립트로 타입 제한
2. jsDoc으로 설명
 
가끔 어떤 유틸 함수를 언제 쓸 수 있을까를 생각해본적이 있는데,
내가 만든 것이고 다른 사람도 리뷰했다면 이를 알겠지만.. 
 
컴파일러를 사용한 방법이 가장 최우선이며,
런타임에 확인하는 것(체크)이 차선

세부조항 없애기
순서가 보장되어야 하는 경우.. 나머지는 private이고 순서대로 실행하는 public 함수를 두어서, 나머지를 호출하는 방법
체크 사용
Throw로 개발자에게 알리기 . 네이밍은 hasbeen~
가능하면 세부사항을 없애도록 한다. 하지만 종속되는건 아닐까?

CHAPTER 4 오류
사용자 입력, 외부 시스템 다운, 버그
복구 가능 복구 불가능

호출하는 쪽에서 유효성검사가 필요하다는 것을 알도록
문제 발생지점에 가깝도록
오류는 신속하게

요란하게
기본값을 반환하지 않는다. (0,[],null)
자칫 빈 배열은 문제가 없는 것처럼 여겨질수 있다. 즉 실제 0과 오류 0을 구분하기 힘들다는 것
try{}catch(){}//비어있음
return;
오류를 발생시킨다 . 메시지로


명시적 방법:
컴파일러가 예외 처리를 강제
메서드 시그니처에 throws 구문을 사용하여 해당 예외를 던질 것임을 명시
검사 예외 처리
(1) outer 함수에서 try - catch로 예외를 처리하거나
예외를 포착하지 않음
(2) outer 함수 안의 inner 함수가 예외를 발생한다면, outer에서는 예외처리를 하지 않을 때 예외가 발생 할 수 있음을 알려야 한다. => 예외처리는 outer 함수를 호출하는 부분이 담당
 
암시적 방법
비검사 예외: 예외를 발생시킬 수 있다는 것을 모를 수 있다.
컴파일러가 예외 처리를 강제하지 않는 예외
주로 RuntimeException 클래스를 상속받는 예외
예외 처리를 하지 않아도 컴파일 오류는 발생하지 않지만, 실행 시에 예외가 발생
 
값이 없음 뿐만 아니라, 값을 얻을 수 없는 이유도 알리기
 
리절트 유형 (개발자들이 이 유형에 익숙해야 한다. 사용법)
 
 

CHAPTER 5 가독성 높은 코드를 작성하라

5.1 서술형 명칭 사용 (주석 사용은 지양한다. 주석도 유지보수를 해야하기 때문)

예: get~ contains~
 
만약 배열 N번째 요소를 사용해서, 주석문이 필요한 경우라면 helper 함수를 만들어보기
(그런데 js의 경우에는 배열을 그대로 사용할수도 있을 것 같다.
 const [firstName, ... middleName, lastName] = data
function firstName(data: string[]){
return data[0]
}
 

코드를 설명하기 위한 주석이아니라, 코드가 존재하는 이유를 설명하는 주석 

 
코드가 많아지는 것은 경계해야 하지만,
이해하기 쉬워야 하며, 잘 동작하고, 오류가 없는지 확인하기 쉬워야 해서 길어지는 것은 문제되지 않는다.
 
https://google.github.io/styleguide/
 

깊이 중첩되는 코드를 피할 것
중첩은 너무 많은 일을 한 결과: 더 작은 함수로 분리
 
함수 호출도 가독성이 있어야 한다.

각 매개변수의 위치가 어떤 것을 의미하는지 이해하려면, 함수의 정의를 확인해야 한다는 번거로움이 있다.

매개변수: 명명된 매개변수를 사용한다

TS에서는 객체의 형태로 매개변수에 전달한다.

매개변수: 서술적 유형

클래스 또는 열거형을 사용..
클래스를 이렇게 서술식으로 사용하는건 놀랍네

sendMessage('hi', new MessagePriority(1), RetryPolicy.ALLOW_RETRY)

클래스?
왜 객체에 리터럴 방식이 아닌 열거형 enum을 사용하지?
TS의 enum은 컴파일 되면 사라진다. 오버헤드가 있을 수 있고, 실제 값으로 변환되면서 디버깅이 어려워질 수 있다.
 
컴파일이 무사히 되는 코드?를 조심해야 할 듯
 

설명되지 않은 값을 사용하지 말라(하드코딩된 숫자 주의)

코드가 변경될 때 적절히 제거되거나 수정되어야 하는데, 하드코딩 되어있다면 이를 눈치채기가 어렵다.
 
잘 명명된 상수 사용
잘 명명된 함수 사용
(상수를 반환하는 공급자 함수, 변환을 수행하는 헬퍼 함수)
 
https://www.guru99.com/functional-programming-tutorial.html
명명함수를 사용한다
명명 함수를 사용하면, 복잡한(이해하기 어려운) 구현 세부 사항을 하위 함수에서 처리하게 할 수 있다. (자명하다면 익명함수를 쓰고, 그렇지 않다면 명명 함수를 써보는 것)
 
컴파일 환경에서 null 또는 옵셔널 반환이 가능하다는 것을 눈치챌 수 있게 작성해야 한다.

널 객체 패턴(위험한 측면도 있다)도 있기는 하지만 널 안정성과 옵셔널이 추세