과거의 저와 유사한 상황의 분들에게 도움을 드리고 싶은 마음으로 시작하는 멘토링인 만큼 저렴한 가격을 책정해 두었습니다.
비전공으로, SI에 들어왔는데, 사수가 없다....
부푼 마음을 안고 끝낸 국비. 열심히 딴 정보처리기사.
하지만 지원을 아무리 넣어도 나는 제자리인 것 같다가... 마침내! 입사하게 된 SI 회사.
그런데 물어볼 사수가 없다. 회사에서는 빨리 결과물만을 달라고 한다.
아니, 방법을 알려줘야지 ㅠㅠ!!!!!!!!
동일 회사에서 3년 반 안에 계약연봉 기준 80%의 연봉 성장을 이루어낸 제가,
같은 고통을 겪어 왔던 입장의 제가,
맨몸으로 겪었던 성장통을 알려드리고 싶습니다.
어떤 식으로 제가 성장했는지, 어떤 식으로 난관을 거쳐 나갔는지에 대한 내용을 먼저 들으신다면
앞으로의 시행착오를 크게 줄이실 수 있을 겁니다.
부가적으로,
저는 인문계 고졸-학점은행제 출신으로, 회사 내 전공자들의 교육도 진행하였습니다.
학은제 학위 달성 플랜을 짜고 싶으신 분들도 관련 상담해 드릴 수 있습니다.
학점은행제로 충분히 대졸 기준 지원 회사를 지원하실 수 있습니다.
또한 f-lab 수강 경험(BE Course)이 있습니다. 해당 경험담을 듣고 싶으신 분도 환영합니다.
[주된 멘토링 대상]
- 비전공으로 개발을 시작한 주니어
- SI 회사에 입사한 3년 차 이하의 주니어
- SI 프로젝트 진행 중 이게 맞는지 혼란을 느끼는 주니어 (: 런? Stay? 현재 상황에서 얻어갈 것은?)
- 금융권 SI에 대한 내용을 듣고 싶은 주니어
- SI에서 백엔드 / 프론트엔드 등으로 직무를 구체화해 나가고 싶지만 무엇을 해야 할지 모르겠는 주니어
- f-lab 코스에서 어떤 걸 경험했는지 궁금하신 분
[얻어가실 수 있는 것]
- SI 회사에서 혼자서 성장하는 방법
- 현재 처한 SI 프로젝트에서 얻을 수 있는 것
- 금융권 SI에서 겪을 수 있는 환경
- 소위 "뻥튀기" 업체인지 알아보는 법
- 성장 가능한 회사인지 알아보는 법
- 직무 설정
- f-lab에서 배울 수 있는 것
- 면접관 입장에서 본 Good/Bad 이력서
[멘토는?]
- SI 기업 5년차 개발자
- 판교 빅테크 기업 외주 개발 진행
- 유명 스타트업 잡 오퍼
- 학점은행제 학위 취득
- f-lab BE 코스 수료
블로그
전체 32025. 03. 23.
1
발자국 3주차: 테스트에서 가장 중요한 것
테스트의 중요성? 사실 테스트가 [중요하다]는 것은 주입당한(?) 사상으로 어렴풋하게 알고 있었던 것 같다. 다들 중요하다고 하니까. 내가 보기에 그렇게 보이기도 하니까. 하지만 실무를 실제로 진행하면서 테스트를 작성할 수 있는 일 자체는 [일정]을 이유로 불가능했고,이는 테스트 코드 없이 실제 운영되는 코드를 작성하는 결과만을 낳았다. 돌이켜보면 이는 오히려 비효율적인 수동 반복 테스트를 낳기도 했다. 음, 그 시간에 차라리 테스트 코드를 짰다면... 나는 더 빠르게 테스트를 진행했을 수도 있다. 하지만 그동안은 테스트 코드가 [학습]의 영역이 아니어야 실무에 적용이 가능하다고 여겼다.(특히 일정이 이슈가 된다면 더더욱) 따라서 이번 기회에 [학습]의 꼭지점을 잡고, 프로젝트에 적용하는 것을 연습해 보기로 했다. 이번 프로그램을 신청한 목적은 [테스트] 였기 때문에 가장 초점을 두고 공부하고자 한 영역이기도 했다. 무엇을 테스트하는가? 우리는 프로그램을 만든다. 프로그램은 잘 돌아가야 한다. 잘 돌아간다는 것은 곧 정확하게 돌아가는 것과 같다 테스트는 프로그램이 [정확하게] 돌아가는 것을 보장하는 일이다.. 그렇다면 무엇을 기준으로 정확성을 판단할 수 있을까? 음... 내가 가장 어려워 하는 것은 이런 부분인 것 같았다. 테스트를 삼을 기준. 따라서 이번에는 어떤 것이 기준이어야 하는지 생각해 보았고, 그 결과는 다음 4가지로 귀결되었다. 기능적 요구사항 – 프로그램이 제공해야 하는 기능이 기대한 대로 동작하는가? 데이터 저장 후 올바르게 조회할 수 있는가? 성능적 요구사항 – 성능, 보안, 확장성과 같은 요소들이 기대치를 충족하는가? API 응답 속도가 1초 이내로 유지되는가? 동시 접속자가 많아도 서비스가 정상적으로 운영되는가? TPS는 원하는 대로 나오는가? 경계 조건 및 예외 처리 - 비정상적인 IO가 발생하는가? 입력 값이 비어 있거나, 허용 범위를 벗어난 경우에도 오류 없이 처리되는가?예외 시에 적절한 예외 반환을 보장하는가? 정확한 유효성 검사를 수행하는가? 데이터 무결성의 검증 - 트랜잭션은 작동했는가? 하나의 작업에 대해 의도한 모든 트랜잭션이 적절하게 적용되었는가? 실패한 프로세스는 없는가? 실패 시 Fallback이 정확하게 이뤄졌는가? 사실 무엇을 테스트할까 알아보면서 내리게 된 결론은.. 한 가지의 방향점을 가리키고 있었다. 지금 내가 세운 기준은 그동안 세워 왔던 단위 테스트 명세서에 들어 있던 것들이라는 걸. 왜 이것들을 진작 연결지어 보지 못했었는지. 테스트가 개발의 병목이 되지 않으려면 좋다. 이제 어떤 걸 테스트할지에 대해서는 명확해졌다. 강의에서는 [어떻게] 테스트를 하는지에 대한 방법론과 그걸 [할 때]의 효율성에 대해 다룬다. 강의를 보게 됨으로써 내 테스트는 한 층 더 정교해지고 빨라졌을 것이다. 그런데 나는 한 가지를 더 생각해 보고 싶었다. [무엇을 우선순위로 삼을 것인가.] 서두에도 언급한 만큼, 테스트 코드를 작성하는 것은 중요하지만, 현실적으로 일정이 촉박한 상황에서 테스트가 개발 속도를 저해하는 요소가 되어서는 안 된다. 따라서, 테스트를 작성하면서도 일정을 맞추는 방법을 고민해야 한다. 이럴 때 필요한 것이 우선순위가 아닐까 한다. 나는 이번에 다음과 같은 우선 순위를 결정해 보았다. 1. 핵심 로직만을 검증하는 Smoke Test(기본 동작 여부 확인) 모든 코드에 대해 100% 테스트 커버리지를 목표로 하면 개발은 지연될 수 있다. 따라서 일정이 촉박한 상황에서는 가장 중요한 핵심 로직과 장애 발생 가능성이 높은 부분만을 테스트 코드로 먼저 작성해야 한다는 생각이 들었다. 이를 비롯해 내가 처음에 이야기했던 [반복 수행될 수밖에 없는 케이스] 같은 곳. 어떠한 케이스 테스트를 코드를 수정할 때마다 돌려 봐야 한다면, 그 케이스들을 먼저 작성하고, 코드로 옮길 수 있는 부분들을 먼저 고려해 볼 것 같다. 2. 테스트 전략을 역할별로 나누어 적용하기 테스트를 작성할 때 모든 것을 하나의 방식으로 해결하려고 하면 시간이 오래 걸릴 수 있다. 이에 따라 역할별로 최소한의 전략을 선택하면 효율적인 테스트 작성이 가능할 것 같다. 단위 테스트(Unit Test)메서드 단위로 검증하여 빠르게 문제를 찾는다.검증이 잦게 필요한 곳이나 오류가 나기 쉬운 테스트에 우선 적용한다.통합 테스트(Integration Test)여러 모듈 간의 연동을 검증한다. 실행 속도가 느리므로 꼭 필요한 부분에만 적용한다.UI 테스트사용자 흐름을 검증하는 테스트로, 작성 및 유지보수에 시간이 많이 걸린다.핵심 기준을 명확하게 세우고, 비즈니스 로직과 분리해서 테스트한다. 즉, 단위 테스트를 우선 작성하여 빠르게 피드백을 받고, 일정에 여유가 있다면 통합 테스트를 추가하는 방식으로 접근하면 효율적일 것 같다. 또한 업무 단위로 테스트가 이뤄지는 곳은 UI 테스트가 통합 테스트로 여겨지기도 하기 때문에, 이러한 분리를 거친다면 검증에 대한 효율이 올라갈 듯하다. 테스트는 ##이다. 이번에 내리게 된 결론. 테스트는 결국 시간의 효율을 위한 것이다. 코드는 사람이 작성하는 것이며, 사람은 실수를 한다. 테스트는 이러한 실수를 빠르게 발견하고 수정할 수 있도록 도와준다. 좋은 테스트는 단순히 버그를 잡는 것이 아니라, 개발자와 사용자 모두에게 소프트웨어가 신뢰할 수 있는지에 대한 확신을 제공한다. 그 고민의 시간을 줄여 줄 것이다. 코드가 변경될 때마다 테스트를 통해 예상치 못한 문제가 발생하지 않는다는 것을 확인할 수 있으며, 이는 코드의 품질을 유지하는 가장 확실한 방법이다. 결국, 테스트는 단순한 검증 과정이 아니라, 코드의 신뢰성을 보장하는 핵심 요소이다. 이를 통해 개발자는 더 빠르고 안정적으로 코드를 수정할 수 있으며, 사용자 역시 신뢰할 수 있는 소프트웨어를 사용할 수 있다. 이 모든 것을 알면서도 주저할 수밖에 없는 것은 당장의 시간, 당장의 일정, 당장의 촉박함. 하지만 이번 회고를 토대로 나는 그러한 상황에서도 어떤 의사결정을 내릴지 결정할 수 있었다. 강사님은 말씀하셨다. 고용된 우리는 프로이며, 프로의 첫 의무는 일정 준수라고. 뼈에 오늘도 새겨 두면서 ..... 이제 앞으로 남은 강의와, 네 번째 발자국이 남아 있다. 3월의 마지막 주도 잘 갈무리해 보고자 한다.
2025. 03. 16.
1
발자국 2주차: 읽기 좋은 코드 == 쓰기 좋은 코드
읽기 좋은 코드, 쓰기 좋은 코드 이번 주의 발자국 회고. Readable 코드에 대한 이야기가 마무리되는 주간이다. 이번에는 강의 듣기와 함께 미션을 토대로, 내가 실제로 코드를 구현해 보는 시간을 가져갈 수 있었는데, 중간 점검 타임에서 과제에 대해서 조금 더 짚어주시면서 내 코드를 돌이켜보는 기회를 얻을 수 있었다. 사실 Spring과 JPA를 사용하는 평소 개발 방식은 많은 부분 "이미 모듈화된" 것들을 사용하는 경우가 많다. 특히 어노테이션을 쓴다든지 하는 케이스의 런타임 객체 관리 위임과 같은 상황에서 우리는 책임의 분리를 덜 고려하고도 편안한 개발을 할 수 있다. 단적인 예만 해도 Dispatcher Servelt은 우리의 xml 등록을 대리해 주고, 스프링은 Bean을 대신 관리해서 DL을 말려준다. (ㅋㅋ) 이런 것들이 감춰져 있기 때문에 우리는 서비스 로직을 조금 더 경량화할 수 있다. 그런데 기껏 스프링이 열심히 도와준 걸 망치면 안 되지 않을까? 필드를 얼마나 넣을 것인지, 이 객체의 책임 = 변경 가능 요소는 몇 개인지와 같은 것들을 잘 고려해 보면서 리팩토링 과제를 진행해 보았다. 그리고 이러한 미션을 점검하는 중간 회고. 중간 점검 회고 우선 중간 점검에서 했던 질문 시간이 꽤 재미있었던 기억이 난다. 멘토님의 커피 사랑 ㅋㅋㅋ 을 알 수 있는 좋은 기회이기도 했고... 나중에 게이샤 커피 꼭 먹어 보겠습니다. 공통 리뷰와 함께 개별 신청자에 대한 리뷰를 진행하는 2시간 가까이의 시간이었다. 목이 아프셨다고 했는데, 다음날 출근해서 말 한마디 못하시는 건 아니었는지 염려가 된다. 주요 내용은 장표를 공유해 주셨는데, 내가 이 내용 외에도 다른 분들 코드 피드백을 들으면서 인상깊었던 부분들을 정리해 보았다. 사물함 사용 가능 여부 등의 ENUM 처리: 추천컬렉션을 가공하는 로직이 생기면 일급 컬렉션을 고민해 볼 것Get / Set이 아닌 연상되는 단어 선택한 점이 좋음if-else보다 if-early return을 추천Mutable 컬렉션보다는 한번에 Immutable 컬렉션을 만들 것많은 클래스에서 사용한다 == 하나의 객체에 책임이 과도하게 몰려 있는 것은 아닌가? : 객체 분리의 신호탄IO 로직이 변경되어도 우리의 도메인 로직은 순수하게 보존되어야 함 이런 이야기가 있으면 나는 이중에서 나에게 도움이 가장 많이 된 것들을 꼽고는 하는데, 이번 글에서는 꼽을 수가 없다. 정말 모든 관점이 큰 도움이 되었던 것 같다. 강의 회고 하고 싶은 것 현재 진행 중인 실무 프로젝트에 배운 내용을 점진적으로 적용해보기"능동적 읽기" 방식으로 오픈소스 코드를 분석하며 좋은 패턴 학습하기 (TOBE - 진짜?) 이번 주간으로 에 대한 강의가 끝이 났다. 사실 강의는... 진짜 솔직하게 말하면 아는 내용이 많다고 생각하면서 봤었는데. 실제로 코드를 리팩토링 하는 과정에서 그 생각이 제법 오만이라는 생각을 계속해서 하게 되었다. 역시 이론과 실제는 다르고, 이상과 활용은 천지차이다. 이번 강의와 미션을 통해 클린코드와 객체 지향 설계의 중요성을 실제로 체감할 수 있었다. 특히 회사에서 가장 의식적으로, 많이 노력하려고 했던 것. "코드는 작성하는 시간보다 읽는 시간이 훨씬 많다"는 강의 내용을 들었던 것을 염두에 두고 코드를 작성하고자 노력을 많이 했다. 사실 가장 어려운 부분은 적절한 추상화 레벨을 결정하는 것인 것 같다. 어떻게 인터페이스를 나누고 책임을 나눠야 하는지... 너무 세부적으로 메서드를 분리하면 오히려 코드 흐름이 파악하기 어려워질 것이고, 너무 크게 묶으면 단일 책임 원칙을 위반하게 될 것이다. 이러한 관점에서 하나 인상깊었던 것이 떠올랐는데, 멘토님이 예로 들어 주셨던 조건 분기문에 대한 코드 분리가 그것이다. 예를 들면, if(type.equals("blahblah")) 일 때 if(isEditable()) 으로 코드를 바꾸고, isEditable에 대한 함수를 하나 더 빼는 형식이다. 이 조건일 때 이런 행위를 한다는 분기를 하나의 함수로 표현하는 것. 사실 객체의 상태를 객체 안에서만 넣는 바람에 밖에서 나눠 볼 생각을 못했던 것 같은데.... 결국 코드는 1줄이나 2줄인 게 중요한 게 아니라, 들이는 공수를 대비해서, 로직을 망가뜨리지 않는 범위에서의 효율을 추구하는 일이라는 것. 이 균형을 맞추는 것이 클린코드의 핵심이라는 것을 조금 더 깨달았다. 읽기 좋은 코드 == (나중에) 쓰기 좋은 코드 따라서 이번에 정립하게 된 것. 읽기 좋은 코드는 동시에 쓰기 좋은 코드이다. 사실 읽기 좋은 코드를 짜다 보면 옆에서 "왜 굳이?" 라는 표현을 들을 수 있다는 생각이 든다. 그런데 나는 읽기 좋은 코드가 오히려 쓰기 좋은 코드라고 생각한다. 보다 정확하게 말하면, "내가 나중에 쓰기 좋은 코드"라고 생각한다. 단적인 예로 일급 컬렉션. 일급 컬렉션은 개발자의 책임을 줄여 로직이 망가질 확률을 최소화하는 일이다. 만약 특정 변수를 주입해서 판단하는 로직이 있다면, 그 로직은 변수에 종속적이며, 추후 개발자의 판단에 종속된다. 우리는 판단을 최소화함으로써 행동을 제약하고, 제약한 행동은 나중에 내가 쓰기 좋은 코드를 낳는다. 행복한 선순환이다. 하지만. "오만"을 참는 법 처음부터 완벽한 코드는 오만이다 이번 중간점검 때 멘토님이 하신 말씀이 있다. 우리는 개발자고, 회사에 고용되어 일하는 것은 프로라는 뜻이다. 그리고 프로가 가장 중요하게 여겨야 할 것은 시간 관리다. 즉, 코드 퀄리티를 우선하다 주객이 전도되는 상황을 일으키는 것은 옳지 않다는 이야기다. 이러한 코드에 대해서는 추후 반영 시 가능하다면 리팩토링을 하는 식으로 점진적 개선을 할 수 있어야 한다. 이상적인 클린코드를 추구하다가 실용성을 간과한 경우를 자주 접하게 된다. 가령 진짜 if문 하나로 해결되는 문제를 객체로 분리했을 때 생기는 문제라든지.... 최신 트렌드의 개발에서 개념을 이해하는 것이 아니라 해당 개념을 적용하고만 싶어서 처리하는 케이스가 그런 상황을 발생시키는 것이 아닐까. 아주 자주 나오는 격언. "은탄환은 없다." 멘토님을 통해서 이번 기회에 또 한 번 상기할 수 있었다. 완벽한 동그라미의 바퀴를 만들고자 하지 말고, 굴러가게 만드는 것이 1번이다. 그 다음 세공하면 된다. 상황에 맞는 적절한 수준의 클린코드 적용을, 리팩토링을 위한 리팩토링을 계속해서 경계해야 한다고 또 한 번 다짐.
2025. 03. 09.
1
발자국 1주차: 읽기 좋은 코드와 현실 비즈니스 속 객체지향의 관계
해당 글은 인프런 워밍업 클럽 스터디 3기 - 백엔드 클린 코드, 테스트 코드를 진행하며 깊게 고민해 보려고 한 내용을 담습니다. 단순한 이해보다는 현실적 적용에 집중합니다. 우리는 자바를 기반한, 객체지향이라는 패러다임을 담은 개발을 하며 의 중요성을 귀가 닳도록 듣는다. 책임의 분리. 어디까지가 객체의 책임인가? 어떤 것은 추상이고 어떤 것이 구상인가? 책임이라는 개념은 객체지향에서 매우 중요하지만, 그와 동시에 현실에서 적용하는 데에서는 생각할 부분이 많다. 나는 비즈니스 일정과 아름다운 코드, 그리고 복잡성에 대한 사이에서 그동안 고민을 많이 해 왔었다. 강의를 들으면서 그것들을 고민하고자 했고, 그 고민에 대한 이번 주 일주일 동안의 이야기를 해 보려고 한다. Graceful Code와 Business 사이에서 사실 SOLID? 안다. 내 연차에서 그것을 모르는 자바 개발자는 드물 것이다. 하지만 그 개발자들은 나와 같이 이 SOLID를 어떻게 적용할지에 대해 고민할 것이다. 따라서 나는 이번 주에는 SOLID 원칙을 실무적으로 적용하는 과정에서 현실적인 한계를 분석하는 데 집중하려 했다. 특히, 기존 시스템에 SOLID 원칙을 도입하려 할 때 발생하는 문제점을 파악하고, 이를 해결하기 위한 현실적인 방법을 고민하고자 했다. 그래, 현재 코드베이스의 특성을 고려하여 현실적인 타협점을 찾는 방법을 고민해 보고 싶었다. 강의를 보며 생각해 본 이번 주의 고민들을 녹이며, 지금부터 SOLID 원칙을 한 가지씩 짚어가면서 이야기해 보고자 한다. 원칙과 현실의 부딪힘 SRP(단일 책임 원칙) 적용의 현실적 고민 사실 제일 좋아하는 원칙이다. SOLID의 가장 핵심이라고 생각하기도 한다. 그렇지만 책임이란 단어는 참 모호하다. [카페]에서 [커피]를 [주문]한다고 했을 때, [주문]이 담당하고 있는 책임은 어디까지일까? 이와 같이 나는 SRP를 고민할 때, [어디까지가 객체의 책임]인가에 대해서 고민한다. class CafeOrderService { private PaymentProcessor paymentProcessor; private NotificationService notificationService; private TransportService transportService; public void processOrder(Order order) { paymentProcessor.process(order); notificationService.sendOrderConfirmation(order); transportService.giveDrink(); } } 카페 주문은 [결제] / [완료 안내] / [손님에게 음료 주기]를 포함할 수 있다. 그런데 카페의 할 일이 많아지게 되면, 이 클래스는 너무 많은 책임을 가지게 된다. 만약 카페가 배달 서비스를 시작하면 카페의 책임은 [배달] / [배달 기사님 콜 부르기] / [배달 완료표시] 등으로 확장될 수 있다. 카페의 할 일은 많지만 책임의 갯수가 늘어날 때는 역시 분리를 고민해야 할 것 같다. 그럼 그걸 몇 개로 제한해야 할까? 3개? 4개? … 개수가 아니지 않을까? 내가 이번에 정의를 내리게 된 것은 SRP가 단순하게 ”하나의 클래스 = 하나의 책임“이 아니라. “변경의 이유가 하나인가”를 고민하는 원칙이라는 것이다. 따라서 우리는 카페 주문이라는 서비스가 “언제” 바뀔지에 따라 응집도의 기준을 정해야 한다. 가령 [카페 주문]이라는 것이 여러 방식의 주문을 가질 수 있게 된다면(방식의 개수가 바뀜), [매장 카페 주문] / [배달 카페 주문]으로 바꾸고, 외부 인터페이스는 타입에 따라 분리하도록 지정하는 것이다. // 주문 처리를 위한 상위 인터페이스 interface OrderService { void processOrder(Order order); } // 매장 주문 처리 class StoreOrderService implements OrderService { private PaymentProcessor paymentProcessor; private NotificationService notificationService; private ServeService serveService; @Override public void processOrder(Order order) { paymentProcessor.process(order); notificationService.sendInStoreOrderConfirmation(order); serveService.serveDrinkAtCounter(order); } } // 배달 주문 처리 class DeliveryOrderService implements OrderService { private PaymentProcessor paymentProcessor; private NotificationService notificationService; private DeliveryService deliveryService; private DriverNotificationService driverService; @Override public void processOrder(Order order) { paymentProcessor.process(order); notificationService.sendDeliveryOrderConfirmation(order); deliveryService.prepareForDelivery(order); driverService.notifyAvailableDriver(order); } } // 주문 타입에 따라 적절한 서비스를 선택하는 팩토리 class OrderServiceFactory { private StoreOrderService storeOrderService; private DeliveryOrderService deliveryOrderService; public OrderService getOrderService(OrderType orderType) { switch (orderType) { case STORE: return storeOrderService; case DELIVERY: return deliveryOrderService; default: throw new UnsupportedOperationException("지원하지 않는 타입:" + orderType); } } } // 클라이언트 코드 class CafeOrderController { private OrderServiceFactory orderServiceFactory; public void processOrder(Order order) { OrderService orderService = orderServiceFactory.getOrderService(order.getType()); orderService.processOrder(order); } } 이제 OrderService 입장에서는 책임이 분리됐다. 타입만 ENUM에서 선택해서 넘겨 주면 되겠다. 그런데 여기서 튀어나오는 원칙이 하나 더 있다. OCP다. OCP(개방-폐쇄 원칙) 적용의 현실적 고민 OCP. 인터페이스 정의의 핵심이다. 특히 전략 패턴에서 고민하게 되는 부분인 것 같다. OCP 이야기가 많이 나오는 예제로 Oauth 로그인이 있다. 네이버 / 카카오 인증을 인터페이스로, 행위 중심을 토대로 폐쇄하되 앞으로 여러 가지 인증의 가능성을 넓히는 것. 그런데 나는 확장의 필요성과 지속성을 먼저 고려해야 한다고 생각한다. “우리는 이 객체를 어디까지 확장할 것인가?” 확장성을 높이기 위해 인터페이스를 도입했지만, 너무 많은 추상화가 오히려 코드 가독성을 해치는 경우도 있을 수 있다. 이번에 크리스마스 특집으로 행사를 한다고 한다. 이 행사는 일주일간 일어나고 사라질 것이다. 그럼 우리는 여러 가지 이벤트가 발생할 때마다 늘 DiscountPolicy의 구현체를 추가해 주어야 하는 것일까? 위의 예시에서도, 이벤트성으로 음료 주문 방식에 증정 이벤트가 추가되었다고 해 보자. 그렇다면 증정 이벤트가 추가된 클래스를 만들어야 할까? 뭐, 그럴 수도 있다. 클래스는 쓰다 지우면 된다. 그런데 추가된 클래스를 나중에 삭제할 수 있다고 쉽게 생각하지만, 실제로는 코드베이스에 남아 오히려 누군가 옵션을 더하는 식으로 클래스가 커져, 유지보수 비용이 발생할 수 있다. 같은 소스코드 안에 있다면 3줄로 관리하면 되는데, 클래스를 하나 늘리는 것이 추후 휴먼 오류 가능성을 높이는 행동이 될 수 있다는 것이다. (세상에는 객체지향을 사랑하는 사람만 있지는 않다) 결국 객체의 [개방]을 위해 보장해야 하는 것은 로직이 얼마나 지속될지의 여부인 것 같다. 그렇다면 리스코프 치환 원칙은 어떠한가? LSP(리스코프 치환 원칙) 적용의 현실적 고민 리스코프 치환 원칙의 중심은 부모에게 있다. 부모가 하는 일을 자식이 위반하지 않아야 하는 것이다. 여기서 가장 적용하기 모호해지는 것은 [부모가 하는 일]이다. 부모는 어떠한 책임을 가질까? 그리고 어떠한 행위를 할까? 역할이 모호한 만큼, 부모의 역할 또한 모호하게 느껴진다. 우리의 카페 주문 예시로 생각해 보자. 사람은 카페에게 기대하는 역할이 있다. 나에게 내가 원하는 음료수를 주는 것이다. 그것이 배달이든, 실제로 가서 주문하는 것이든 달라지는 것은 없다. 여기서 가장 중요한 것은 나 / 음료수 / 전달 이다. 나는 리스코프 원칙을 [클라이언트가 기대하는 응답을 주는 것]을 부모가 하는 일을 위반하지 않는 것이라고 생각한다. 우리의 카페 주문에서의 리스코프 책임 원칙은, “부모가 가진 인터페이스의 계약을 지키는 것”은, [음료수를 전달하는 것]일 것이다. 방식은 다르더라도 음료수만 잘 배달하면 된다. 그러면 지킨 것이다. 그렇다면 스프링에서 가장 많이 쓰이는 DIP는 어떨까? DIP(의존성 역전 원칙) 적용의 현실적 고민 DIP는 고수준 모듈이 저수준 모듈에 의존하지 않고 둘 다 추상화에 의존하게 만든다. 스프링 프레임워크에서는 이 원칙을 기반으로 DI(의존성 주입)를 제공한다. 개발자로서 우리는 스프링에게 객체 생성을 위임하고 수많은 DI를 수행한다. new를 직접 호출할 필요가 없다니! 정말 편리하다. 그런데 모든 의존성을 인터페이스로 추상화하는 것이 항상 최선일까? 다음과 같은 상황을 고려해보자. interface UserService { User findById(Long id); void register(User user); } class CafeUserService implements UserService { // 카페 유저 관련 구현 } class StoreUserService implements UserService { // 상점 유저 관련 구현 } 현재 서비스에는 카페 사용자만 존재하고, 상점 사용자는 아직 구현 계획이 없다. 그럼에도 불구하고 “미래의 확장성”을 위해 인터페이스를 도입해야 할까? 인터페이스를 도입하면 분명 유연성을 얻을 수 있지만, 당장 StoreUserService 구현체가 필요하지 않다면 이는 불필요한 복잡성을 가져올 수 있다. 게다가 초기 스타트업에서 이 부분은 두드러진다. 도입 가능성을 예측했으나 갈아엎어지는 기획이 너무나 많으니까... 따라서 DIP 적용의 균형점은 근미래의 변경 가능성 / 팀의 개발 문화에 있다고 생각한다. 물론 대규모 엔터프라이즈 애플리케이션에서는 철저한 DIP 적용이 장기적으로 유리할 수 있다. 작은 프로젝트나 스타트업에서는 과도한 추상화가 오히려 개발 속도를 늦출 수 있다. “도입이 상상되는 인터페이스”는 애자일과 맞지 않는다. 결국 DIP의 적용은 실용적 균형의 문제가 아닐까 생각해 본다. 이제 마지막, ISP에 대해서 생각해 보았다. ISP(인터페이스 분리 원칙) 적용의 현실적 고민 ISP의 케이스에서 가장 경계해야 할 것은 결국 [개수]라는 생각을 한다. 얼마나 분리할 것인가? 얼마나 분리하는 것이 효율적인가? 나는 [현재 상태에서 필요한 만큼]이라고, 그러니까 최소한이라라고 생각한다. 많은 인터페이스가 생겼을 때 가장 큰 문제는 아무래도 파일이 최소 2배가 된다는 점이다. 수많은 파일은 복잡성을 높이는 것이 사실이니까. 어떠한 아키텍처가 이 수많은 인터페이스들을 깔끔하게 감당할 수 있는가? Spring과 같은 DI 프레임워크에서는 이런 문제가 더욱 두드러질 수 있다. 수많은 작은 인터페이스들의 구현체를 모두 Bean으로 등록하고 관리해야 하기 때문이다. 동시에 나는 섣부른 인터페이스 분리를 가장 경계해야 한다고 생각한다. 인터페이스가 재정의되어야 하는 순간, 기존에 해당 인터페이스들을 사용하고 있는 객체의 로직을 전부 다 다시 살펴야 한다. 따라서 ISP 또한... 먼저 상상하지 않는 것이 중요해 보인다. 이 인터페이스를 구현한 뒤 시일이 지난 이후, 다른 구현체에서도 상태가 변하지 않을 인터페이스만을 최대한 고민해 보려 한다. 잠시 응집도가 떨어지더라도. 회고 우리는 현실과 아름다운 코드의 사이에서 무엇을 포기해야 하는가? 객체의 책임은 비즈니스의 상황마다 가변적일 수 있다는 생각을 하기 때문이다. 인터페이스가 많으면 아름답다. 하지만 한눈에 파악하는 것은 어려워진다. 전략패턴은 객체의 책임을 확장성있게 분리한다. 하지만 어떠한 객체를 할당할지 정하는 구체적인 룰이 정해져야 한다. 결국 아름다움은 집단의 룰을 포함한다. 혼자서는 무한대로 아름다울 수 있지만, 같이 하는 현실에서도 아름다움을 추구하는 것이 Readable한 코드의 핵심이라고 생각한다. 비즈니스적으로 빠른 코드 ≠ 읽기 좋은 코드 ≠ 아름다운 코드 장기적인 관점에서 아름다운 코드는 결국 집단이 얼마나 동일한 룰을 체득하고 있는지에 따라 달라진다고 생각한다. 그리고 우빈님의 강의는 그 룰에 대한 가이드라인을 주고 있다는 생각이 들었다. 이 룰을 체득한다면, 적어도 비즈니스를 위한 코드를 생각할 때 객체지향의 관점을 망치지는 않을 것이다. 다음 주 계획 다음 주에는 내가 알고 있는 클린코드와, 강의에서 이야기하는 클린 코드를 적용하면서 TDD를 공부해 볼 예정이다.프로젝트 한 가지를 가지고 와서 이야기를 하면 좋을 것 같아서, Gilded Rose를 가지고 와 봤다.유명한 리팩토링 kata 라이브러리니 프로젝트를 [적용]하는 방식에 있어서 많은 것을 이야기해 볼 수 있을 것 같다.테스트 코드에 대해서도 강의를 베이스로 해서 Junit을 연습해 보고자 한다.