본문 바로가기
프로그래밍/Java

나쁜 코드 지우기 4

by 이재만박사 2017. 11. 23.

* 일반


G1. 한 소스 파일에 여러 언어를 사용한다


 - 오늘날 프로그래밍 환경은 한 소스 파일 내에서 다양한 언어를 지원한다

예를 들어, 어떤 자바 소스 파일은 XML, HTML, YAML, Javadoc, JavaScript, 영어 등을 포함한다

또 다른 예로, 어떤 JSP 파일은 HTML, 자바, 태그 라이브러리 구문,  영어 주석, Javadoc, XML, JavaScript 등을 포함한다

좋게 말하자면 혼란스럽고, 나쁘게 말하자면 조잡하다

이상적으로는 소스 파일 하나에 언어 하나만 사용하는 방식이 가장 좋다 현실적으로는 여러 언어가 불가피하다

하지만 각별한 노력을 기울여 소스 파일에서 언어 수와 범위를 최대한 줄이도록 애써야 한다



G2. 당연한 동작을 구현하지 않는다


 - 최소 놀람의 원칙(The Principle of Least Surprise)에 의거해 함수나 클래스는 다른 프로그래머가 당연하게 여길 만한 동작과 기능을 제공해야 한다. 

예를 들어, 요일 문자열에서 요일을 나타내는 enum으로 변환하는 함수를 살펴보자


Day day = DayDate.StringToDay(String dayName);


우리는 함수가 'Monday'를 바로 Day.MONDAY 로 변환하리라 기대한다

또한 일반적으로 쓰는 요일 약어도 올바로 변환하리라 기대한다 대소문자는 당연히 구분하지 않으리라 기대한다

당연한 동작을 구현하지 않으면 코드를 읽거나 사용하는 사람이 더 이상 함수 이름만으로 함수 기능을 직관적으로 예상하기 어렵다 저자를 신뢰하지 못하므로 코드를 일일이 살펴야 한다



G3. 경계를 올바로 처리하지 않는다


 - 코드는 올바로 동작해야 한다 너무나도 당연한 말이다 

그런데 우리는 올바른 동작이 아주 복잡하다는 사실을 자주 간과한다 

흔히 개발자들을 머릿속에서 코드를 돌려보고 끝낸다

자신의 직관에 의존할 뿐 모든 경계와 구석진 곳에서 코드를 증명하려 애쓰지 않는다

부지런함을 대신할 길은 없다

모든 경계 조건, 모든 구석진 곳, 모든 기벽, 모든 예외는 우아하고 직관적인 알고리즘을 좌초시킬 암초다

스스로 직관에 의존하지 마라 모든 경계 조건을 테스트하는 테스트 케이스를 작성하라



G4. 안전 절차 무시


 - 체르노빌 원전 사고는 책임자가 안전 절차를 차례로 무시하는 바람에 일어났다 실험을 수행하기에 번거롭다는 이유에서였다 결국 실험은 제대로 수행되지 않았고, 세계는 사상 최악의 상업용 원자력 발전 사고를 목격했다

안전 절차를 무시하면 위험하다 

serialVersionUID를 직접 제어할 필요가 있을지도 모르지만 그래도 직접 제어는 언제나 위험하다

컴파일러 경고 일부를 꺼버리면 빌드가 쉬워질지 모르지만 자칫하면 끝없는 디버깅에 시달린다 실패하는 테스트 케이스를 제껴두고 나중으로 미루는 태도는 신용카드가 공짜 돈이라는 생각만큼 위험하다



G5. 중복


 - 이 책에 나오는 가장 중요한 규칙 중 하나이므로 심각하게 숙고하기 바란다

소프트웨어 설계를 거론하는 저자라면 거의 모두가 이 규칙을 언급한다

데이비드 토머스와 앤디 헌트는 이를 DRY(Don't Repeat Yourself) 원칙이라 부른다

켄트 백은 익스트림 프로그래밍의 핵심 규칙 중 하나로 선언한 후 "한 번, 단 한번만" 이라 명명했다

론 제프리스는 이 규칙을 "모든 테스트를 통과한다"는 규칙 다음으로 중요하게 꼽았다


코드에서 중복을 발견할 때마다 추상화할 기회로 간주하라

중복된 코드를 하위 루틴이나 다른 클래스로 분리하라 

이렇듯 추상화로 중복을 정리하면 설계 언어의 어휘가 늘어난다

다른 프로그래머들이 그만큼 어휘를 사용하기 쉬워진다

추상화 수준을 높였으므로 구현이 빨라지고 오류가 적어진다


가장 뻔한 유형은 똑같은 코드가 여러 차례 나오는 중복이다

프로그래머가 미친듯이 마우스로 긁어다 여기 저기로 복사한 듯이 보이는 코드다

이런 중복은 간단한 함수로 교체한다


좀더 미묘한 유형은 알고리즘이 유사하나 코드가 서로 다른 중복이다

중복은 중복이므로 Template Mothod 패턴이나 Strategy 패턴으로 중복을 제거한다



G6. 추상화 수준이 올바르지 못하다


 - 추상화는 저차원 상세 개념에서 고차원 일반 개념을 분리한다

때로 우리는 추상클래스와 파생 클래스를 생성해 추상화를 수행한다

추상화로 개념을 분리할 때는 철저해야 한다

모든 저차원 개념은 파생 클래스에 넣고, 모든 고차원 개념은 기초 클래스에 넣는다


예를 들어, 세부 구현과 관련한 상수, 변수, 유틸리티 함수는 기초 클래스에 넣으면 안 된다

기초 클래스는 구현 정보에 무지해야 마땅하다

소스 파일, 컴포넌트, 모듈도 마찬가지다 

우수한 소프트웨어 설계자는 개념을 다양한 차원으로 분리해 다른 컨테이너에 넣는다

때로는 기초 클래스와 파생 클래스로 분리하고, 때로는 소스 파일과 모듈과 컴포넌트로 분리한다

어느 경우든 철저히 분리해야 한다 고차원의 개념과 저차원의 개념을 섞어서는 안 된다


다음 코드를 살펴보자


public interace Stack

{

    Object pop() throws EmptyException;

    void push(Object o) throws FullException;

    double percentFull();

    class EmptyException extends Exception {}

    class FullException extends Exception {}

}


percentFull 함수는 추상화 수준이 올바르지 못하다 Stack을 구현하는 방법은 다양하다

어떤 구현은 '꽉 찬 정도'라는 개념이 타당하다 어떤 구현은 알아낼 방법이 전혀 없다

그러므로 함수는 BoundedStack과 같은 파생 인터페이스에 넣어야 마땅하다


크기가 무한한 스택은 0을 반환하면 되지 않습니까? 이렇게 물을지도 모르겠다

하지만 진정으로 무한한 스택은 존재하지 않는다


다음 코드는 스택 크기를 확인했다는 이유만으로 OutOfMemoryException 예외가 절대 발생하지 않으리라 장담하지 못한다


stack.percentFull() < 50.0;


그러므로 0을 반환하면 거짓말을 하는 셈이다


다시 말해, 잘못된 추상화 수준은 거짓말이나 꼼수로 해결하지 못한다

추상화는 소프트웨어 개발자에게 가장 어려운 작업 중 하나다 잘못된 추상화를 임시변통으로 고치기는 불가능하다













댓글