본문으로 건너뛰기

6. 예측 가능한 코드를 작성하라

올바른 코드 사용 방법을 이해하는 것은 매우 중요합니다. 코드가 실제로 하는 일이 예측과 다르다면, 예상치 못한 결과가 발생할 수 있습니다.

예측 가능한 코드를 작성하는 것은 일반적으로 코드 동작을 분명하게 하는 것입니다. 그렇지 않으면 코드가 예상한 대로 동작하지 않을 수 있습니다. 이번 장에서는 코드가 예측을 벗어나 동작하는 일반적인 경우와 이를 피하기 위한 기술을 살펴보겠습니다.

매직값을 반환하지 마세요

매직값은 함수의 정상적인 반환 유형에 적합하지 않지만 특별한 의미를 가지는 값을 말합니다. 매직값의 일반적인 예는 값이 없거나 오류가 발생했음을 나타내기 위해 -1을 반환하는 것입니다.

매직값은 함수의 정상적인 반환 유형에 들어맞기 때문에 이 값이 갖는 특별한 의미를 인지하지 못하고, 이에 대해 적극적으로 경계하지 않으면 정상적인 반환값으로 오인하기 쉽습니다.

매직값은 버그를 유발할 수 있습니다.

일반적으로 매직값을 반환하면 예측을 벗어날 위험이 있으므로 사용하지 않는 것이 가장 바람직합니다.

예를 들어, User.getAge() 함수의 에러값을 매직값인 -1로 리턴한다고 하면, 여러 유저의 평균 나이를 구하고 싶을 때 사용자의 모든 User.getAge()값을 더한 후 그 수만큼 나누게 됩니다. 하지만 매직값 -1 때문에 잘못된 결과를 얻을 수 있습니다. 이러한 버그는 예측하기 어렵습니다.

해결책으로는 널, 옵셔널 또는 오류를 반환하는 것이 좋습니다.

때때로 매직값이 우연히 발생할 수 있습니다.

개발자가 자신의 코드에 주어진 모든 입력과 이러한 입력값들이 어떤 영향을 미칠 수 있을지에 대해 충분히 생각하지 않을 때도 매직값은 반환될 수 있습니다.

널 객체 패턴을 적절히 활용하세요

널 객체 패턴을 사용하는 이유는 널값을 반환하는 대신 유효한 값이 반환되어 그 이후에 실행되는 로직에서 널값으로 인해 시스템에 피해가 가지 않도록 하는 것입니다. 이것의 가장 간단한 형태는 빈 문자열이나 빈 리스트를 반환하는 것이며, 더 정교한 예로는 모든 멤버 함수가 아무것도 하지 않거나 기본 값을 반환하는 클래스를 구현하는 것이 있습니다.

빈 컬렉션을 반환하면 코드가 개선될 수 있습니다.

getClassNames라는 함수는 html요소의 class attributes를 조회하고 그 리스트를 반환하는 함수입니다. class attributes가 존재하지 않는다면 null을 반환하는 대신 빈 배열을 반환하여 이 함수를 사용하는 쪽에서의 코드를 개선할 수 있습니다.

이 예는 코드 품질을 향상시키는 널 객체 패턴의 좋은 예입니다. 이로 인해 호출하는 쪽의 코드는 간단해지고 코드가 예측을 벗어나는 작동을 할 가능성이 매우 낮아집니다.

빈 문자열을 반환하는 것도 때로는 문제가 될 수 있습니다.

단순 사용자의 입력을 처리하는 함수의 경우 사용자가 코멘트를 입력하지 않은 것인지 혹은 빈 문자열을 명시적으로 입력한 것인지 구분하는 것이 별 의미가 없습니다. 따라서 코멘트가 없는 경우 널 대신 빈 문자열을 반환해도 됩니다.

하지만 문자열이 ID로 사용되는 곳에서는 문자열이 없는지를 파악하는 것이 중요할 수 있습니다. 이에 따라 이후 실행할 논리에 영향을 미칠 수 있으므로 문자열이 없음을 함수 호출하는 쪽에서 명시적으로 인식하도록 하는 것이 중요합니다.

더 복잡한 널 객체는 예측을 벗어날 수 있습니다.

널 객체 패턴의 복잡한 형태 중 하나는 클래스를 만들고 무해한 값을 클래스 안에 넣어두는 것입니다. 이 패턴으로 코드 작성이 간단해지지만, 이로 인해 예상을 벗어나는 결과를 초래할 수 있습니다.

예를 들어, getRandomMug()라는 리스트에서 랜덤 머그잔을 리턴하는 함수가 있다고 가정해봅시다. 만약 리스트가 비어있다면 크기가 0인 머그잔 객체를 생성해서 반환합니다. 이렇게 하면 이후 코드 작성이 간단해지지만, 심각한 버그가 아무도 모르게 발생할 수 있습니다. 예를 들어, 커피 머그잔 크기 분포 보고서 작성을 위해 해당 코드를 사용한다면 크기가 0인 커피 머그잔으로 인해 부정확한 보고서가 나올 수 있습니다.

예상치 못한 부수 효과를 피하세요

부수 효과(사이드 이펙트)란 함수 호출로 인해 함수 외부에 상태 변화가 생기는 것을 의미합니다.

부수 효과가 예상되고 의도된 경우에는 괜찮지만, 예상되지 않은 경우에는 버그를 유발할 수 있습니다.

따라서 예상치 못한 부수 효과를 방지하기 위해서는 미리 부수 효과가 발생하지 않도록 하는 것이 좋습니다.

분명하고 의도적인 부수 효과는 괜찮습니다.

부수 효과가 필요한 경우가 있습니다. 이 때는 함수명에서 해당 부수 효과가 발생한다는 것을 명시하는 이름을 사용하여 의도를 분명히 하면 좋습니다. (예: displayErrorMessage)

예기치 않은 부수 효과는 문제가 될 수 있습니다.

해결책으로는 부수 효과를 피하거나, 그 사실을 분명하게 하는 것입니다. 부수 효과를 일으키는 함수는 호출하는 쪽에서 이 사실을 명확하게 알 수 있도록 해야 합니다.

애초에 부수 효과를 일으키지 않는 것이 가장 좋지만, 실제로는 어려울 수도 있습니다. 이 경우에는 부수 효과를 피할 수 없다면 적절한 이름을 사용하여 명확하게 나타내는 것이 효과적입니다. (예: redrawAndGetPixel())

입력 매개변수 수정에 대한 주의사항

입력 매개변수를 수정하면 버그가 발생할 수 있습니다.

함수는 일반적으로 매개변수를 통해 입력을 받아들이고, 반환값을 통해 출력을 제공합니다. 입력 매개변수를 수정하는 것은 함수가 외부에 영향을 미치기 때문에 부수 효과를 일으킵니다. 많은 개발자들이 입력 매개변수의 수정이 일어나지 않을 것이라고 예상하고 있기 때문에, 이러한 부수 효과가 발생하면 예상치 못한 버그가 발생할 수 있습니다.

해결책) 변경하기 전에 복사본을 만드세요.

입력 매개변수의 값을 변경해야하는 경우, 변경하기 전에 새로운 자료구조에 원래 값을 복사하는 것이 가장 좋은 방법입니다. 이렇게 하면 원래 객체가 변경되지 않으므로 부수 효과를 방지할 수 있습니다.

오해를 초래하는 함수는 작성하지 마세요.

중요한 입력이 누락되면 예상치 못한 결과가 발생할 수 있습니다.

해결책) 중요한 입력은 필수 항목으로 만드세요.

어떤 매개변수 없이는 함수가 수행하려는 작업을 할 수 없는 경우, 해당 매개변수는 함수에 중요합니다. 이러한 매개변수에 대해서는 값을 사용할 수 없는 경우 함수를 호출할 수 없도록 널 값을 허용하지 않는 것이 더 안전합니다.

중요한 매개변수가 널 값을 받아들일 수 있게 하면 호출하는 쪽에서는 호출하기 전에 널값 여부를 확인할 필요가 없습니다. 하지만 이러한 접근 방식은 코드의 오해를 초래할 수 있으므로, 가능한 경우 필수 항목으로 만드는 것이 좋습니다. 이렇게 하면 코드의 가독성이 향상되고 예상치 못한 동작이 발생할 가능성이 줄어듭니다.

미래를 대비한 열거형 처리 방법

의존하는 코드에 부실한 가정을 하면 예상치 못한 결과가 발생할 수 있습니다. 열거형을 처리하는 경우, 나중에 열거형에 더 많은 값이 추가될 수 있다는 점을 항상 염두에 두는 것이 중요합니다. 이를 무시하고 코드를 작성하면, 예상치 못한 결과를 초래할 수 있습니다.

암묵적으로 처리하는 것은 문제가 될 수 있습니다.

enum Names {
A : 'a'
B : 'b'
}

if(value === Names.A) {
//A 처리
}
else {
// B 처리
}

위 코드는 Names에 새 값 C가 추가 된다면, B 처리를 가정했던 else문이 C값에서도 실행되어 예상치 못한 동작이 생길 수 있습니다.

해결책) 모든 경우를 처리하는 스위치 문을 작성하세요.

더 나은 접근 방법은 모든 열거값을 명시적으로 처리하고, 처리되지 않은 새로운 열거값이 추가되는 경우 컴파일이 실패하거나 테스트가 실패하도록 하는 것입니다. 이를 위한 일반적인 방법은 모든 경우를 처리하는 스위치 문을 사용하는 것입니다.

기본 케이스를 주의 하라

스위치 문은 일반적으로 처리되지 않은 모든 값에 대해 적용할 수 있는 기본 케이스를 지원합니다. 열거형을 처리하는 스위치 문에 기본 케이스를 추가하면 향후 열거형 값이 암시적으로 처리될 수 있으며 잠재적으로 예기치 않은 문제와 버그가 발생할 수 있습니다.

코드 품질 향상과 테스트의 관계

코드 품질 향상 노력에 반대하는 주장으로, 테스트가 모든 문제를 해결할 수 있다는 주장이 있습니다. 그러나 이러한 주장은 현실적으로는 그렇지 않습니다.

코드를 작성할 때 코드를 어떻게 테스트할지 고려할 수 있습니다. 그러나 예상치 못한 코드를 방지하는 것은 코드의 기술적 정확성 때문뿐만 아니라, 다른 개발자가 코드를 사용해야 할 때 코드가 올바르게 작동하도록 하는 것입니다.

하지만 테스트만으로 모든 예외 상황을 보장하기는 어렵습니다.

  • 어떤 개발자는 테스트를 충분히 수행하지 않을 수 있습니다.
  • 테스트가 항상 실제 상황을 정확하게 시뮬레이션하는 것은 아닙니다.
  • 어떤 것들은 테스트하기 어렵습니다. (특히 멀티 스레드 문제)

따라서, 테스트는 중요하지만, 코드 품질 향상은 테스트만으로 대체할 수 없습니다. 직관적이지 않거나 예상을 벗어나는 코드에는 숨겨진 오류가 있을 수 있으며, 이를 방지하기 위해서는 코드 품질 향상 노력이 필요합니다.