ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 냄새와 휴리스틱
    Java 2021. 7. 22. 16:01

    주석

    부적절한 정보

    주석은 코드와 설계에 기술적인 설명을 부연하는 수단이다.

    쓸모 없는 주석

    오래된주석, 잘못된 주석, 중복된 주석, 성의없는 주석은 빠르게 삭제하자

    주석 처리된 코드

    주석으로 처리된 코드는 읽는 사람을 헷갈리게 하며, 자신이 포함된 모듈을 오염시킨다. 즉시 삭제하자
    - 정말 필요한 코드라면 소스코드 관리 시스템에서 이전버전을 가져오면 된다.

    환경

    빌드 및 테스트

    빌드 및 테스트는 모두 한 단계, 한 명령으로 끝내야 한다.
    - 빌드의 경우 소스 코드 관리 시스템에서 이것저것 따로따로 체크아웃할 필요가 없어야 한다.
    - 테스트의 경우 한 번에 실행하는 능력은 아주 근본적이고 아주 중요하기에 그 방법이 빠르고, 쉽고, 명백해야 한다.

    함수

    인수

    함수에서 인수 개수는 적을수록 좋으며, 넷이 넘어간다면 그 가치가 아주 의심스러우니 최대한 피해야 한다.

    출력 인수

    출력 인수는 직관을 정면으로 위배한다. 일반적으로 독자는 인수를 입력으로 간주한다. 함수에서 뭔가의 상태를 변경해야 한다면 함수가 속한 객체의 상태를 변경하자.

    플래그 인수

    플래그 인수는 하나의 기능을 수행해야 하는 함수에게 가장 혼란을 초래한다.

    • 참 일 경우 어떤 기능...
    • 거짓 일 경우 특정 기능...

    죽은 함수

    쓸모없으니 삭제하자. 소스관리 프로그램이 기억한다.

    일반

    한 소스 파일에 여러가지 언어

    여러가지 언어 사용은 혼잡하고 조잡스럽다. 그렇기에 소스 파일 하나에 언어 수와 범위를 최대한 줄이도록 노력하자

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

    최소 놀람의 법칙에 의거해 함수나 클래스는 다른 프로그래머가 당연하게 여길 만한 동작과 기능을 제공해야한다.
    - 이거 이렇게는 해주겠지..? -> 이게 안되네 -> 그럼 이거는 뭘 해주는거지?

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

    코드는 올바르게 동작해야 하지만... 올바른 동작이 매우 어렵고 복잡하다. 부지런함을 대신할 지름길은 없다. 모든 경계 조건, 모든 구석진 곳, 모든 기벽, 모든 예외는 우아하고 직관적인 알고리즘을 좌초시킬 암초이다.
    -스스로의 직관에 의존하지 않고, 경계 조건을 찾아내 테스트하는 테스트 케이스를 작성하자.

    안전 절차 무시

    실패하는 테스트 케이스를 일단 제껴두고 나중으로 미루는 태도는 위험하다.

    중복

    이 책에서 가장 중요한 규칙이다
    - 코드에서 중복을 발견할 경우 추상화할 기회라고 생각해라.
    - 중복된 코드를 하위 루틴이나 다른 클래스로 분리해라.

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

    추상화는 저차원 상세 개념에서 고차원 일반 개념을 분리한다.
    - 처차원 개념은 파생 클래스에 넣고 모든 고차원 개념은 기초 클래스에 넣는다.

    기초 클래스가 파생 클래스에 의존한다

    기초 클래스는 파생 클래스를 아예 몰라야 한다.

    과도한 정보

    자료, 유틸리티 함수, 상수, 임시 변수는 숨기고 인스턴스 변수가 넘치는 클래스를 피해라. 인터페이스를 매우 작게 그리고 매우 깐깐하게 만들어라

    수직 분리

    변수와 함수는 사용되는 위치에 가깝게 정의한다. 지역 변수는 처음을 사용하기 직전에 선언하며 수직으로 가까운 곳에 위치해야한다.

    일관성 부족

    어떤 개념을 특정 방식으로 구현했다면 유사한 개념도 같은 방식으로 구현한다.

    잡동사니

    호출을 안하는 변수, 함수 등 잡동사니는 제거하자

    인위적 결합

    서로 무관한 개념을 인위적으로 결합하지 않는다.
    - enum은 특정 클래스에 속할 이유가 없다. 속한다면 사용하는 코드가 특정 클래스를 알아야 한다.

    기능 욕심

    클래스 메서드는 자기 클래스의 변수와 함수에 관심을 가져야지 다른 클래스의 변수와 함수에 관심을 가져서는 안된다.

    선택자 인수

    선택자 인수는 큰 함수를 작은 함수 여럿으로 쪼개지 않으려는 게으름의 소산이다.

    모호한 의도

    의도는 분명하게 표현해야 한다.

    잘못 지운 책임

    코드는 독자가 자연스럽게 기대할 위치에 배치한다.

    부적절한 static 함수

    특정 객체와 관련이 있거나, 재정의가 일어날 함수라면 static으로 선언하지말자

    서술적 변수

    프로그램 가독성을 높인다.

    이름과 기능이 일치하는 함수

    이름만으로 분명하지 않으면 추가 문서를 봐야한다.

    알고리즘을 이해하라

    알고리즘을 올바른걸 이해하기 위해서는 기능이 뻔히 보일정도로 깔금한 함수와 명확한 재구성이 답이다.

    논리적 의존성은 물리적으로 드러내라

    한 모듈이 다른 모듈에 읜존한다면 물리적인 의존성도 있어야 한다. 의존하는 모듈이 상대 모듈에 대해 뭔가를 가정하면 안되며, 의존하는 모든 정보를 명시적으로 요청하는 편이 좋다.

    If/Else or Switch/Case 문 보다 다형성을 사용해라

    Switch/Case문 사용하기 이전에 다형성을 먼저 고려해보며, 안될경우 한 번만 사용한다. 만약 같은 선택을 수행하는 다른 코드에서도 이러한 고민이 생길경우 다형성 객체를 생성해 switch문을 대신한다.

    표준 표기법을 따르라

    따르자.

    매직 숫자는 명명된 상수로 교체하자

    숫자 뿐만 아니라 특정 String 하드코딩도 상수로 교체하자

    정확하라

    코드에서 뭔가를 결정할때에는 결정을 내리는 이유와 예외 처리할 방법을 분명히 알아야 한다. 코드에서 모호성과 부정확은 의견차나 게으름의 결과다.

    관례보다 구조를 사용하라

    설계 결정을 강제할 때는 규칙보다 관례를 사용한다. 명명 관례도 좋지만 구조 자체로 강제하면 더 좋다.

    조건을 캡슐화하라

    부울 논리는 이해하기 어렵기에 조건의 의도를 분명히 드러나는 함수로 표현해라

    부정 조건은 피해라

    긍정 조건보다 이해하기 어렵다

    함수는 한 가지만 해야한다.

    최대한 한 가지만 수행하는 함수를 만들 수 있도록 잘게 쪼개야 한다.

    숨겨진 시각적인 결합

    때로는 시각적인 결합이 필요하다. 하지만 시간적인 결합을 숨겨서는 안된다. 함수를 짤 때에는 함수 인수를 적절히 배치해 함수가 호출되는 순서를 명백히 드러낸다.
    아래의 코드는 일종의 연결 소자를 생성해 시간적인 결합을 노출한다. 각 함수가 내 놓는 결과는 다음 함수에 필요하다. 그러므로 순서를 바꿔 호출할 수 없다.

    더보기
    // 애매한 코드
    // 시간적인 결합을 강제하지 않기에 다른 프로그래머가 함수의 호출 순서를 바꿔 Exception 이 발생해도 막을 도리가 없다.
    public void dive(String reason) {
        saturateGradient();
        reticulateSplines();
        diveForMoog(reason);
    }
    
    // 더 좋은 코드  
    // 강제하는 코드
    public void dive(String reason) {
        Gradient gradient = saturateGradient();
        List<Spline> splines = reticulateSplines(gradient);
        diveForMoog(splines, reason);
    }

    일관성을 유지하라

    코드 구조를 잡을 때는 이유를 고민하라. 그리고 그 이유를 코드 구조로 명백히 표현하라. 구조에 일관성이 없어 보인다면 남들이 맘대로 바꿔도 괜찮다고 생각한다.

    경계 조건을 캡슐화하라

    경계 조건은 빼먹거나 놓치기 십상이다. 경계 조건은 한 곳에서 별도로 처리한다. 코드 여기저기에서 처리하지 않는다. 즉 (number +1, -1 을 흩어놓지 않는다.)

    함수는 추상화 수준을 한 단계만 내려가야 한다.

    함수 내 모든 문장은 추상화 수준이 동일해야 한다. 그리고 그 추상화 수준은 함수 이름이 의미하는 작업보다 한 단계만 낮아야 한다.

    더보기
    // before  
    public String render() throws Exception {  
    StringBuffer html = new StringBuffer("<hr");
    
    if (size == 0) {
        html.append("size=\"").append(size +1).append("\"");
    }
    
    html.append(">");
    }  
    
    // after  
    public String render() throws Exception {  
    HtmlTag hr = new HtmlTag("hr");
    
    if (size ==0) {
        hr.addAttribute("size", "" + (size +1));
    }
    
    return hr.html();
    }

     

    설정 저보는 최상위 단계에 둬라

    추상화 최상위 단계에 둬야 할 기본값 상수나 설정 관련 상수를 저차원 함수에 숨겨서는 안된다.

    추이적 탐색을 피해라

    일반적으로 한 모듈은 주변 모듈을 모를수록 좋다. 좀 더 구체적으로, A가 B를 사용하고 B가 C를 사용하더라도 A가 C를 알아야 할 이유는 없다. 이를 디미터의 법칙이라 부른다.

    자바

    긴 import 목록은 피하고 와일드 카드를 사용해라 import package.*;

    긴 import 목록은 읽기 부담스럽다. 명시적인 import 문은 강한 의존성을 생성하지만 와일드 카드는 그렇지 않다.
    명시적으로 import 문을 작성할 경우 그 클래스를 무조건 알아야 하지만 와일드 카드로 패키지를 지정하면 특정 클래스가 존재할 필요는 없다.
    단 와일드 카드 import 문은 때로 이름 충돌이나 모호성을 초래한다. 이름이 같으나 패키지가 다른 클래스는 명시적인 import 문을 사용하거나 아니면 코드에서 클래스를 사용할 때 전체 경로를 명시한다.
    다소 번거롭지만 자주 발생하지 않으므로 여전히 와일드 카드 import 문이 명시적인 import 문보다 좋다.

    상수는 상속하지 않는다

    상수는 상속하지 않고 static import 를 사용하자

    상수 대 enum

    public static final int 라는 옛날 기교를 더 이상 사용할 필요가 없다 int는 코드에서 의미를 잃어버리기도 한다.
    반면 enum은 그렇지 않다. enum은 이름이 부여된 열거체에 속하기 때문이다.

    이름

    서술적인 이름을 사용하라

    소프트웨어 가독성의 90%는 이름이 결정한다. 그러므로 시간을 들여 현명한 이름을 선택하고 유효한 상태로 유지한다.
    신중하게 선택한 이름은 추가 설명을 포함한 코드보다 강력하다. 신중하게 선택한 이름을 보고 독자는 모듈 내 다른 함수가 하는 일을 짐작한다.

    적절한 추상화 수준에서 이름을 선택하라

    구현을 드러내는 이름은 피하라. 작업 대상 클래스나 함수가 위차하는 추상화 수준을 반영하는 이름을 선택하라

    가능한 표준 명명법을 사용하라

    기존 명명법을 사용하는 이름은 이해하기 더 쉽다.

    명확한 이름

    함수나 변수의 목적을 명확히 밝히는 이름을 선택한다. 이름이 길다는 단점을 서술성이 충분히 커버한다.

    긴 범위는 긴 이름을 사용해라

    이름 길이는 범위 길이에 비례해야 한다. 범위가 작으면 아주 짧은 이름을 사용해도 괜찮다. 하지만 범위가 길어지면 긴 이름을 사용한다.

    인코딩을 피하라

    이름에 유형 정보나 범위 정보를 넣어서는 안 된다.

    이름으로 부수 효과를 설명하라

    함수, 변수, 클래스가 하는 일을 모두 기술하는 이름을 사용한다. 이름에 부수 효과를 숨기지 않는다.

    테스트

    불충분한 테스트

    테스트 케이스는 잠재적으로 깨질 만한 부분을 모두 테스트해야 한다. 테스트 케이스가 확인하지 않은 조건이나 검증하지 않은 계산이 있다면 그 테스트는 불완전하다.

    커버리지 도구를 사용해라

    커버리지 도구는 테스트가 빠뜨리는 공백을 알려준다.

    사소한 테스트를 건너뛰지 마라

    건너뛰지 말아라

    무시한 테스트는 모호함을 뜻한다.

    불분명한 요구사항은 테스트 케이스를 주석으로 처리하거나 테스트 케이스에 @Ignore 를 붙여 표현한다. 선택 기준은 모호함이 존재하는 테스트 케이스가 컴파일이 가능한지 불가능한지에 달려있다.

    경계 조건을 테스트하라

    경계 조건은 각별히 신경 써서 테스트한다.

    버그 주변은 철저히 테스트하라

    버그는 서로 모이는 경향이 있다. 한 함수에서 버그를 발견했다면 그 함수를 철저히 테스트 해야 한다.

    실패 패턴을 살펴라

    때로는 테스트 케이스에 실패하는 패턴으로 문제를 진단할 수 있다. 테스트 케이스를 최대한 꼼꼼히 짜라는 이유도 여기에 있다. 합리적인 순서로 정렬된 꼼꼼한 테스트 케이스는 실패 패턴을 드러낸다.

    테스트 커버리지 패턴을 살펴라

    통과하는 테스트가 실행하거나 실행하지 않는 코드를 살펴보면 실패하는 테스트 케이스의 실패 원인이 드러난다.

    테스트는 빨라야 한다.

    느린 테스트 케이스는 실행하지 않게 된다. 일정에 쫒길 수 록 느린 테스트는 건너뛰게 된다.

    'Java' 카테고리의 다른 글

    이펙티브 자바 [2] 객체 생성과 파괴  (0) 2022.11.08
    서블릿 동작원리  (0) 2021.09.16
    메서드 메모리관리  (0) 2020.06.17
    jMeter 사용하기 (with MQTT)  (2) 2020.05.12
    소켓 통신 I/O Stream  (0) 2020.05.07

    댓글

Designed by Tistory.