최적코딩 ( Optimal Coding ) : Achieving Purpose While Minimizing Cost
Why Optimal Coding?
최적코딩 ( Optimal Coding ) : Achieving Purpose While Minimizing Cost
Why Optimal Coding?
개발자들은 어떤 코드를 작성하고 싶을까? 아마도 Perfect 한 코드일 것이다.
누구나 Perfect 한 코드를 작성하는 법을 배우고 싶고, 실제로 작성하고 싶어 할 것이다. 모두가 Perfect 코드는 추구하지만, 사실 실제로 작성하는 것은 쉽지 않다. 그리고 실제로 작성된 코드에 대해서 그것이 정말 완벽한 지 그 기준을 누가 어떻게 정하는 것이며, 우리는 모두 그 기준에 공감 할 수 있을까? Perfect 코드는 파랑새와 같다.
어떤 코드가 가장 좋은 코드인가? 라는 질문에 나는 “최적코드” 가 가장 좋은 코드다 라고 답변하고 싶다.
클린코드, 가독성 좋은 코드
등등 모두 자신들 만의 좋은 코드에 대한 기준이 있겠지만, 내가 “최적코드” 가 가장 좋은 코드라고 생각하는 이유는 단순하다. 최적코드란 말 그대로 목적에 최적화된 코드를 말하기 때문이다. 코드를 작성하고 그 코드가 동작해야 하는 목적이라는 맥락과 동 떨어져 언제나 좋은 코드라는 개념은 존재할 수 없기 때문이기도 하다.
What is Optimal Coding?
최적화된 코드란 아래와 같이 표현할 수 있다.
최적코딩 == 코딩(개발)을 통해 얻고자 하는 목적을 달성 하면서, 코드를 생산하고 유지하고 폐기 하는데 소비된 총 생애 비용이 최소인 코딩.
단순히 이야기 하자면 가성비 좋은 코드를 개발하는 것 처럼 생각될 수 있지만 꼭 그렇지는 않다. 여기서 이야기 하는 비용은 코드 생산에 투입된 인건비, 소요된 시간 등 직접적 비용 뿐 아니라. 간접적으로 지출된 기회비용도 포함해야 한다. 가성비가 좋다고 해서 필요이상의 목적을 달성하는 코드를 작성한다면 사치가 될 뿐이다.
따라서 , 최적코드는 합의된 목적을 달성 하면서 한계 비용을 넘지 않는 코드로 봐야 한다.
How to Achieve Optimal Coding
최적코드가 무엇인지를 이해 했다면, 그럼 과연 어떻게 최적코딩을 할 수 있을까?
우선 업계에 이미 잘 알려진 좋은 코드(혹은 좋은 설계)를 위한 덕목 들에 대해 상기해 보자.

모두 맞는 말이다. 그러나 문제는 너무 많고 기억하기 어렵다는 점이다. 이러한 원칙을 하나 하나 익히고 코드를 작성할때 항상 염두에 두는 것은 결코 쉽지 않은 일이다. 하지만 이 원칙들을 관통하는 단 하나의 핵심 원칙이 있다면 어떨까? 마치 자연계의 모든 거시적 운동 법칙이
F=ma
라는 단순한 지배방정식(Governing Equation)으로 표현되듯, 최적코딩을 위한 지배방정식 혹은 지배원칙(Governing Principal)이 있다면 누구나 쉽게 기억하고 적용 할 수 있지 않을까?
최적코딩을 위한 지배방정식
IT업계뿐 아니라, 단세포에서 출발하여 지성이 출현하기까지의 과정을 꿰뚫고 적용되어 온 단 한 가지 지배원칙이 이미 존재해 왔다.
MECE ( Mutually Exclusive Collectively Exhaustive) : 빠짐없이 안겹치게
적절히 잘 분류해야 한다는 원칙이다. 너무나 단순한 원칙이지만 사실 이 세상의 모든 것이 이 단순한 원칙에 의해 탄생했다고 해도 과언이 아니다.
최근 엄청난 성과를 보이고 있는 AI 기술의 핵심도 단순화 시켜 보면 수많은 데이터를 적절히 분류하는 것이 전부이다. 분류는 유사한 것들을 모으고, 그렇지 않은 것들을 나누는 것이다. 그리고 이를 가장 완벽하게 표현한 원칙이 MECE이다.
HCLC (High Cohesion, Low Coupling)원칙에서 높은 응집도는 유사한 것들이 모여 있음을 나타내며, Low Coupling은 그렇지 않은 것들이 나뉘어 있음을 의미한다. LTDB (Logic at the Top, Data at the Bottom) 도 마찬가지이다.
복잡성을 피하고 무식할 정도로 단순화 하라는 원칙 ( KISS :Keep It Simple, Stupid)이 이야기 하는 것은 복잡성의 수준을 인간의 가독력과 단기 기억용량에 맞추라는 뜻이다. 얼마나 상세히 분류를 해야할 지 즉, 복잡성 수준을 결정하는 분류 기준을 그것을 이용하는 주체가 처리할 수 있는 역량 수준에 맞추는 것 자체도 유사성에 기반한 한차원 높은 분류에 해당할 뿐이다.
Mutually Exclusive : 겹치지 않게 경계를 설정하는 기법
분류를 위해서는 분류를 위한 틀과 각각의 항목을 가르는 경계를 설정해야 한다. 그러나, 겹치지 않는 명확한 경계를 설정하는 것은 쉽지 않은 일이다. 파도치는 바닷가의 해안선을 생각해 보자, 어디까지를 정확히 육지라고 해야 하고 어디서 부터 바다라고 해야 할까?

s/w 의 세계는 본질적으로 if 문 예/아니오 의 연속이다. 즉 애매한 것이 존재할 수 없는 세상이다. Yes or No의 단순 무식한 세계에 변화 무쌍한 바닷가의 해안선을 겹치지 않게 그려야 하는 것이 개발자의 숙명이다. 모순적으로 보이는 이러한 과제를 풀어줄 열쇠는 바로 추상화와 일반화이다.
추상화 vs 일반화
예를 들어 높은 건물의 베란다에 설치된 난간의 높이는 정확히 몇 cm 여야 안전할까? 베란다를 이용하는 사람들 , 베란다 난간의 구조 등 다양한 상황에 따라 그 구체적인 기준이 달라질 수 있을 것이다. 따라서 단순히 50cm 이상, 100cm 이상 이어야 한다는 식으로 경계를 구체화 하여 설정하는 일은 언제나 오답이 될 가능성이 크다.
‘실수나 우발적 실족 등으로 인해 투숙객이 베란다 안전 난간 밖으로 떨어지는 일이 없을 정도의 높이여야 한다.’
와 같이 그 경계를 추상적으로 설정한다면, 우리는 얼마든지 더 복잡한 상황에 대해서도 충분한 분류의 기준 즉, 경계선을 그릴 수 있게 된다.
이렇게 추상적 경계를 활용하면, 막연한 문제를 조금 더 구체화 시켜서 다룰 수 있다.
구체적 경계 vs 추상적 경계
개발자들이 가장 많이 접하는 추상화 , 일반화 과정은 method 디자인 및 응용과정에서 언제나, 반드시 발생한다. 혹시 argument 와 parameter 의 차이점에 대해 정확히 알고 있는가? argument 와 parameter 개념의 명확한 차이점을 확인하면서 개발자들이 일상에서 늘 접하는 추상화와 일반화 과정을 이해해 보자.
argument vs parameter
method를 정의할때 method가 input으로 받아들이는 변수를 parameter라고 부른다. 이 과정은 추상화 과정이다. 그리고 method를 호출할 때 입력하는 구체적인 값을 argument라고 한다. 이 과정은 일반화다.
먼저, 추상화 과정을 보자
1+1 , 1+2, 1+3, 2+2, 2+5 …. 이러한 수많은 경우의 수를 하나의 추상적 정적 루틴으로 설계한 결과가 메소드 선언/정의 이다.
다.
public int add(int former, int latter)
연산자 앞에 나온 숫자들 1, 1, 1, 2, 2 를 추상적으로 표현한 녀석이 int former 이다.
마찬가지로 연산자 뒤에 나온 숫자들 1,2,3,2,5 를 추상적으로 표현한 녀석은 int latter 이고 이것을 parameter라고 부른다. 즉 parameter 는 실제로는 값이 다른 다양한 입력값의 공통점 : int 을 찾아내어 집합을 도출해 내는 추상화 과정을 통해 정의된다.
다음은 일반화 과정이다.
int a = 1+1;
int b = 1+2;
int c = 1+3;
int d = 2+2;
int e = 2+5;
위와 같이 제 각각 구체적인 상황에서 공통점을 찾아내어 추상화 된 구조속에 대입할 수 있다.
int a = add(1,1); 1, 1 이 각각 former, latter 로 일반화 되어 add 라는 메소드의 argument로 대입되었다.
int b = add(1,2); 1, 2 이 각각 former, latter 로 일반화 되어 add 라는 메소드의 argument로 대입되었다.
int c = add(1,3); 1, 3 이 각각 former, latter 로 일반화 되어 add 라는 메소드의 argument로 대입되었다.
int d = add(2,2); 2, 2 이 각각 former, latter 로 일반화 되어 add 라는 메소드의 argument로 대입되었다.
int e = add(2,5); 2, 5 이 각각 former, latter 로 일반화 되어 add 라는 메소드의 argument로 대입되었다.
또 다른 추상화와 일반화의 사례를 보자.

아이콘을 더블 클릭하는 구체적 경우의 수는 screen 해상도에 따라 매우 많다. 짧은 시간안에 다양한 좌표를 마우스가 두번 클릭하는 구체적인 경우 (조합이 많다)를 → onBoubleClick 이라는 이벤트로 추상화하면, 이후에는 약간의 수전증이 있는 사람이라도 아이콘의 어느 구석을 클릭하던지 일반화된 결과(doubleClick)를 얻을 수 있다.
추상화는 다양한 경우에서 공통점을 찾아 하나의 틀을 구성하는 과정, 즉 설계 과정에서 주로 발생한다.
일반화는 구체적인 시행 과정에서 새로운 데이터와 상황을 기존에 설계한 틀 내에서 해석,적용하는 것이다.
이러한 추상화, 일반화 과정을 통해 MECE 중 ME(Mutually Exclusive : 겹치지 않게) 작업을 위한 경계를 명확히 설정 할 수 있다.
Collectively Exhaustive : 빠뜨리지 않고 모두 다루는 법
대부분 개발자들이 특정 모듈을 설계할때 해당 모듈의 핵심 기능에 집중하라는 말을 들어 봤을 것이다.
- SRP : Single Responsibility Principle
- KISS : Keep It Simple, Stupid
- YAGNI : You Ain’t Gonna Need It
위에 열거된 원칙들은 불필요한 오지랖 떨지 말라는 이야기를 강조하고 있다. 하지만 이러한 메시지가 많은 오해를 불러 일으키는 것도 사실이다. 집중한다는 것이 그 외의 것을 완전히 무시하라는 이야기는 아니기 때문이다.
집중을 하면 좋은 것이지만 반드시 그래야 하는 것은 아니다. 반면에 s/w 모듈은 언제나 자신에게 발생할 수 있는 모든 경우의 수를 다루어야 한다. 이것은 선택이 아니라 필수이다. 조금 과장을 하자면 위에 예시를 든 덧셈 함수(메소드)에는 입력된 argument가 int type이 아닌 경우는 물론이거니와 우리 태양계 밖 우주 어딘가에서 초신성이 폭발하는 경우에 대한 사건도 다루어야 한다. 즉, 우주에서 발생하는 모~든 경우의 수가 다 고려되어야 한다는 것이다. 덧셈 함수 하나 만들자고 우주의 모든 상황을 다 고려해야 된다니 이게 무슨 소린가 싶을 것이다. 아니 그럼, 동일한 add(1,1) 함수가 아침에 호출하면 2를 반환하고 저녁에 호출하면 3을 반환하기라도 한다는 것인가?
여기에 대한 해답도 역시 추상화와 일반화에 있다. 즉 집중의 대상이 아닌 것들을 무수한 개별의 것으로 취급하는 것이 아니라 추상화된 하나의 집합으로 취급하는 것이다. 여기서 등장하는 것이 exception 이다. 그래서 모든 로직은 True or False 의 2단 구조가 아니라 True or Fasle or Exception 3단 구조여야 한다. 집중의 대상은 True/False 의 경계를 명확히 가려내고 (ME : Mutally Exclusive) 그 외의 모든 경우의 수는 적절한 추상화 작업을 통해 Exception 집합으로 빠짐없이 (CE : Collectively Exhaustive) 다루어야 하는 것이다.
Error나 Exception 은 애물단지가 아니다. 오히려 프로그램 작동 중 발생하는 중요한 사건을 의사결정권자에게 직접 보고하기 위한 너무도 중요한 매커니즘이다. java 언어의 Exception 구조에 대해 검토해 보면서 이 부분을 이해해 보자.
Exception 의 구조는 왜 생겨 난 것인가?
MECE 관점에서 생각해 보자
Exception 구조는 exception 이 없는 랭귀지로 프로그래밍 할 경우에 자주 발생하는 고질적인 결합을 제거 하기 위해 있는 것이다.
여기서 고질적인 결합의 대상이 무엇인지를 아는 것이 핵심이다.
예외의 발생위치 vs 처리위치
통상적으로 예외가 발생한 코드의 계층과 그것을 처리해야 하는 코드가 위치하는 계층이 다르다. 통상 낮은 계층 코드에서 발생한 예외를 높은 계층 코드에서 처리해야하는 경우가 많다. try-catch, throws 등의 메시지 직보 매커니즘이 없다면 낮은 계증에서 발생한 예외상황을 여러 상위 계층을 통과하여 최종 처리 계층 코드에 보고하고 이에 따른 의사결정 결과 역시 다시 여러 계층을 통과해서 전달되어야 한다. 이런 구조는 서로 다른 수준간의 결합을 유발하여 코드의 구조를 심각하게 훼손하게 된다.
checked exception 은 왜 명시적으로 처리해야 하는가?
어떤 질문에 답변하기가 힘들다면 질문을 변경해 보는 것도 좋은 습관이다. 질문을 아래와 같이 변경해 보자
unchecked exception 은 왜 명시적으로 처리하지 않아도 되는가?
java RuntimeException API document에서 답의 힌트를 찾을 수 있다.
*RuntimeException is the superclass of those exceptions that **can be thrown during the normal operation of the Java Virtual Machine**.*
*RuntimeExceptionand its subclasses are unchecked exceptions.*
Unchecked exceptions do not need to be declared in a method or constructor’s
throwsclause if they can be thrown by the execution of the method or constructor and propagate outside the method or constructor boundary.
unchecked exception 은 Java Virtual Machine 에서 발생시키는 것이기 때문에 자바 실행 구문 상 어느곳에서나 발생할 수 있다. 그렇게 때문에 일일이 선언을 하지 않는다. 즉, 내부적으로 throws 절이 이미 있는 것과 같다는 뜻이다. 다시 말해서 내가 새로운 메소드 하나를 정의 하면 컴파일러가 투명한 글씨로 throws 절에 RuntimeException 을 추가해 준다고 생각하면 된다. (MECE 의 CE 준수)
Checked Exception 이든 unchecked Exception 이든 무시해도 좋은 Exception은 존재 하지 않는다.
Unchecked Exception은 무시해도 되는게 아닐 뿐더러 절대로 무시되는 일도 없다. Unchecked Exception 을 개발자가 처리하지 않을 경우에는 Exception 처리를 위한 끝판왕 Virtual Machine이 나서서 매우 단호하면서 확실한 방법으로 Exception을 다룬다. 바로 앱을 kill 하는 것이다.
Exception은 s/w 모듈이 처리해야 하는 모든 경우의 수를 빠뜨리지 않고 다루면서도(Collectively Exhaustive) 핵심 로직에 집중 할 수 있도록 해준다. 뿐만 아니라, 이슈가 발생한 현장과 이슈를 처리해야 하는 책임이 서로 다른 경우에 즉 Exception 발생 위치와 Exception 처리 위치가 다른 경우에 , 이러한 상이한 계층이 예외상황 처리때문에 겹합되는 것을 막아준다. (Mutually Exclusive)
버려야할 습관들
끝으로 그동한 막연하게 좋은 것으로 생각해 왔던 나쁜 습관들 몇가지를 언급하면서 글을 마무리 하겠다. 아래의 내용들은 개발자들이 흔히 좋은 것이라거나, 필요한 습관이라고 오해하는 것들 중 대표적인 것이다.
- 코드 재사용
- performance 를 위한 희생
- 오류킬러 printStackTrace
- 방어코드
코드 재사용의 역설
재사용? 한 번만 더 그딴 소리 해봐

어떤 코드가 좋은 코드인가요? 라는 질문에 가장 많이 나오는 답변 중 하나가 바로
“재사용” 가능한 코드
일 것이다. 이 말 속에 코드는 재사용 될 수록 좋다 라는 가치관이 내포되어 있다. 오늘을 기점으로 우리는 “재사용”이라는 말을 입에 담지 말도록 하자. 너무 과격한 이야기처럼 들리는가?
마음속에서 이미 이렇게 이야기 하고 있을 것이다. ‘재사용 가능한 코드가 왜 나빠? ‘
코드가 가급적 많이 재사용 된다면 전체적인 구조도 단순해지고 코드도 간결해 질테니 좋은 것 아닌가? 라고 생각이 되는게 상식이다.
과연 그런가?
실상은 정 반대이다. 나는 재사용을 고려해서 개발한 코드 때문에 전체적인 설계가 무너지는 경우를 더욱 많이 보았다. 코드는 무조건 더 많이 재사용될 수록 좋은 것이 아니다.
코드는 필요한 만큼만 정확히 재사용 되어야 한다.
재사용이 과하면 코드 내에 결합을 유발한다.
- MECE 에서 ME 원칙 위배
부족하면 코드 내에 응집도가 낮아진다.
- MECE 에서 ME 원칙 위배
재사용은 (의도적으로)하는게 아니라 결과적으로 개발해 놓고 보니 (어느새)되어 있는 것이어야 한다. 재사용 하는 것이 아니라, 재사용 되는 것이다.
Performance 를 위해 구조를 희생했다는 착각
성능은 구조를 희생해서 얻는 것이 아니다. 성능은 비용/무결성을 희생해서 얻게 되는 것이다. 가용 예산과 무결성 리스크의 한계가 정해졌다면 해당 비 기능 요건을 만족하는 최적의 구조는 정해져 있다
- performance vs architecture (X)
- performance vs cost&integrity (O)
PrintStackTrace & 방어코드
Call Stack을 모두 추적해야 하는 상황은 사실상 각각의 모듈이 자신과 인접한 모든 상황을 다루지 못 했기 때문에 발생하는 것이다. 즉, 빠짐 없이 다룬다(CE) 원칙을 지키지 않았기 때문에 생긴 문제인 것이다. 이는 철저한 CE 원칙을 지킴으로서 Exception 형태의 명시적이고 체계적인 방식으로 원인과 해결의 책임이 어디에 있는지 쉽게 알수 있는 방식으로 대체 되어야 한다.
어떤 일이 어떻게 발생할 지 모르니 겹겹이 안전하게 작성해 두는 방어 코드도 역시 마찬가지이다. 이러한 방어 코드는 자칫 자신이 다루어서는 안되는 예외상황을 코드가 있는 위치에서 스스로 다루게 만든다. 이는 중복과 결합을 의미한다. ME 원칙에 따라 자신이 적절한 해결자가 아니라면 발생한 이슈를 상부에 보고하고 전파하는 본연의 역할에만 충실 할 필요가 있다.
많이 아는 것 보다 하나라도 제대로 이해하는 것이 더 중요하다.
이상으로 필자가 IT업계에서 20여년동안의 경험과 연구를 통해 체득한 가장 이상적인 코드를 작성하는 방법에 대해 이야기 해 보았다. 반응이 좋다면 실제 코드에서 ‘최적코딩' 기법이 어떻게 적용 될 수 있는지 추가 포스팅을 통해 이어가 보도록 하겠다.
(모르는 사람이 많은 것 같은데, 밑에 있는 박수(clap) 아이콘은 1인당 50번 까지 클릭 가능하다. 참고하시길 ^^)
메타데이터
- post_id
- f3a2c0ce87c0
- slug
- 최적코딩-optimal-coding-achieving-purpose-while-minimizing-cost-f3a2c0ce87c0
- url
- https://medium.com/@neolha/%EC%B5%9C%EC%A0%81%EC%BD%94%EB%94%A9-optimal-coding-achieving-purpose-while-minimizing-cost-f3a2c0ce87c0
- canonical_url
- https://medium.com/@neolha/%EC%B5%9C%EC%A0%81%EC%BD%94%EB%94%A9-optimal-coding-achieving-purpose-while-minimizing-cost-f3a2c0ce87c0
- author_url
- https://medium.com/@neolha
- status
- ok
- fetched_at
- 2026-06-11 21:11:36