클린코드 (1) - 의미 있는 이름

gentledot·2021년 5월 2일
0
post-thumbnail

자료 출처

이 책은 세 부분으로 나눠진다.
처음 몇 장은 깨끗한 코드를 작성하는 원칙, 패턴, 실기를 설명한다. 코드가 많아 읽기가 힘들지도 모르지만 둘째 부분을 준비하는 단계다. 첫 부분만 읽고서 책을 내려놓는다면...... 행운을 빈다.
둘째 부분은 좀 더 어렵다. 여러 사례 연구를 소개하는데, 복잡도는 점점 더 높아진다. 각 사례 연구는 코드를 깨끗하게 고치는, 즉 문제가 있는 코드를 문제 가 더 적은 코드로 바꾸는 연습이다. 상세히 살펴보려면 집중력이 필요하다. 설명과 코드를 번갈아 뒤적여야 한다. 코드를 분석하고 이해하며 코드에 가하는 변경과 이유를 납득해야 한다. 그러려면 여러 날이 걸리므로 시간도 충분히 투자해야 한다.
셋째 부분은 결말이다. 사례 연구를 만들면서 수집한 냄새와 휴리스틱(heuristic) 을 마지막 장에서 열거한다. 사례 연구에서 코드를 분석하고 정리하면서 우리는 우리 행위의 모든 이유를 휴리스틱이나 냄새로 정리했다. 코드를 분석하고 고치며 우리가 느끼는 감정을 이해하려 애썼고, 그렇게 느끼는 이유와 그렇게 고치는 이유를 잡아내려 애썼다. 코드를 짜고, 읽고, 정리하는 관점에서, 우리가 생각하는 방식을 묘사한 지식 기반을 구축했다.
- 책 들어가는 글 중 일부 발췌

개요

  • 궁극적으로 요구사항을 잘 표현(구현) 할 수 있으려면
    • 설계 의도가 잘 드러난 코드가 될 수 있어야 하고
    • 요구사항에 대해 변경이 어렵지(두렵지) 않아야 한다고 생각합니다.
  • 좋은 코드를 사수하는 일은 프로그래머의 책임이라고 이야기하고 있기 때문에 책 내용을 정리하면서 그 책임을 이행하고자 합니다.
  • 나쁜 코드를 접하고서도 눈감고 유지하려는 습성에서 벗어나고 싶다고 생각만 하지 않고 어떤 방법과 내용을 확인해야 하는지 정리하고자 합니다.
    • 나쁜 코드를 보고 느낀 생각 뿐 아니라 다른 사람이 제가 작성한 레거시 코드를 봤을 때도 욕을 먹고 싶지 않다는 마음에서도 내용을 정리하고자 하는 동기가 되었습니다.

책 내용에 대한 짧막한 정리 위주로 작성하였습니다. 앞으로의 내용은 편의상 문어체로 작성하였습니다.

제가 느끼는 생각이나 보충하고 싶은 내용은 인용 구문을 활용하여 추가 작성하였습니다. (잘못된 내용이 있다면 피드백 부탁드리겠습니다.)

의미 있는 이름

이름을 잘 짓는 규칙

  • 문장이나 문단처럼 읽히는 코드 또는 표, 자료 구조처럼 읽히는 코드를 짜는데 집중하자
  • 코드를 개선하려는 노력을 중단하지 말 것! (좋은 이름 선택하는 노력도)
  • 다른 사람이 짠 코드를 손본다면 리팩토링 도구를 사용해 문제 해결 목적으로 이름을 개선할 것

이름을 맥락에 맞게 수정하는 것은 정말로 어려운 것 같습니다. 기존에 통용되었던 단어여야 하고, 의미가 바로 읽혀야 하는 이름을 찾아야 하기 때문입니다. 그러기 위해선 코드가 어떻게 흘러가는지 맥락(context)를 이해해야 하기 때문에 더욱 어려움이 느껴졌던 것 같습니다. (지금도 어렵게 느낍니다......)

의도를 분명히 밝혀라

  • 변수, 함수, 클래스 이름은 다음의 질문 모두 답해야 한다

    • 객체의 존재 이유?
    • 객체의 수행 기능?
    • 객체의 사용 방법?
  • 따로 주석이 필요하다면 의도를 분명히 드러내지 못했다는 것

  • 의도가 드러나는 이름의 예

    int d; // 경과 시간 (단위: 날짜)
    
    int elapsedTimelnDays;
    int daysSinceCreation;
    int daysSinceModification;
    int fileAgelnDays;
  • 해독이 어려운 코드의 예

      public List<int[]> getThem() {
      		List<int []> listl = new ArrayList<int[]>();
      		for (int[] x : theList)
      			 if (x[0] = 4) listl.add(x);
      		return list1;
      }
    • theList에 무엇이 들어갔는지? (int 배열만 파악 가능)
    • theList에서 0번째 값이 왜 중요한가 (if로 분기를 준 이유?)
    • 값 4의 의미는 무엇인가? (무슨 조건인가?)
    • 함수가 반환하는 list1은 어떻게 사용하는가? (협력 체계 하에서 list1을 return하는 책임은 무엇 때문인가?)
  • 변수 변경

      // 변경 전
      public List<int[]> getFlaggedCells() {
      		List<int[]> flaggedCells = new ArrayList<int[]>();
      		for (int[] cell : gameBoard)
      			if (cell[STATUS_VALUE] = FLAGGED)
      				flaggedCells.add(cell);
      		return flaggedCells;
      }
    // 변경 후
      public List<Cell> getFlaggedCells() {
      		List<Cell> flaggedCells = new ArrayList<int[]>();
      		for (Cell cell : gameBoard)
      			if (cell.isFlagged())
      				flaggedCells.add(cell);
      		return flaggedCells;
      }
    • theList = gameBoard
    • 0번째 값 = cell[STATUS]
    • 값 4의 의미 = FLAGGED (깃발이 꽂힌 상태)
    • return 값 = 깃발이 꽂힌 cell

int[] 를 명시적인 객체로 변경하는 것으로써 함수가 하는 일, 함수가 다루는 객체를 대략적으로 이해 또는 대비 하고 코드를 작성할 수 있게 만들었다고 생각합니다.

그릇된 정보를 피하라

  • 코드에 그릇된 단서를 남겨서는 안됨! = 코드 의미를 흐리는 행위
    • accountList 변수가 List가 아니라면 그릇된 정보를 제공하는 것
      • accountGroup, bunchOfAccounts, accounts 로 명명하는 것이 좋음
      • 변수가 담고 있는 data 유형을 이름에 넣지 않는 편이 바람직

List 객체 생성 시 접미사로 -List 로 붙였던 적이 많았기에 앞으로는 데이터 유형보다는 의미 집단이 표현되는 명칭을 사용할 수 있도록 의식하면서 만들어보고자 합니다.

  • 서로 흡사한 이름을 사용하지 않도록 주의

    • XYZControllerForEfficientHandlingOfStrings 과 XYZComrollerForEfficientStorageOfStrings 의 차이는? ...
  • 소문자 L, 대문자 O 사용 시 헛갈리는 상황 발생

      int a = 1;
      ifO == 1 )
      	a = Ol;
      else
      	l = 01;

"알기 쉬운 이름으로 바꾸면 문제가 깨끗이 풀린다." 라고 나와 있으며 이름이 명확하지 않으면 결국 열어본 파일(또는 클래스) 상에서 해결할 수 없어 다른 정보를 찾아야 하기 때문에 일이 번거로워지기도 했습니다.

의미 있게 구분하라

  • 컴파일러나 인터프리터만 통과하려는 생각으로 코드를 구현하는 프로그래머는 스스로 문제를 일으킨다

    class DtaRcrdl02 {
      private Date genymdhms;
      private Date modymdhms;
      private final String pszqint = "102";
     }
    class Member {
      private Date generationTimestamp;
      private Date modificationTijnestamp;
      private final String recordld = "102";
    }

검색하기 쉬운 이름을 사용하라

  • 문자 하나를 사용하는 이름과 상수는 텍스트 코드에서 쉽게 눈에 띄지 않는다
    • MAX_CLASSES_PER_STUDENT7 중에 눈에 잘 띄는 것은...?

책의 필자 (Tim Ottinger) 개인적으로 간단한 method에서 local variable만 한 문자를 사용한다고 합니다.

  • 검색하기 쉬운 상수명의 차이

      for (int j = 0; j < 34; j++) {
      		s += (t[j] * 4) / 5;
      }
    
      int realDaysPerldealDay = 4;
      const int WORK_DAYS_PER_WEEK = 5;
      int sum = 0;
      for (int j = 0; j < NUMBER_OF_TASKS; j++) {
      		int realTaskDays = taskEstimate[j] * reaIDaysPerldealDay;
      		int realTaskWeeks = (realTaskDays / WORK_DAYS_PER_WEEK);
      		sum += realTaskWeeks;
      }

함수의 라인은 길어지지만 결국 5를 사용하는 것과 WORK_DAYS_PER_WEEK 로 사용하는 것 중 의미 파악이 쉬운 것은 후자일 것입니다.

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

  • 루프에서 반복 횟수를 세는 변수 i, j, k (l 말고) 는 허용 가능하지만 그 외는 적절치 못함
  • 똑똑한 프로그래머와 전문가 프로그래머 사이에서 나타나는 차이점 중 하나는 전문가는 명료함이 최고라는 사실을 이해한다는 것이다.

클래스 이름 명명

  • 클래스 이름과 객체 이름은 명사나 명사구가 적합
    • WikiPage, BankAccount 등
    • Manager, Processor, Data, Info 등과 같은 단어는 피하는게 좋다.
  • 동사는 사용하지 않는다.

Data, Info 는 보면서 뜨끔했던 내용이었던게 평상시에 해당 데이터의 부차정보에 대해서 -Info로 붙여 명칭을 지정한 적이 많았기 때문입니다.

java의 경우 클래스를 통해 객체단위를 형성하게 되므로 이름을 생각할 때 구현하고자 하는 객체를 생각하면서 명명하는 것이 이름 짓는데 도움이 된다고 합니다.

사실상 대상 클래스가 field나 method를 통해 객체의 상태, 행위, 책임에 대한 정보를 가지고 있기 때문에 class를 굳이 -Info라 명명하는 것는 중복적인 표현이면서 모호한 표현이라는 피드백을 받았습니다.

method 이름 명명

  • 동사나 동사구가 적합

    • postPayment, deletePage, save 등
  • 접근자(Accessor), 변경자(Mutator), 조건자(Predicate)는 javabean 표준에 따라 값 앞에 get, set, is를 붙임

    JSP 자바빈즈 - JSP/서블릿 흝어 보기

  • 생성자(Constructor)를 중복정의 (overload)할 때는 static factory method를 사용

    // Complex fulcrumPoint = new Complex(23.0);
    Complex fulcrumPoint = Complex.FromRealNumber(23.0);
    • method는 인수를 설명하는 이름 사용
    • 생성자 사용을 제한하려면 해당 생성자를 private로 선언

기발한 이름은 피하라

  • 의도를 분명하고 솔직하게 표현하자
  • HolyHandGrenade??? → DeleteItems

코딩한 개발자 혼자 알고 있는 표현보다는 범용적으로 사용되는 단어, 표현을 사용하는 것이 좋다는 것으로 생각됩니다.

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

  • method 이름은 독자적이고 일관적이어야 한다.
    • 똑같은 method를 class마다 fetch, retrieve, get으로 제각각 부르면 혼란스럽다.
  • 일관성 있는 어휘는 코드를 파악하기 쉽게한다
    • 동일 코드 기반에 controller, manager, driver 를 섞어 쓰면 혼란스럽다. (DeviceManager, ProtocolController ...)

말장난을 하지 마라

  • 한 단어를 두 가지 이상의 목적으로 사용하지 말 것!
  • add method가 기존 값 두 개를 더하거나 이어서 새로운 값을 만든다고 가정한다면
    • 집합에 값 하나를 추가한다면 add로 불러도 괜찮을까?? → insert나 append가 적합할 것임
  • 코드를 최대한 이해하기 쉽게 짜야 한다.

대충 훑어봐도 이해하기 편한 코드를 작성 하는 것이 목표!

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

  • 모든 이름을 문제 영역 (domain)에서 가져오는 정책은 현명하지 못함
    • domain 전문가인 고객에게 매번 의미를 물어야하기 때문...
  • 기술 개념에는 기술 이름이 가장 적합한 선택
    • AccountVisitor (Visitor 패턴에 친숙한 프로그래머라면 이해 가능), JobQueue 등

구현에 필요한 내용과 연관된 객체명이 아니라면 기술적인 이름을 사용함으로써 해당 객체가 어떤 동작 또는 역할을 수행할 것인지 미리 짐작해볼 수 있을겁니다.

적절한 프로그래머 용어가 없다면 문제 영역에서 가져온 이름을 사용하라

  • 해법 영역과 문제 영역을 구분할줄 알아야 한다.
  • 문제 영역 개념과 관련이 깊은 코드는 문제 영역에서 이름을 가져와야 한다.

의미 있는 맥락을 추가하라

  • 의미가 분명한 이름 > 클래스, 함수, 이름 공간에 넣어 맥락 부여 > 접두어를 붙여 의미 부여

        // 일반적인 변수명의 예 
          firstName, lastName, street, houseNumber. city, state, zipcode // state 하나만 있다면?
    
        // 주소라는 맥락을 이해할 수 있는 변수명의 예
          addrFirstName, addrLastName, addrStreet, addrHouseNumber. addrCity, addrState, addrZipcode // addr 접두어롤 통해 address라는 맥락 확인 가능
    
          // 만약 해당 변수가 class의 member라면 Address라는 맥략 파악이 더욱 용이
          // 변수들이 좀 더 큰 개념에 속한다는 사실이 컴파일러에게도 분명해진다.
          class Address{
          		...
          }
  • 맥락이 불분명한 변수

    private void printGuessStatistics(char candidate, int count) {
    		String number;
    		String verb;
    		String pluralModifier;
    
    		if (count == 0) {
    			number = "no";
    			verb = "are";
    			pluralModifier = "s";
    		} else if (count == 1) {
    			number = "1";
    			verb = "is";
    			pluralModifier = "";
    		} else {
    			number = Integer.toString(count);
    			verb = "are";
    			pluralModifier = "s";
    		}
    		String guessMessage = String.format(
    			"There %s %s %s%s", verb, number, candidate, pluralModifier
    		);
    		print(guessMessage);
    }
    • 함수가 좀 길다...
    • 세 변수를 함수 전반에서 사용한다...
  • 맥락이 분명한 변수

    public class GuessStatisticsMessage {
    		private String number;
    		private String verb;
    		private String pluralModifier;
    
    		public String make(char candidate, int count) {
    				createPluralDependentMessageParts(count);
    				return String.format(
    				"There %s %s %s%s",
    				verb, number, candidate, pluralModifier );
    		}
    
    		private void createPluralDependentMessageParts(int count) {
    				if (count = 0) {
    				thereAreNoLetters();
    				} else if (count = 1) {
    				thereIsOneLetter();
    				} else {
    					thereAreManyLetters(count);
    				}
    		}
    
    		private void thereAreManyLetters(int count) {
    				number = Integer.toString(count);
    				verb = "are";
    				pluralModifier = "s";
    		}
    
    		private void thereIsOneLetter() {
    				number = "1";
    				verb = "is";
    				pluralModifier = "";
    		}
    
    		private void thereAreNoLetters() {
    				number = "no";
    				verb = "are";
    				pluralModifier = "s";
    		}
    }
    • GuessStatisticsMessage 라는 class를 만들고 세 변수를 member 변수로 사용하여 맥락이 분명해짐
    • if로 분기된 역할은 class의 method로 구분

불필요한 맥락을 없애라

  • Gas Station Deluxe Application을 짠다고 할 때
    • 모든 class의 이름을 GSD로 시작하는 명칭으로 지정할 수 있을 것임.
      • IDE에서 G를 입력하고 자동 완성 guide에서 G로 시작하는 모든 클래스를 열거할 것이므로...
    • 그런데 GSD 회계 모듈에 MailingAddress class를 추가하면서 GSDAccountAddress로 이름 바꾸었다면?
      • 다른 고객 관리 프로그램에서 고객 주소가 필요할 때 GSDAccountAddress class를 사용한다면 적절한 이름이 될 것인가?

다른 곳에서도 쓰여야 하는 객체라면 특정 프로그램 또는 특정 위치(패키지) 로 제한하지 않는게 맥락 파악에 혼란이 없을 것 같습니다.

  • accountAddress, memberAddress는
    • class instance로는 적합
    • class 명으로는 부적합

class instance로는 적합 이라는 내용은
정해진 규약을 따라 특정 모듈을 위해 작성된 객체임을 표기하는데 적합하다는 것으로 이해하였습니다.

profile
그동안 마신 커피와 개발 지식, 경험을 기록하는 공간

0개의 댓글