Software Design

최소 놀람의 원칙(Principle of Least Astonishment), getUser가 메일을 보내면 안 되는 이유

코드 리뷰하다가 이런 함수를 만난 적 있어요.

이석우iOS Developer3분 읽기
최소 놀람의 원칙(Principle of Least Astonishment), getUser가 메일을 보내면 안 되는 이유 대표 이미지

코드 리뷰하다가 이런 함수를 만난 적 있어요.

getUser()라는 이름인데, 안을 열어보니 유저를 조회하면서 마지막 접속 시간을 업데이트하고 캐시를 갱신하고, 심지어 휴면 계정이면 알림 메일까지 보내더라고요.

이름만 보고 “조회니까 안전하겠지” 하고 반복문 안에서 호출했다면? 유저들에게 메일 폭탄이 날아갔겠죠.

오늘 꺼낼 원칙은 이런 코드를 걸러내는 최소 놀람의 원칙입니다. KISS·DRY·YAGNI·SOLID로 이어온 개발 원칙 시리즈인데, 오늘 것은 이름부터 재미있어요.

코드에서 튀어나오는 깜짝 상자, 반갑지 않죠
코드에서 튀어나오는 깜짝 상자, 반갑지 않죠

최소 놀람의 원칙(Principle of Least Astonishment, 줄여서 POLA)이 말하는 건 이겁니다.

코드는 읽는 사람이 예상한 대로 동작해야 한다. 사용자를 놀라게 하는 설계가 있다면, 그 설계를 다시 생각하라.

1960~70년대 시스템 설계 쪽에서 나온 오래된 원칙인데, UI 디자인과 API 설계, 일상 코드까지 전부 적용되는 범용 원칙이에요.


놀람이 왜 비용일까?

프로그래밍에서 ’놀람’은 감정 문제가 아니라 비용 문제입니다.

개발자는 코드를 읽을 때 이름과 관례를 근거로 가설을 세우면서 읽어요. getUser는 조회만 하겠지, isValid는 불리언을 주겠지, 하고요. 코드가 가설대로 움직이면 술술 읽힙니다.

그런데 가설이 깨지는 순간, 읽던 흐름을 멈추고 함수 안으로 들어가서 진짜 동작을 확인해야 해요. 이런 함수가 코드베이스에 열 개만 있어도 “이 코드는 이름을 못 믿겠다”는 불신이 깔리고 그때부터는 모든 함수를 열어보며 읽게 됩니다. 코드 읽는 속도가 반토막 나는 거죠.

최악의 경우는 확인조차 안 하고 가설대로 썼다가 장애가 나는 경우고요. 위의 메일 폭탄처럼요.


코드에서 놀람이 튀어나오는 장면들

제가 실제로 겪었던 패턴 위주로 정리해볼게요.

1. 이름과 행동이 다른 함수

// 이름은 조회인데 안에서 상태를 바꾼다
func getUser(id: Int) -> User {
    let user = db.find(id)
    user.lastSeenAt = Date()  // 놀람 1: 부수효과
    db.save(user)
    if user.isDormant {
        mailer.send(to: user.email, message: "휴면 해제 안내")  // 놀람 2: 외부 호출
    }
    return user
}

조회 함수에 숨어 있는 쓰기 작업이 놀람의 대표 주자입니다. get은 읽기만, 상태를 바꾸면 update나 touch 같은 이름을 써야 해요.

2. 관례를 배신하는 반환값

같은 코드베이스에서 어떤 함수는 못 찾으면 nil을 주고, 어떤 함수는 예외를 던지고, 어떤 함수는 빈 객체를 준다면, 호출하는 쪽은 매번 도박을 하게 됩니다. 어느 쪽이든 하나로 통일하는 게 중요해요.

3. 침묵하는 실패

func parseConfig(_ json: Data) -> Config {
    guard let config = try? JSONDecoder().decode(Config.self, from: json) else {
        return Config()  // 놀람: 설정이 깨졌는데 아무 일 없다는 듯 빈 설정 반환
    }
    return config
}

설정 파일이 깨졌는데 조용히 기본값으로 돌아가면, 사용자는 한참 뒤에 “왜 설정이 적용이 안 되지?“로 고통받습니다. 실패는 시끄럽게 알리는 게 덜 놀랍습니다.

이름과 행동이 다르면 읽는 사람이 고생합니다
이름과 행동이 다르면 읽는 사람이 고생합니다

덜 놀라운 코드를 쓰는 요령

제가 지키려고 하는 것들은 이렇습니다.

요령 내용
이름이 계약이다 이름이 약속한 것만 하고, 그 이상을 하면 이름을 바꾼다
관례를 따른다 언어·프레임워크·팀의 기존 스타일을 이유 없이 벗어나지 않는다
부수효과는 드러낸다 상태를 바꾸는 함수는 이름에서 티가 나게 한다
놀라게 할 거면 문서로 어쩔 수 없이 특이한 동작이 필요하면 주석과 문서로 크게 알린다

이 중에 제일 강력한 건 두 번째예요. 똑똑한 나만의 방식보다 지루한 표준 방식이 팀 전체로 보면 항상 이깁니다. 지난 KISS 편에서 “있어 보이는 한 줄”보다 평범한 반복문이 낫다고 했던 것과 정확히 같은 맥락이에요.

지루하고 예측 가능한 코드가 결국 이깁니다
지루하고 예측 가능한 코드가 결국 이깁니다

마치면서

최소 놀람의 원칙에서 기억할 건 세 가지예요.

  • 코드는 이름과 관례가 만든 기대대로 동작해야 합니다. 기대를 깨는 코드가 버그의 온상이에요.
  • 조회 함수에 부수효과를 숨기지 말고, 실패를 조용히 삼키지 마세요.
  • 기발한 코드보다 지루하고 예측 가능한 코드가 팀에서는 이깁니다.

좋은 코드의 기준을 하나만 꼽으라면 저는 이걸 꼽겠어요. 읽는 사람이 놀라지 않는 코드.

최근에 코드 읽다가 “엥, 이게 왜 이렇게 동작해?” 했던 순간이 있다면, 그게 바로 이 원칙이 깨진 현장입니다.