코드 계약이란? (협업을 위한 코드 작성법) #좋은코드나쁜코드 #3장
* 본 아티클은, '좋은 코드 나쁜 코드' 3장을 기반으로 작성된 글입니다.
코드 품질의 핵심 요소 6가지
- 가독성
- 모듈화
- 재사용성 및 일반화성
- 테스트 용이성
- 예측 가능한 코드를 작성하라
- 코드를 오용하기 어렵게 만들라
본 장은 이 중 ‘예측 가능한 코드를 작성하라’와 ‘코드를 오용하기 어렵게 만들라’는 2개와 깊게 관련이 있다.
코드를 작성할 때 다음의 3가지를 반드시 기억해야 한다.
- 자신에게 분명하다고 해서 다른 사람에게도 분명한 것은 아니다.
- 다른 개발자는 무의식 중에 당신의 코드를 망가뜨릴 수 있다.
- 시간이 지나면 자신의 코드를 기억하지 못한다.
따라서, 코드를 작성할 당시에 다른 사람(미래의 자신 포함)에게도 분명하며, 망가뜨리기 어려운 코드를 작성해야 한다.
내가 작성한 코드를 다른 사람들이 파악하는 방법
- 이름 확인
- 데이터 타입 확인
- 문서 읽기
여기까지가 실용적인 방법이고,
- 직접 묻기
- 자세한 구현 코드 읽기
위 두 가지는 비현실적이고 비효율적인 방법이다.
코드 계약
서 다른 코드 간의 상호작용을 계약처럼 생각한다.
계약에서 정의한 대로 분명하게 실행되고, 예상과 다르게 실행되는 것이 없어야 한다.
- 선결 조건 : 코드를 호출하기 전에 사실이어야 하는 것
- ex: 실행 전 시스템의 상태, 코드에 공급해야 하는 입력
- 사후 조건 : 코드가 호출된 후에 사실이어야 하는 것
- ex: 시스템이 새로운 상태에 놓였는지, 반환값
- 불변 사항 : 코드 호출 전후가 동일해야 하는 사항
명백한 사항 / 세부 조항
전기 스쿠터 렌탈 앱을 예로 들자.
- 핵심 사항
- 전기 스쿠터 대려
- 시간 당 10달러
- 세부 조항
- 충돌 사고 시 수리 비용 지불
- 시속 30마일 이상 → 벌금 부과
이중, 시속 30마일 이상에 벌금을 부과하는 항목은 당황스러운 조항일 수 있다.
코드에서는,
- 핵심 사항
- 함수와 클래스 이름
- 인자 타입
- 반환 타입
- Checked exception
- 세부 조항
- 주석, 문서
- Unchecked exception
핵심 사항으로 조건을 명백하게 하는 것이 좋다.
문서, 주석은 업데이트가 어려워 믿기 힘들다.
Unchecked exception은 처리가 강제되지 않아서 놓칠 수 있다.
세부 조항에 대한 의존성을 최대한 줄이고,
핵심 사항만으로 명확하게 코드를 설명할 수 있도록 하자.
코드 예시
class UserSettings {
UserSettings() { ... }
// 이 함수를 사용해 설정이 로드되기 전에 다른 함수 호출 X
Boolean loadSettings(File location) { ... }
// 이 함수는 loadSettings() 함수 호출 이후, 다른 함수 호출 이전에 호출해야 함
void init() { ... }
// 사용자가 선택한 UI의 색상 반환
// 선택된 색상이 없으면 null 반환
// 설정이 로드 또는 초기화되지 않았으면 null 반환
Color? getUiColor() { ... }
}
클래스, 메서드명을 통해 역할을 충분히 파악할 수 있지만,
loadSettings() → init() → getUiColor() 순서로 실행되지 않으면 문제가 발생한다.
void setUiColor(UserSettings userSettings) {
Color? chosenColor = userSettings.getUiColor();
if (chosenColor == null) {
ui.setColor(DEFAULT_UI_COLOR);
return;
}
ui.setColor(chosenColor);
}
위의 setUiColor에서 chosenColor의 null 가능성을 체크하지만, 이것이 상태의 오류인지, 사용자가 선택한 색상이 없음을 나타내는 것인지 알 수 없다.
상태의 오류가 발생해도 → 프로그램은 계속 동작한다.
세부 조항을 제거하는 방법
- 정적 팩토리 메서드 → 설정 로딩, 초기화가 완료된 인스턴스만 반환하도록 함
- getUiColor()가 null을 반환하면 → 색상을 선택하지 않았다는 뜻임
- 생성자를 private으로 설정
- 설정 관련 메서드 private 설정
class UserSettings {
private UserSettings() { ... }
static UserSettings? create(File location) {
UserSettings settings = new UserSettings();
if (!settings.loaadSettings(locations)) {
return null;
}
settings.init();
return settings;
}
private Boolean loadSettings(File location) { ... }
private void init() { ... }
Color? getUiColor() { ... }
}
상태, 가변성이 클래스 외부로 노출되는 것을 없앴다. → 버그 침투 가능성을 낮췄다.
체크, 어서션 (Check, Assertion)
- 체크 (Check)
- 전제 조건 검사 : 입력 인수가 올바르거나, 초기화가 수행되었거나, 일부 코드를 실행하기 전에 시스템의 유효성을 확인
- 사후 상태 검사 : 반환값 또는 코드 실행 후 유효성 확인
- 어서션 (Assertion)
- 체크와 매우 비슷하지만, 배포를 위해 빌드할 때 보통 컴파일에서 제외된다.
- 때문에, 실제 서비스 환경에서의 실패를 명백하게 보여주지는 않는다
- 체크와 매우 비슷하지만, 배포를 위해 빌드할 때 보통 컴파일에서 제외된다.
Color? getUiColor() {
assert(hasBeenInitialized(), "UserSettings가 초기화되지 않음");
...
}
위의 코드는 어서션의 예시이다. (테스트 코드에서 많이 보았을 것이다.)
두 가지 모두 개발 또는 테스트 단계에서 코드의 오용을 파악하는 것을 목적으로 하지만, 효과가 보장되지는 않는다.
코드 계약에 세부 조항이 있을 때 체크나 어서션을 사용하면 좋다.
하지만 가능하다면 애초에 세부조항을 피하는 것이 좋다.