패스트터틀

클린 코드(로버트 C. 마틴) - 깨끗한 코드, 이름, 함수, 주석, 형식맞추기 본문

Humanities/book - developer

클린 코드(로버트 C. 마틴) - 깨끗한 코드, 이름, 함수, 주석, 형식맞추기

SudekY 2020. 1. 30. 10:57

 

클린 코드-애자일 소프트웨어 장인정신(로버트 C. 마틴)

 내가 이러한 책을 읽을정도로 수준높은 실력을 가지지는 않았지만 평생 해야할 코딩을 굳이 주관적으로만 하기보다는 객관적인 방법에 따라 효율적으로 하면 좋지 않을까 생각이 되어서 집어 들었다.

 

"코드는 더 이상 문제가 아니라고, 모델이나 요구사항에 집중해야 한다고 생각하는 사람도 있으리라.

하지만 코드의 중요성을 등한시하는이는 언젠가 비정형적인 수학이 나오리라 기대하는 수학자와 비슷하다."

 

"우리 모두는 대충 짠 프로그램이 돌아간다는 사실에 안도감을 느끼며 그래도 안 돌아가는 프로그램보다 돌아가는 쓰레기가 좋다고 스스로를 위로한 경험이 있다. 나중에 코드를 손보겠다고 다짐했었다. 하지만 나중은 결코 오지 않는다. Leblanc's Law를 몰랐다."

 

"깨끗한 코드를 작성하는 프로그래머는 빈 캔퍼스를 우아한 작품으로 바꿔가는 화가와 같다."

 

"코드를 읽는 시간대 코드를 짜는 시간 비율이 10대 1을 훌쩍 넘는다. 새 코드를 짜면서 우리는 끊임없이 기존 코드를 읽는다. 그러므로 급하다면, 서둘러 끝내려면, 쉽게 짜려면, 읽기 쉽게 만들면 된다."

 

1. 의도를 분명히 밝혀라.

 따로 주석이 필요하다면 의도를 분명히 드러내지 못했다는 말이다.

int d // 경과 시간(단위: 날짜)
//ㅣ
//ㅣ
//ㅣ
//▽
int elapsedTimeInDays;
int daysSincesCreation;
int daysSinceModification;
int fileAgeInDays;

코드의 단순성이 아니라 코드의 함축성이다.

int > 클래스로 바꿔서 더욱 정확히 명시하였음

public List<int[]> getThem(){
	List<int[]> list1 = new ArrayListMint[]>();
    for(int[] x : theList)
    	if(x[0] == 4)
        	list1.add(x);
        return list1;
}
//ㅣ
//ㅣ
//ㅣ
//▽
public List<Cell> getFlaggedCells(){
	List<Cell> flaggedCells = new ArrayList<Cell>();
    for(Cell cell : gameBoard)
    	if(cell.isFlagged())
        	flaggedCells.add(cell);
    return flaggedCells;
}

 

2. 그릇된 정보를 피하라

-> 널리 쓰이는 의미가 있는 단어를 다른 의미로 사용해도 안된다. 

-> List가 아니라면 AccountList라고 명명 하지말라.

-> 흡사한 이름 사용하지말기

-> o나 l을 같이 쓰는 변수는 최악임. 단순한 문자 절대 사용 하지말기

 

3. 읽는 사람이 차이를 알도록 이름을 지어라

 

4. 발음하기 쉬운 이름을 사용해라

 

5. 나중에 검색하기 쉬운 이름을 사용하라

 -> 이름길이는 범위 크기에 비례해야 한다. 

 

6. 인코딩을 피하라

 -> 자바 프로그래머는 변수 이름에 타입을 인코딩할 필요가 없다.

 -> 옛날에 많이 사용하던 I (IShape, Idoll .. ) 은 주의를 흐트리고 과도한 정보를 제공한다.

 -> Interface class와 concrete class로 하나의 이름을 정할경우 차라리 I보다 C가 낫다.

 

7. 자신의 기억력을 자랑하지 마라

 -> 똑똑한 프로그래머와 전문가 프로그래머 사이에서 나타나는 차이점 하나만 들자면, 전문가 프로그래머는 명료함이 최고라는 사실을 이해한다.

 

8. 클래스이름

 -> 명사나 명사구 추천

 

9. 메서드 이름

 -> 동사나 동사구 적합

 

10. 한 개념에 한 단어를 사용하라.

 -> DeviceManager와 ProtocolController는 근본적으로 어떻게 다른가? 어째서 둘 다 Controller가 아닌가. 정말 둘 다 Driver가 아닐 이유가있나?. 이름이 다르면 독자는 당연히 클래스도 다르고 타입도 다르리라 생각한다.

 

11. 말장난 하지 마라 

 -> add를 쓸거면 항시 더한다는 개념에만 사용해라 

 

12. 해법 영역에서 가져온 이름을 사용하라

 -> 기술 관련 용어를 사용해도된다. 독자도 프로그래머다.

 

13. 문제 영역에서 가져온 이름을 사용하라.

 -> 적절한 '프로그래머 용어'가 없다면 문제 영역에서 이름을 가져온다.

 -> 우수한 프로그래머와 설계자라면 해법 영역과 문제 영역을 구분할 줄 알아야한다.

 

14. 의미 있는 맥락을 추가하라.

 -> firstName, lstName, street, houseNumber, city, state, zipcode라는 변수가 주소와 관련되어있다는것을 알지만

대체적으로 유추하는 형식이라서 따로 떨어져있을때는 알기가 힘들다.

 -> addr이라는 접두어를 추가해 addrFirstName, addrLastName 라 쓰면 맥락이 좀 더 분명해진다.

 

15. 불필요한 맥락을 없애라

 -> 고급 휘발유 충전소(Gas Station Deluxe) 라는 애플리케이션을 짠다고 가정하자

모든 클래스 이름을 GSD로 시작하겠다는것은 바람직하지 못하고 전봇대에 이 쑤시는 격이다.

 -> 일반적으로는 짧은이름이 긴 이름보다 좋다. 단 의미가 분명할때이다.

 

"이름을 바꾸지 않으려는 이유는 다른 개발자가 반대할까 두려워서이다. 그렇다고 코드를 개선하려는 노력을 중단해서는 안 된다. 다른 사람이 짠 코드를 손본다면 리팩터링 도구를 사용해 문제 해결 목적으로 이름을 개선하라. 단기적인 효과는 물론 장기적인 이익도 보장한다."

 

 

함수 작성 규칙

 

1. 작게 만들어라!

 -> 함수를 만드는 첫째 규칙은 작게!다. 둘째 규칙은 더 작게!다.

 -> if,else,while 문 등에 들어가는 블록은 한 줄이어야 한다는 의미다.

 

2. 한 가지만 해라

 

"함수가 '한 가지'만 하는지 판단하는 방법이 하나 더 있다. 단순히 다른 표현이 아니라 의미 있는 이름으로 다른 함수를 추출할 수 있다면 그 함수는 여러 작업을 하는 셈이다."

 

3. 함수 당 추상화 수준은 하나

코드는 위에서 아래로 이야기처럼 읽혀야 좋다

내려가기 규칙사용(TO문단사용법)

A와B를 하려면 C를 해야한다.

   A를 하기위해서는 D를 해야한다.

   D를 하기위해서는 F를 해야한다.

   F를 하기위해서는 H를 해야한다.

        H를 하기위해서는 I를 해야한다.

        I를 하기위해서는 O를 해야한다.

   B를 하기위해서는 Q를 해야한다.

....

...

..

.

 

4. Switch문

아래와 같이 switch문을 abstract factory에 꽁꽁 숨기라는데 아직 지식이 짧아 무슨말인지 이해를 잘 못하겠다...ㅜㅜ

public abstract class Employee{
    public abstract boolean isPayday();
    public abstract Money calculatePay();
    public abstract void deliverPay(Money pay);
}

public interface EmployeeFactory{
    public Employee makeEmployee(EmployeeRecord r) throws InvalidEmployeeType;
}

public class EmployeeFactoryImpl implements EmployeeFactory{
    public Employee makeEmployee(EmployeeRecord r) throws InvalidEmployeeType{
        switch(r.type){
            case COMMISSIONED:
                return new CommissionedEmpoyee(r);
            case HOURLY:
                return new HourlyEmployee(r);
            case SALARIED:
                return new SalariedEmployee(r);
            default:
                throw new InvaliedEmployeeType(r.type);

        }
    }
}

 

4. 서술적인 이름을 사용하라

 -> 이름이 길어도 괜찮다. 길고 서술적인 이름이 짧고 어려운 이름보다 좋다.

 -> 이름을 정하느라 시간을 들여도 괜찮다.

 -> 서술적인 이름은 개발자 머릿속에서도 설계가 뚜렷해진다.

 -> 이름을 붙일때는 일관성이 있어야 한다.

 

5. 함수 인수

 -> 함수인수는 코드를 읽는사람이 이해를 어렵게 만든다.

 -> 함수인수를 최소한으로 하여라. 가장좋은건 0개이고 많아도 삼항이상은 피하여라.

 -> flag인수는 추한것. 부울값을 넘기는 관례는 끔찍하다. 함수가 한꺼번에 여러 가지를 처리한다고 대놓고 공표하는 셈이다.

 -> 가변인수를 사용(최대 삼항까지)하고, 함수이름에 인수대로 키워드를 넣는것도 좋다.

 -> 부수효과를 일으키지마라 - 세션정보를 업데이트 하는것을 넣었다가 기존 세션정보가 날아가는 경우가 있을수도 있음

 -> 출력 인수로 public void appendFooter(StringBuffer report)를 쓸때 appendFooter(s); 말고 report.appendFooter()로 사용하라 - 함수에서 상태를 변경해야 한다면 함수가 속한 객체 상태를 변경하는 방식을 택한다.

 

6. 명령과 조회를 분리하라

 -> if( set("username", "unclebob")).... 

 -> 바꾸기 if( attributeExists("username")) { setAttribute("username", "unclebob"); ... } - 명령과 조회의 분리

 

7. 오류 코드보다 예외를 사용해라

 -> if문 중복 말고 예외처리를 사용할경우 코드가 깔끔해진다.

 -> try/catch문은 추하다. Try/Cath 블록을 별도 함수로 뽑아내는 편이 좋다.

 -> 오류처리는 '한 가지' 작업만 해야 한다. try문으로 시작해 catch/finally문으로 끝내라

 -> 오류코드 대신 예외를 사용하면 새 예외는 Exception 클래스에서 파생되므로 재컴파일/재배치 없이도 새 예외 클래스를 추가할 수 있다.

 

8. 반복하지 마라

 -> 중복을 없애면 모듈 가독성이 크게 높아진다.

 

9. 구조적 프로그래밍

 -> 함수를 작게만든다면 return, break, continue를 여러차례 사용해도 괜찮다. 다만 goto는 사용하지말아라

 

10. 함수를 어떻게 짜죠?

 -> 고치고 고쳐라

 -> 단위 테스트 케이스를 만들어라

 

11. 결론 

  -> Master 프로그래머는 시스템을 프로그램이 아니라 이야기로 여긴다. 시스템이라는 이야기를 풀어가는데 있다는 사실을 명심하기 바란다.

 

 

주석

 

"나쁜 코드에 주석을 달지 마라. 새로 짜라."

-브라이언 W. 커니핸, P.J. 플라우거-

 

 -> 주석은 기껏해야 필요악이다. 실패를 만회하기 위해 주석을 사용한다.

 -> 현실적으로 주석을 유지보수하기란 불가능하다.

 -> 주석을 유지보수하는방향보다 애초에 이해가능한 코드를 짜는것이 훨씬 효율적이다.

 

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

 -> 주석을 다는 이유는 코드품질이 나쁘기 때문이다.

 

2. 코드로 의도를 표현하라!

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

//
//

if(employee.isEligibleForFullBenefits())

위보다 아래가 훨씬 직관적이다.

 

3. 좋은 주석

 -> 정말로 좋은 주석은, 주석을 달지 않을 방법을 찾아낸 주석이다.

 -> 법적인 주석(어쩔수없이 넣어야하는주석)

 -> 정보를 제공하는 주석

 -> 의도를 설명하는 주석

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

 -> 결과를 경고하는 주석

 -> TODO주석(앞으로 할 일)

 -> 중요성을 강조하는 주석

 

 

4. 나쁜 주석

 -> 주절거리는 주석 - 주석을 달려면 시간을 들여 최고의 주석을 달도록 노력해라.

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

 -> 오해할 여지가 있는 주석

 -> 의무적으로 다는 주석 - 잘못된 정보만 제공할 여지만 만든다.

 -> 이력을 기록하는 주석 - 소스관리시스템이 생긴 현재는 쓸모없는 행위

 -> 있으나 마나 한 주석 - 너무 당연한 사실을 언급하는 주석

 -> 함수나 변수로 표현가능하면 주석 달지말아라

 -> 닫는 괄호에 다는 주석

 -> 공로를 돌리거나 저자를 표시하는 주석

 -> 주석으로 처리한 코드 - 삭제해버려라. 잃어버릴 염려는 없다.

 -> HTML주석 - 혐오 그 자체

 -> 전역 정보 - 근처코드만 주석 달기

 -> 너무 많은 정보

 -> 모호한 관계 - 주석과 주석이 설명하는 코드는 둘 사이 관계가 명백해야함

 -> 짧은 헤더

 

 

형식 맞추기

 

-> 프로그래머라면 형식을 깔끔하게 맞춰 코드를 짜야 한다. 팀을 일한다면 팀이 합의해 규칙을 정하고 모두가 그 규칙을 따라야 한다.

 

1. 형식을 맞추는 목적

 -> 매우중요하기 때문에 융통성없이 맹목적으로 따르면안된다.

 

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

 -> 대부분 500줄을 넘지 않고 대부분 200줄 정도인 파일로도 커다란 시스템을 구출 할 수 있다.

 -> 일반적으로 큰 파일보다 작은 파일이 이해하기 쉽다.

 

3. 신문 기사처럼 작성하라.

 -> 이름은 간단하면서 설명이 가능하게 짓는다.

 

4. 개념은 빈 행으로 분리하라.

package HelloWorld;
import java.util.regex.*;
public class ......{
	public static .....{
    private sta.......{
    }
    private .......{
    }
}
///////////////////////////////////////////////
//////////////아래와 같이//////////////////////

package HelloWorld;	

import java.util.regex.*;

public class ......{
	public static .....{
    
    private sta.......{
    };
    
    private .......{
    };
}

 

5. 세로 밀집도

 -> 서로 밀접한 코드 행은 세로로 가까이 놓여야 한다.

 -> 주석으로 떨어트리지 말아라

 

6. 수직 거리

 -> 서로 가까운 개념은 세로로 가까이 둬야 한다.

 -> 변수는 사용하는 위치에 최대한 가까이 선언한다. 지역변수는 각 함수 맨 처음에 선언

 -> 인스턴스변수는 c++에서는 가위 규칙에 의해 클래스 마지막에 선언하지만 자바에서는 맨 처음에 변수를 선언한다.

 -> 종속함수는 세로로 가까이 배치한다. 

 -> 개념적으로 친화도가 높은 코드는 세로로 가깝게 배치한다.

 -> 호출되는 함수를 호출되는 함수보다 나중에 배치한다. 

 -> 고차원에서 저차원으로 자연스럽게 내려간다.

 

7. 가로 형식 맞추기

 -> 80~120자리로 행 길이를 제한한다.

 -> 가로 공백을 적절히 사용하여서 가독성을 높여라

int lineSize=Line.length() ----> int lineSize = Line.length();
totalChars+=lineSize; ----> totalChars += lineSize;
return(-b*Math.sqrt(determinant))/(2*a); ----> return( -b * Math.sqrt(determinant)) / (2*a);

 -> 가로 정렬은 주석참고

public class FitNesseExpediter implemnets ResponsSender
{
private A       B
private BBB     C
private TTTTT   G
private CB      H
}

// 저자도 이런식으로 선언하는것을 좋아했지만 아무쓸도없다는 것을 알게되었음
// 괜히 엉뚱한 부분을 강조해서 진짜 의도가 가려질수 있음
public class FitNesseExpediter implemnets ResponsSender
{
private A B
private BBB C
private TTTTT G
private CB H
}

// 그냥 위와 같이 쓰라고 한다.
// 문제는 목록 길이지 정렬 부족이 아니다.

 -> 들여쓰기 안하면 가독성 떨어진다.

 -> 다음과 같이 들여쓰기 무시하고 싶을때도 하지말라고

public CommentWidget(ParentWidget parent, String text){super(parent, text);};
public String render() throws Exception{return "";}
// 이런식으로 하지말고

public CommentWidget(ParentWidget parent, String text){
	super(parent, text);
    };
public String render() throws Exception{
	return "";
    }

 -> 가짜 범위(속이 빈 while, for)는 가능한 한 피하고 세미콜론은 새행에다가 들여써서 넣어준다.

while (dis.read(buf, 0, readBuffferSize) != -1)
;

 

8. 규칙

 -> 팀에 속한다면 자신이 선호해야 할 규칙은 바로 팀 규칙이다.

 

9. 저자의 형식 규칙

 -> 아래 코드 참고(오타점검 안함)

public class CodeAnalyer implements JavaFileAnalysis{
    private int lineCount;
    private int maxLineWidth;
    private int widestLineNumber;
    private LineWidthHistogram lineWidthHistogram;
    private int totalChars;

    public CodeAnalyer() {
        lineWidthHistogram = new LineWidthHistogram();
    }

    public static List<File> findJavaFiles(File parentDirectory) {
        List<File> files = new ArrayList<File>();
        findJavaFiles(parentDirectory), files);
        return files;
    }

    private static void findJavaFiles(File parentDirectory, List<File> files) {
        for (File file : parentDirectory.listFiles()) {
            if (file.getName().endsWith(".java"))
                files.add(file);
            else if (file.isDirectory())
                findJavaFiles(file, files);
        }
    }

    public void analyzeFile(File javaFile) throws Exceeption {
        BufferedReader br = new BufferedReader(new FileReader(javaFile));
        String line;
        while ((lline = br.readLine()) != null)
            measrueLine(line);
    }

    private void measureLine(String line) {
        lineCount++;
        int lineSize = line.length();
        totalChars += lineSize;
        lineWidthHistogram.addLine(lineSize, lineCount);
        recordWidestLine(lineSize);
    }

    private void recordWidestLine(int lineSize) {
        if (lineSize > maxLineWidth) {
            maxLineWidth = lineSize;
            widestLineNumber = lineCount;
        }
    }

    public int getLineCount() {
        return lineCount;
    }

    public int getMaxLineWidth() {
        return maxLineWidth;
    }

    public int getWidestLineNumber() {
        return widestLineNumber;
    }

    public LineWidthHistogram getLineWidthHistogram() {
        return linewWidthHistorgram;
    }

    public double getMeanLineWidth() {
        return (double)totalChars/lineCount;
    }

    public int getMedianLineWidth() {
        Integer[] sortedWidths = getSortedWidths();
        int cumulativeLineCount = 0;
        for (int width : SortedWidths) {
            cumulativeLineCount += lineCountForWidth(width);
            if (cumulativeLineCount > lineCount/2)
                return width;
        }
        throws new Error("Cannot get here");
    }

    private int lineCountForWidth(int width) {
        return lineWidthHistogram.getlineforWidth(width).size();
    }

    private Integer[] getSortedWidths() {
        Set<Integer> widths = lineWidthHistogram.getWidths();
        Integer[] sortedWidths = (widths.toArray(new Integer[0]));
        Arrays.sort(sortedWidths);
        return sortedWidths;
    }
}

 

Comments