클린코드(Clean Code) 2장 (swift ver.)

2장 의미 있는 이름

의도 있는 이름

의도가 분명한 이름은 정말로 중요하다. 자신을 포함한 코드를 읽는 사람이 이해가 가능하기 쉽게 작명해야한다.
변수, 함수, 클래스명은 다음 질문에 대답이 가능하게 작명해야한다.

변수(함수, 클래스)의 존재 이유는?

수행 기능은?
사용 방법은?

위 질문을 주석을 꼭 확인해야 의도를 알 수 있게 하지 않아야한다.

func getThem() -> [[Int]] { 
  var arr1: [[Int]] = []

  for x in theList { 

    if x[0] == 4 {
      arr1.append(x)
     }

  }
  return arr1
}

위 코드는 간단한 코드이지만 어떻게 동작하는지 한눈에 이해가 가지않는다. 이유는 코드의 함축성 떄문이다. 위 코드를 작성한 개발자는 분명 독자가 아래의 정보를 안다고 가정하고 작성했다.

arr1에는 어떤것이 들어가있는가?
arr1에서 0번쨰 인덱스의 값이 왜 중요한가?
값 4는 어떤 의미인가?
getThem 함수가 반환하는 arr1을 어떻게 사용하는가?

잘못된 코드는 아니지만 이해할 수 있게 도와주는 정보를 제공하는건 분명 가능했었다.

이제, 다르게 생각하여 지뢰찾기 게임을 만든다고 가정하자. 즉, arr2은 게임판이라는 사실을 알게 된다.
arr1 을 gameBoard 라고 이름을 바꿔보자.

게임판에서 각 칸은 단순 배열로 표현한다. 배열의 0번쨰 인덱스의 값은 각 칸의 상태를 뜻한다. 값 4는 깃발이 꽃힌 상태를 의미한다. 위 개념을 코드에 이름을 붙여서 바꿔보자.

func getFlaggedCells() -> [[Int]] {

    var flaggedCells: [[Int]] = []
    
    for cell in gameBoard {
        
        if cell[STATUS_VALUE] == FLAGGED {
            flaggedCells.append(cell)
        }
    }
    
    return flaggedCells
}

바뀐 코드의 단순성은 변하지 않았다. 필요한 변수와 연산자는 위에서 작성한 코드와 같다. 단지, 조금더 읽기 쉬워졌을뿐이다. 여기서 더 나아가, 칸을 클래스화 하고, isFlagged라는 더 명시적인 함수를 사용해 FLAGGED 라는 상수를 숨겨도 가능하다.

func getFlaggedCells() -> [[Cell]] {

    var flaggedCells: [[Cell]] = []
    
    for cell in gameBoard {
        
        if cell.isFlagged {
            flaggedCells.append(cell)
        }
    }
    
    return flaggedCells
} 

이렇게 변경하면 맨 처음 작성한 코드보다 훨씬 더 가독성이 뛰어나게 된다. 이것이 좋은 이름을 써야하는 이유이다.

그릇된 정보를 피하라

작명할때, 같은 개발 환경(같은 언어)에서 개발하는 사람들 뿐만 아니라 다른 개발환경에서 개발하는 사람들을 위해, 프로그래밍에서 보편적으로 널리 쓰이는 이름을 사용해서는 안된다. 작명을 someList 라고 작성해놓고 실제로는, List가 아닌경우, 독자에게 잘못된 정보를 제공하게 되는 셈이다. 이럴땐, someGroup 혹은 bunchOfSome 같은 평문으로 좀 더 잘 읽히는 방식으로 이름을 작성하는게 옳다.

또한, 서로 흡사한 이름을 사용하지 않는것이 바람직하다. 유사한 개념(viewController, cell 등등)은 유사한 표기법을 사용하게 되고, 이는 곧 정보의 일관성을 떨어뜨린다.

예를 들어, 소문자 L과, 숫자 1과, 대문자 I가 계속해서 사용되는 코드가 있다고 가정하자. 이것을 한눈에 알아보는것은 거의 불가능에 가깝다.

의미 있게 구분하라

컴파일러와 인터프리터만 통과하면 된다는 방식으로 코드를 작성하는건 나중에 필현적으로 문제가 생기게 된다. 예를 들어, 같은 범위에서 다른개념을 비슷한 이름으로 작명하는 경우가 종종 있다. 비록 이 경우는 지금의 IDE에서는 전역변수와 지역변수를 구분해주지만 그래도 주의해야한다.

컴파일러를 통과하더라도 연속된 숫자(a1, a2 …)를 덧붙이거나, 불용어를 추가하는 방식도 옳지 못한 방식이다. 이름이 다르다면, 의미도 달라야한다. 연속된 숫자를 사용하는 방식은 의도를 가지고 작명하는 방식과 정반대의 작명 방식이다. 잘못된 정보를 제공하는건 아니지만, 작성자의 의도를 알 수 없기 때문이다. 아래 코드를 예로 들어보자.

public func copyChars( ar1: inout [Character], ar2: inout [Character]) {
    for i in 0..<ar1.count {
        ar2[i] = ar1[i]
    }
}

위 코드의 파라미터를 source와 destination으로 작성하면 훨씬 더 알아보기 쉽다.

불용어를 추가해서 작성하는 방식은 아무런 정보를 제공하지않는다. Product 라는 클래스가 있다고 가정해보자. 위와 다른 클래스를 ProductInfo 혹은 ProductData 라고 작성하면 아무런 의미가 없다. 개념이 같은데 이름을 구분하지 않았기 때문이다. Info나 Data는 의미가 불분명한 불용어이다.

그렇다고 a, an, the 를 사용하지 말라는 것은 아니다. 의미가 다르다면 충분히 사용이 가능하다. 다만, 이미 같은 something 이 이미 있다고해서 theSomething을 사용하는것은 잘못된것이다.

불용어는 중복이다. 예를들어 name이라는 변수와 nameString 이라는 변수가 있다고 가정하자. 두개의 차이를 단번에 알 수 있겠는가? name이 Int 혹은 Bool로 변할 수 있는가? 즉, 아무런 차이가 없다는 것이다. 이것이 바로 그릇된 정보 제공하는것의 대표적인 예시이다. **읽는 사람이 차이를 구분할 수 있도록 이름을 지어야 한다.

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

사람은 단어에 능숙하다. 그리고 단어는 입으로 말할수가있다. 그렇지만, 발음하기 어려운 단어를 사용해가면서 작명할 이유는 없다. 발음하기 쉬울수록, 조금 더 이해하기 쉽기 때문이다.

예를 들어보자. ptltu 라는 변수가 있다. 뜻은 Print The Location, Time, Username 라는 뜻이다. 위 단어를 어떤식으로 발음하겠는가? 피티 르투? 피 틀룻 유? **발음 하기 쉬운 단어는 읽기 쉽다.

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

MAX_CLASSES_PER_STUDENT 라는 라는 변수가 있다고 가정하자. 위 변수는 검색으로 쉽게 찾을 수 있다. 하지만 임의의 숫자는? 예를 들어 3이라는 숫자를 변수에 넣었다고 하자. 검색란에 3을 넣으면 온갖 곳에서 3이 들어간 곳을 하이라이팅 할것이다. 또 임의의 알파벳 특히 모음인 e는? 알파벳 e는 가장 많이 쓰이는 문자이다. 긴 이름의 변수는 무조건 짧은 이름의 변수보다 좋다.

메서드에서 로컬변수 한 문자만 사용하는것이 좋다. 이름 길이는 범위 크기에 비례해야 한다. 아래 예제를 보자.

var s: Int = 0
var t: [Int] = []

for i in 0 ..< 34 {
    s += t[i] * 4 / 5
}

위 코드와

var realDaysPerIdealDay = 4
let WORK_DAYS_PER_WEEK = 5
let NUMBER_OF_TASKS = 34
var sum: Int = 0

var taskEstimate: [Int] = []

for i in 0 ..< NUMBER_OF_TASKS {
    
    let realTaskDays: Int = taskEstimate[i] * realDaysPerIdealDay
    let realTaskWeeks = (realTaskDays / WORK_DAYS_PER_WEEK)
    sum += realTaskWeeks
    
}

위 코드가 있다.

위 코드에서 sum은 검색이 가능하긴 하다. 하지만 WORK_DAYS_PER_WEEK 에 비하면? WORK_DAYS_PER_WEEK는 훨씬 찾기가 쉽다.

인코딩을 피하라

헝가리언 표기법과 멤버 변수에 접두어를 쓰는 표기법이 있다. 하지만, 위 표기법은 과거 컴파일러가 타입을 점검 하지 않았을때나 사용하던 방식이다. 현재는 컴파일러가 모든 변수의 타입을 기억하고 강제하기 때문에, 위와같은 표기법은 오히려 독자의 가독성을 떨어뜨린다.

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

변수명을 a,b,c … 이런식으로 작성하는것 최악이다. 자신이 기억력이 좋은것은 잘 알겠다. 다만, 위 코드를 읽을 다른사람은? 물론, for-loop 안의 반복횟수를 세는 i,j,k 는 옛낣부터 사용하던것은 괜찮지만 그 외의 변수들은 이런식으로 작성하는건, 명료함이 최고다 라는 사실을 망각한 프로그래머가 작성한 코드일 뿐이다.

클래스 이름

클래스명과 객체명은 명사 혹은 명사구가 적합하다. 단, Data, Info, Manager 만 단독으로 적는것은 피하는것이 좋고, 동사는 사용하지 않는것이 좋다.

메서드 이름

메서드명은 동사 혹은 동사구가 적합하다. save, deletePage, postPayment 와 같이 작성하는 것이 바람직하다.

기발한 이름은 피하라

기억하기 쉽게 혹은 비슷한 개념이 있어서 이름을 조금 특별한 방식으로 작성하는 경우가있다. kill() 대신 whack() 을 사용하는건 분명 웃기긴하나 저 단어를 모르는 사람한테는 또 하나의 일거리가 늘어난것 뿐이다. 의도는 분명하고 솔직하게 표현하라.

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

같은 메서드를 여러 클래스에서 다른 이름으로 쓴다고 가정하자. 어느 클래스에서 어떤 이름을 썼는지 헷갈리기 쉬워진다. 마찬가지로, controller, driver, manager 를 섞어서 쓰는것도 헷갈리기 쉽다. 이름이 다르니, 분명 타입도 다르리라 생각되기 때문이다. **일관성 있는 어취는 코드를 사용할 프로그래머한테 도움이 된다.

말장난을 하지 마라

popLast()이라는 메서드가 있다고 하자. 이 메서드는 배열의 맨 끝에 위치한 값을 반환한 후 값을 지운다. 이때, removeLast() 라는 메서드는 배열의 맨 끝의 위치한 값을 지우는 메서드가 있는데, 두 메서드 배열의 맨 끝에 위치한 값을 지우는것은 동일하다. 하지만, popLast는 반환하는 값이 있고, removeLast는 반환하는 값이 없다. 두 메서드는 같은 이름으로 불려질 수 있는가? 끄집어 내다 라는 뜻의 pop 이라는 단어와 지우다 라는 뜻의 remove는 엄연히 다른 뜻이고, 곧 이는 다른 기능을 한다는 뜻이다. **프로그래머는 코드를 최대한 이해하기 쉽게 작성해야한다. 집중해서 읽어야 이해가 가는 코드를 작성하는것이 아닌, 대충 봐도 이해가 가기 쉽게 코드를 작성해야한다.

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

코드를 읽는 사람도 같은 프로그래머이다. 스택을 “나중에 들어온 것이 먼저 나가는 특성을 가진 배열” 이라고 늘려서 부르는 것이 과연 옳을까? 읽는 사람마다 같은 개념을 스택으로 이해했던 사람이 한두번 다시 되묻는 상황이 오게 될것이다. 기술 개념에는 기술 이름이 가장 적합한 선택이다.

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

적절한 ‘프로그래밍 용어’ 가 없는 경우엔, 문제영역에서 이름을 가져온다. 이는 나중에 전문가에게 의미를 물어볼 수 가 있다. **뛰어난 프로그래머는 해법 영역과 문제 영역을 구분할 줄 알아야한다.

의미 있는 맥락을 추가하라

스스로 의미가 분명한 이름이 없지는 않다. 하지만 대부분은 그렇지가 않다. 예를 들어, firstName, lastName, street, zipcode, city, state 라는 변수가 있다. 같이 보면 state 는 주소의 도명이라는 것을 알 수가 있다. 하지만, state가 단독으로만 있다면? 한번에 state가 주소의 일부라는 것을 알 수가 있을까? 위 변수 이름의 앞에 addr을 붙여주면 위 문제는 해결된다. 아니면 Address 라는 클래스를 생성하는것도 좋다. 아래 예를 보자.

func printGuessStatistics(cadidate: Character, count: Int) {
    
    var number: String
    var verb: String
    var pluralModifier: String
    
    if count == 0 {
        number = "no"
        verb = "are"
        pluralModifier = "s"
    }
    else if count == 1 {
        number = "1"
        verb = "is"
        pluralModifier = ""
    }
    else {
        number = String(count)
        verb = "are"
        pluralModifier = "s"
    }
    
    let guessMessage:String = "There \(verb) \(number) \(cadidate)\(pluralModifier)"
    
    print(guessMessage)
}

함수가 일단 길다. 그리고 변수 세가지를 전반적으로 쓰인다. 함수를 작은 조각으로 GuessStatisticMessage 라는 클래스를 만든 후 세 변수를 클래스에 넣으면 변수의 맥락이 명확해진다. 이렇게 되면 세 변수는 확실하게 GuessStatisticMessage에 속하게 된다. 아래 코드는 변경한 코드이다.

class GuessStatisticsMessage {
    
    var number: String
    var verb: String
    var pluralModifier: String
    
    init(number: String, verb: String, pluralModifier: String) {
        self.number = number
        self.verb = verb
        self.pluralModifier = pluralModifier
    }
    
    func make(candidate: Character, count: Int) -> String {
        createPluralDependentMessageParts(count: count)
        return "There \(verb) \(number) \(candidate)\(pluralModifier)"
    }
    
    func createPluralDependentMessageParts(count: Int) {
        if count == 0 {
            thereAreNoLetters()
        }
        else if count == 1 {
            thereIsOneLetter()
        }
        else {
            thereAreManyLetters(count: count)
        }
    }
    
    func thereAreManyLetters(count: Int) {
        number = String(count)
        verb = "are"
        pluralModifier = "s"
    }
    
    func thereIsOneLetter() {
        number = "1"
        verb = "is"
        pluralModifier = ""
    }
    
    func thereAreNoLetters() {
        number = "no"
        verb = "are"
        pluralModifier = "s"
    }
}

불필요한 맥락을 없애라

고급 휘발유 충전소(Gas Station Deluxe) 라는 애플리케이션을 만든다고 가정하자. 모든 클래스를 GSD로 시작하는것은 바람직하지 않다. 이름이 긴 클래스 앞에 또 GSD 를 추가하여 만들 이유가 전혀없다. 분명 긴 이름이 짧은 이름보단 좋다. 다만, 의미가 분명한 경우에 한해서이다. **이름에 불필요한 맥락을 추가하지 않도록 주의한다.

마치면서

좋은 이름을 선택하려면 설명 능력이 뛰어나고 문화적인 배경이 같아야 한다. 바로 이 부분이 제일 어렵다. 좋은 이름을 쓰는 능력은 기술, 비즈니스, 관리 문제가 아닌 바로 교육 문제이다. 이것이 바로 작명하는 방식을 제대로 익히기 못하는 이유다.

이름을 바꾸려고 하지 않는 이유는 뭘까? 많은 이유가 있겠지만은 그 중 하나는 다른 개발자가 이름을 바꾸는거에 반대할까봐 두려운 것이다. 하지만 이제부터 우리는 달라져야한다. 좋은 이름으로 바꿔주는거에 감사하고 반가워해야한다. 우리들 대다수는 자신이 짠 클래스의 이름과 메서드의 이름을 전부 외우질 못한다. 외우는건 IDE에 맡겨두고 우리는 문장이나 문단처럼 읽히는 코드나 적어도 표나 자료구조처럼 읽히는 코드를 짜는데 집중해야한다. 이 코드를 개선하나가면서 자기 나름대로 바꾸다간 다른 누군가가 불만을 표할지도 모르지만, 그렇다고 개선하려는 노력을 멈춰서는 안된다.

좋은 웹페이지 즐겨찾기