Programming 41

Clean Code - 외부 코드 사용하기 (+ 경계 클래스, 어댑터 패턴)

개요. 시스템에 들어가는 모든 소프트웨어를 직접 개발하는 경우는 드물다. 패키지를 사거나 라이브러리 등 오픈 소스를 사용하기도 한다. 이 때 우리는 외부 코드를 우리코드에 어떻게 해야 더 깔끔하고 안전하게 통합할 수 있는 지 고민해야 한다. 고민은 외부 코드와 우리 코드의 경계를 파악하는 것부터 시작한다. 경계(Boundaries) 전체 소프트웨어 개발에 사용하게 되는 외부 코드를 내 코드에서 호출하는 부분을 경계(boundaries)라고 한다. 경계 없이 단순하게 외부 코드를 사용하려는 곳에서 직접 호출할 수 있지만 그렇게 했을 때 아래와 같은 문제가 생긴다. 인터페이스 제공자가 지원한 인터페이스를 그대로 사용해야 한다. 인터페이스 제공자는 최대한 범용적이게 개발하는 것을 선호한다. 인터페이스 사용자는..

직접 만든 예시로 보는 SOLID 원칙!

객체 지향 설계 5원칙 SOLID에 대해 알아보자. SRP: Single Responsibility Principle 단일 책임 원칙 Bad 아래 클래스가 있다. class UserData { String id; String username; } 그리고 아래와 같이 UserData 클래스를 무분별하게 사용한다. class UserController { UserData getUser() {} void postUser(UserData userData) {} } class UserService { UserData getUser() {} } 여기서 패스워드 변경 기능이 추가되면 UserData에 password 필드를 추가해야 한다. class UserData { String id; String username..

Programming/OOP 2022.04.25

DTO를 Inner static class로 간결하게 관리하기! (+ domain 분리)

DTO 관리 API가 클라이언트(Client)가 보낸 Request Payload를 받을 때 RequestDto를 사용하고, 결과 값을 내려줄 때 ResponseDto를 사용합니다. 문제는 API 스펙을 고도화할수록 Dto가 너무 많아진다는 것입니다. End-point 요청에 따라 Request Body가 다르고 Response Body도 다릅니다. 그래서 API 스펙을 많이 뽑을 수록 Dto 패키지는 아래 처럼 지저분해집니다. (이미지 길이 보니까 더 길어 보이네요..!) Domain 분리 프로젝트가 어느정도 크기가 커지면 로직 안에서 동작하는 클래스들은 domain별로 관리하는 게 좋습니다. domain별로 class를 정리합니다. 아까보다는 좋긴 하지만, 더 간결해질 방법이 있습니다. Inner s..

Controller, Service, Repository를 Static으로 하지 않는 이유!!

Static class ? 어제 레거시 프로젝트를 받아서 기능 개선을 진행하게 되었습니다. 코드가 조금 신기했던게, Controller, Service, Repository가 정적 클래스(class)로 되어 있었습니다. Controller에서 Service의 메소드를 호출할 때 Service.method() 형식으로 호출하고 인스턴스를 생성하지 않는 방법입니다. Why? 정적 클래스(Static class)로 사용하면 어차피 재사용하게되고, 큰 문제는 없지 않나? 싶었습니다. 실제로 99%의 MVC 메서드는 정적으로 사용할 수 있다고 합니다. 그렇다면 왜 정적 클래스를 사용하지 않고, 싱글톤(Singleton)으로 instance를 만들어서 사용할까요? DI(의존성 주입) 정적 클래스는 상태를 가지지 않..

리팩토링 - Switch문을 다형성으로 바꾸기

조건문을 다형성으로 바꾸기 (Replace conditional with polymorphism) 모든 조건문을 다형성으로 바꿔야 좋은 것은 아닙니다. 여러 타입에 따라 각기 다른 로직으로 처리해야 할 경우에 조건문을 다형성으로 만들 수 있습니다. 공통으로 사용되는 로직은 상위클래스에 두고, 코드 변경의 여지가 있는 달라지는 부분만 하위 클래스에 둠으로써 가독성과 유지보수성을 얻는 것입니다 ! 예제 아래의 코드를 보면, printerMode라는 멤버 변수의 값에 따라 switch문에서 각기 다른 로직을 적용하게 됩니다. 각 조건마다 적지 않은 코드가 실행되고, 메서드 호출도 있고, 각각의 하위 메서드도 하나의 클래스에 전부 있는 상태입니다. public class Printer { private int ..

리팩토링 - 반복문 분리 (split loop)

반복문 분리 레거시 코드를 보면 하나의 반복문에서 여러 다른 작업을 수행하는 코드를 쉽게 찾을 수 있습니다. 하지만, 그렇게 되면 반복문 안의 하나의 작업을 수정하는 데도 다른 작업까지 고려해야 하는 상황이 발생합니다. 반복문을 작업별로 분리하면 쉽게 이해하고 유지보수할 수 있습니다. 예시 아래의 예제는 두 가지 작업을 하고 있습니다. 그럼에도 이렇게 짜는 이유는 우리는 반복문의 범위가 동일하다면 굳이 분리하지 않기 때문입니다. Date firstCreatedAt = null; Participant first = null; for (Comment comment: comments) { Participant participant = findParticipant(comment.getUserName(), par..

리팩토링 - 메소드 올리기 (Pull up method)

메소드 올리기 (Pull up method) 메소드 올리기는 하위 클래스들의 중복 메소드를 상위 클래스로 빼는 리팩토링 기술입니다. 아래와 같은 Dashboard 클래스가 있고, 이를 상속하는 두 클래스가 있다고 가정하겠습니다. [부모 클래스] public class Dashboard { public static void main(String[] args) throws IOException { ReviewerDashboard reviewerDashboard = new ReviewerDashboard(); reviewerDashboard.printReviewers(); ParticipantDashboard participantDashboard = new ParticipantDashboard(); parti..

리팩토링 - 함수 추출 (Extract function)

함수 추출 함수 추출은 함수를 정의할 때 각 기능들이 어느정도 코드가 길고 읽기가 어렵다면 그 기능을 함수로 추출하는 방법입니다. 예시는 아래와 같습니다. (읽지 않으시는 걸 추천드립니다..) 두 함수가 읽는 것에도 꽤 시간이 들어가고, 공동으로 사용하는 코드도 많은 것 같습니다. 해당 부분들을 각 함수로 추출해보겠습니다. public class StudyDashboard { private void printParticipants(int eventId) throws IOException { GitHub gitHub = GitHub.connect(); GHRepository repository = gitHub.getRepository("whiteship/live-study"); GHIssue issue =..

조건문 Refactoring 하는 방법! - 1편

조건문 Refactoring if ~ else문은 꼭 필요하면서도, 프로젝트를 복잡하게 만드는 요소입니다. 잘못 사용한다면, 열심히 개발한 프로젝트가 최악의 프로젝트가 될 수도 있죠. 조건문을 작성하면서 많은 개발자 분들이 실수하고 있는 부분들을 담을테니 참고해주세요! 1. return true; / return false;를 사용하지 마라! 조건 절이 Boolean 반환 값을 가지는데, 굳이 if ~ else문을 사용하면 코드가 복잡해집니다. [Bad😢] public boolean isAdmin(User user) { if(user.role == UserRole.ADMIN) { return true; } else { return false; } } [Good😍] public boolean isAdmin..

Clean Code - 예외 처리 시 놓치는 부분 정리 (Log, Null 검사, Checked exception)

반드시 Log를 남겨라 예외가 발생하면 대부분 언어에서는 호출 스택을 남깁니다. 하지만, 호출 스택만으로 왜 예외가 발생했는지를 알려면 많은 노력이 필요합니다. 따라서 반드시 catch문에서 충분한 로그를 남기는 것이 좋습니다. public void sendShutDown() { try { tryToShutDown(); } catch (DeviceShutDownError e) { logger.log(e); } } null을 반환하지 마라 아래의 코드를 보면 뭔가를 get 할때마다 null이 아닌지 검사를 하고 있습니다. 이렇게 코드를 구현한다면 프로젝트 하나에 거의 모든 곳에 null 검사가 들어가야 합니다. 누락되서 말썽을 일으키기도 쉽고, 코드도 더러워집니다. public String getCompa..