728x90
반응형
안녕하세요, 코딩 여정에 함께하는 동료 개발자 여러분! 😊 혹시 코드를 읽다가 거대한 함수를 마주하고 길을 잃은 듯한 느낌, 받아보신 적 있으신가요? 마치 한 권의 백과사전처럼 온갖 내용이 다 들어있는 함수 말이에요. 📜 오늘은 이런 '만능 함수'의 함정에서 벗어나, 코드를 훨씬 깔끔하고 관리하기 쉽게 만들어주는 마법 같은 원칙, 바로 **'단일 책임 원칙(Single Responsibility Principle, SRP)'**에 대해 이야기 나눠보려고 합니다. 특히 함수를 설계할 때 이 원칙을 어떻게 적용하는지 함께 알아보시죠! 🚀
반응형
❌ 이런 함수는 피해주세요! (SRP 위반 사례)
함수가 너무 많은 일을 하려고 하면 여러 문제가 발생할 수 있습니다. 마치 한 사람이 너무 많은 역할을 떠안으면 지치고 실수하는 것처럼 말이죠.
- 수백 줄짜리 '백과사전' 함수 함수 하나가 화면을 가득 채울 정도로 길다면 어떨까요? 📜 이름만 봐서는 정확히 어떤 작업을 하는지 짐작하기 어렵고, 전체 로직을 이해하려면 한참을 들여다봐야 합니다. 이런 함수는 그 자체로 "나중에 문제 생길 거예요!"라고 외치고 있는 것과 같습니다.
- 가독성 최악: 코드를 이해하는 데 너무 많은 시간이 소요됩니다.
- 디버깅 지옥: 어디서 버그가 발생했는지 찾기가 매우 어렵습니다. 😭
- 수정의 두려움: 작은 부분을 수정해도 얘기치 않은 다른 부분에서 문제가 터질 수 있습니다. (사이드 이펙트!)
- 여러 기능을 한 번에! '만능 맥가이버칼' 함수 (God Function) 사용자 정보 조회부터 주문 처리, 이메일 발송, 로그 기록까지... 함수 하나가 너무 많은 책임을 지고 있는 경우입니다. 🛠️🤔 어떤 점이 문제일까요?
- 높은 결합도: 하나의 기능 변경이 연관 없는 다른 기능에 영향을 줄 가능성이 큽니다.
- 재사용성 저하: 이 함수 전체를 다른 곳에서 재사용하기는 거의 불가능합니다.
- 테스트의 어려움: 너무 많은 것을 한 번에 테스트해야 해서 복잡하고 시간이 오래 걸립니다. 특정 부분만 테스트하기도 어렵고요.
-
// 나쁜 예시: 만능 함수 function handleOrder(orderId, userId) { // 1. 주문 정보 가져오기 const order = db.getOrder(orderId); if (!order) { console.error("주문 없음"); return; // 💣 여기서 끝? 에러 처리는? } // 2. 사용자 정보 가져오기 const user = db.getUser(userId); if (!user) { console.error("사용자 없음"); return; // 💣 } // 3. 재고 확인 및 업데이트 if (!inventory.checkAndUpdate(order.items)) { console.error("재고 부족"); // 롤백 로직 필요 (생략) return; // 💣 } // 4. 결제 처리 const paymentSuccess = payment.process(user, order.total); if (!paymentSuccess) { console.error("결제 실패"); // 롤백 로직 필요 (생략) return; // 💣 } // 5. 이메일 알림 발송 email.sendOrderConfirmation(user.email, order); // 6. 로그 기록 logger.log("주문 성공: " + orderId); console.log("주문 처리 완료!"); }
- 끝없이 들어가는 '인셉션' 함수 (깊은 들여쓰기) if-else 문이나 반복문이 여러 겹으로 중첩되어 코드의 논리 흐름을 파악하기 매우 어려운 함수입니다. 겹겹이 쌓인 코드 안으로 빠져드는 느낌이죠. 🌀🤔 어떤 점이 문제일까요?
- 가독성 저하: 로직을 한눈에 따라가기가 너무 힘듭니다.
- 실수 유발: 조건을 하나라도 잘못 이해하면 예상치 못한 결과가 발생할 수 있습니다.
- 유지보수 어려움: 새로운 조건을 추가하거나 기존 로직을 수정하기가 매우 까다롭습니다.
// 나쁜 예시: 깊은 들여쓰기 function processData(data, userType, config) { if (data) { if (data.isValid) { if (userType === "admin") { // ... 로직 1 ... if (config.isProduction) { // ... 로직 2 ... return "어드민 프로덕션 처리 완료"; } else { // ... 로직 3 ... return "어드민 개발 처리 완료"; } } else if (userType === "member") { // ... 로직 4 ... if (config.sendNotification) { // ... 로직 5 ... // ... 알림 발송 ... return "멤버 알림 발송 완료"; } else { return "멤버 처리 완료 (알림 없음)"; } } else { return "알 수 없는 사용자 타입"; // 😖 } } else { return "유효하지 않은 데이터"; // 😖 } } else { return "데이터 없음"; // 😖 } }
✅ 이렇게 함수를 설계해보세요! (SRP 준수 사례)
그렇다면 어떻게 함수를 잘 만들 수 있을까요? 해답은 '단일 책임 원칙'을 따르는 것입니다!
- 한 가지 일만 똑 부러지게! (함수 분리) 위에서 봤던 '만능 맥가이버칼' 함수 handleOrder를 SRP에 따라 여러 개의 작고 명확한 함수로 분리해봅시다.💡 어떤 점이 좋을까요?
- 명확성: 각 함수는 자신의 책임만 다하므로 코드를 이해하기 훨씬 쉽습니다.
- 유지보수 용이성: 수정이 필요할 때 해당 함수만 집중해서 보면 되므로 다른 부분에 미치는 영향을 최소화할 수 있습니다.
- 재사용성 향상: getUserDetails 같은 함수는 주문 처리 외 다른 곳에서도 유용하게 쓰일 수 있습니다.
// 좋은 예시: 기능별로 분리된 함수들 // 각 함수는 한 가지 책임만 가집니다. function getOrderDetails(orderId) { const order = db.getOrder(orderId); if (!order) throw new Error(`주문 정보(${orderId})를 찾을 수 없습니다.`); // ✅ 명확한 에러 return order; } function getUserDetails(userId) { const user = db.getUser(userId); if (!user) throw new Error(`사용자 정보(${userId})를 찾을 수 없습니다.`); // ✅ return user; } function checkAndUpdateStock(items) { if (!inventory.checkAndUpdate(items)) { // 필요시, 여기에서 관련 롤백 로직을 수행하거나 구체적인 예외를 던질 수 있습니다. throw new Error("재고가 부족하거나 업데이트에 실패했습니다."); // ✅ } // 성공 시 명시적으로 반환하거나, 반환 없이 정상 종료 } function processPaymentForOrder(user, totalAmount) { const paymentSuccess = payment.process(user, totalAmount); if (!paymentSuccess) throw new Error("결제에 실패했습니다."); // ✅ } function sendOrderConfirmationEmail(emailAddress, orderDetails) { // 이메일 발송 실패 시 어떻게 처리할지 정책에 따라 구현 (예: 로깅, 재시도 큐) email.sendOrderConfirmation(emailAddress, orderDetails); } function logOrderSuccess(orderId) { logger.log("주문 성공: " + orderId); } // 개선된 메인 처리 로직: 이제 각 단계를 명확히 호출합니다. function handleOrder_SRP(orderId, userId) { try { const order = getOrderDetails(orderId); // 주문 정보에 userId가 포함되어 있다고 가정하거나, 별도 파라미터로 받습니다. const user = getUserDetails(order.userId || userId); checkAndUpdateStock(order.items); processPaymentForOrder(user, order.total); sendOrderConfirmationEmail(user.email, order); logOrderSuccess(orderId); console.log("주문 처리 완료!"); return { success: true, orderId: orderId }; // ✅ 명확한 결과 반환 } catch (error) { console.error(`주문 처리 중 오류 발생 (주문 ID: ${orderId}): ${error.message}`); // 여기서 공통 에러 처리 또는 구체적인 롤백 로직 호출 // 예: initiateOrderRollback(orderId, error); return { success: false, error: error.message }; // ✅ 실패 시에도 명확한 결과 } }
- 명확한 입력(매개변수)과 출력(반환 값) 함수가 무엇을 받고(매개변수), 그 결과로 무엇을 반환하는지 명확해야 합니다. 마치 잘 설계된 자판기처럼요! 🪙🥤
- 예측 가능성: 함수의 사용법을 쉽게 알 수 있고, 어떻게 동작할지 예측하기 쉬워집니다.
- 부수 효과(Side Effect) 최소화: 함수가 외부 상태를 직접 변경하기보다, 입력값을 받아 처리 후 결과를 반환하는 순수 함수(Pure Function)에 가깝게 만들면 좋습니다.
- 테스트는 식은 죽 먹기! 🥣 (테스트 용이성) 함수가 작고 한 가지 일만 한다면, 마치 레고 블록 하나하나를 살펴보듯 테스트 코드를 작성하기 매우 쉬워집니다.
- 단위 테스트 용이: 각 기능을 독립적으로, 그리고 쉽게 테스트할 수 있어 버그를 조기에 발견하고 코드의 안정성을 크게 높일 수 있습니다.
- 신뢰성 향상: 테스트가 잘 된 코드는 변경에 대한 자신감을 줍니다.
🎯 핵심은 이겁니다, 여러분!
"함수는 작게, 한 놈만 팬다! 🥊"
함수가 하나의 책임만 갖도록 명확하게 정의하고, 그 책임에만 집중하도록 만들어야 합니다. 이것이 바로 단일 책임 원칙의 핵심입니다.
단일 책임 원칙을 함수 설계에 적용하는 것은 처음에는 조금 어색하고 일이 더 많아지는 것처럼 느껴질 수 있습니다. 🤔 하지만 장기적으로는 코드의 가독성, 유지보수성, 테스트 용이성을 크게 향상시켜 결국 우리를 행복하게 만들어 줄 거예요. 😊 오늘부터 여러분의 함수를 한번 SRP의 눈으로 살펴보시는 건 어떨까요? 작은 변화가 코드 전체의 품질을 높이는 마법을 경험하게 되실 겁니다! ✨
728x90
반응형
'프로그래밍 > 개발 팁' 카테고리의 다른 글
[좋코vs나코] 제5편: "미로 같은 조건문" - 복잡한 조건문 단순화하기 얽힘주의보! 🚧 (3) | 2025.05.19 |
---|---|
[좋코vs나코] 제4편: "복붙의 향연" - 중복 코드 제거 (DRY: Don't Repeat Yourself) 🔄 (6) | 2025.05.19 |
[좋코vs나코] 제2편: "주석 없이는 해독 불가!" vs "코드가 곧 문서" - 주석의 올바른 활용법 💬 (4) | 2025.05.16 |
[좋코vs나코] 제1편: "이름이 왜 이래?" - 명확한 이름 짓기의 중요성 🏷️ (6) | 2025.05.15 |
[좋코vs나코]좋은 코드, 왜 중요할까요? 🤔 코드 품질 UP! (3) | 2025.05.15 |