🎯 목표 설정

  • 우아한테크코스에서의 목표: 개발자로서 추구할 가치를 설정하기 위해 끊임없이 도전하며 나 자신을 깊이 알아가는 것이다. 이를 위해 다양한 문제에 부딪히고, 프로젝트에 애정을 가지며 단순한 구현을 넘어 집요하게 리팩토링하는 경험이 필요하다고 느꼈다.
  • 프리코스에서의 목표: 따라서 이번 프리코스에서의 목표는 효율적인 리팩토링을 고민하며 이를 코드에 녹여내는 것이다. 이 과정에서 새로운 기술을 적극적으로 도입해 보고, 배운 점을 체계적으로 정리하려고 한다. 또한, 이러한 배움에 우선으로 집중하기 위해 처음부터 클린 코드 원칙을 준수하여 유지보수가 용이한 코드를 작성하고자 한다.

 

 

✏️ 주차 별 회고 기록 방법

  1. 목표 설정: 요구사항을 분석하고 명확한 학습 목표를 세운다.
  2. 진행 과정: 고민의 흔적이 드러나는 시도와 과정을 기록한다.
  3. 기술 고도화 및 리팩토링: 새로운 기술을 도입하고, 리팩토링을 통해 코드를 개선한다.
  4. 클린 코드 원칙 준수 점검 및 코드 개선: 작성한 코드가 클린 코드 원칙을 잘 따르고 있는지 점검하고 개선한다.
  5. 배운 점 정리: 배운 내용을 정리하고 복습한다.
  6. 고찰: 느낀 점과 개선할 부분을 기록한다.
  7. 참고 자료: 참고한 자료의 출처를 남긴다.

위 과정을 매주 블로그에 정리할 계획이다.

 

 

👔  자바 스타일 가이드

Google Java Style Guide [코딩규칙] 자바 코딩 규칙(Java Code Conventions)을 참고하여 정리한 내용이다.
  • 파일 이름: 클래스의 이름과 동일하게 작성하며, 확장자는 .java로 끝난다.
  • 들여쓰기: 2칸 들여쓰기를 기본으로 한다.
  • 한 줄 길이: 80자 이하로 제한하며, 가독성을 위해 줄 바꿈을 사용한다.
  • 중괄호 사용: if, for, while 등의 조건문에서 중괄호를 생략하지 않는다.
  • 변수 이름: lowerCamelCase로 작성하고, 명확한 의미를 갖도록 한다.
  • 상수 이름: UPPER_SNAKE_CASE로 작성한다.
  • 구현 주석: 코드의 로직을 설명하며, 코드를 이해하는 데 필요한 정보만 포함한다. (형태: //)
  • 문서화 주석: Javadoc을 활용하여 클래스나 메서드의 기능을 설명한다. (형태: /** ... */)
  • 메서드 이름: lowerCamelCase로 작성하며, 동작을 나타내는 동사 또는 동사구로 사용한다.
  • 클래스 이름: UpperCamelCase로 작성한다.
  • 메서드 인자 제한: 한 메서드에서 인자는 4개 이상을받지 않도록 하고, 가급적 3개 이하로 줄인다.
  • else 예약어 사용 금지: else는 피하고, 명확한 조건을 사용한다.
  • 디미터 법칙: 한 줄에 점(.)은 하나만 사용하며, 내부 객체에 직접 접근하지 않는다.
  • Getter/Setter 사용 자제: 도메인 객체에서는 Getter/Setter를 지양하고, 객체의 상태는 행동을 통해 변경한다.
  • 메서드의 책임 분리: 메서드는 하나의 역할만 담당하도록 설계한다.

 

➕ 우아한테크코스 스타일 가이드

우아한테크코스의 자바 스타일 가이드는 Google Java Style Guide를 기반으로 하며, 몇 가지 차이점을 반영하여 정리한 내용이다. Google Java Style Guide와 다른 부분은 아래와 같다.
  • 들여쓰기: 4칸 들여쓰기를 기본으로 한다.
  • 한 줄 길이: 최대 120자로 제한된다.
  • 들여쓰기 지속: 줄이 너무 길어져 줄 바꿈이 필요할 경우, 그 다음 줄은 원래 줄에서 최소 +8칸 이상 들여쓰기를 해야 한다.
  • 수직 빈 줄: 빈 줄은 가독성을 높이기 위해 적절한 위치에 사용할 수 있다. 클래스의 첫 번째 멤버나 초기화 앞에 있는 빈 줄을 강제하지 않는다.

 

 

📚 클린 코드 원칙 정리

우아한테크코스 문서 저장소에 있는 우아한테크코스 클린코드 원칙을 참고하여 정리한 내용이다.

▶ 한 메서드에 오직 한 단계의 들여쓰기(indent)만 허용했는가?

  • 이유: 들여쓰기가 많아지면 코드의 복잡해지고 가독성이 떨어진다.
  • 방법: 메서드 내에서 중첩된 로직을 피하고, 한 단계의 들여쓰기로 제한한다.

🔽 좋은 예시: 한 단계의 들여쓰기

public void printName(User user) {
    if (user == null || !user.isActive()) {
        return;
    }
    System.out.println(user.getName());
}

🔽 나쁜 예시: 여러 단계의 들여쓰기

public void printName(User user) {
    if (user != null) {
        if (user.isActive()) {
            System.out.println(user.getName());
        }
    }
}

 

▶ else 예약어를 쓰지 않았는가? 

  • 이유: else는 코드의 흐름을 복잡하게 만든다.
  • 방법: early return을 사용하여 else 구문을 제거한다.

🔽 좋은 예시: early return 사용

public void login(User user) {
    if (!user.isActive()) {
        System.out.println("유저가 활성화되지 않았습니다.");
        return;
    }
    authenticate(user);
}

🔽 나쁜 예시: else 사용

public void login(User user) {
    if (user.isActive()) {
        authenticate(user);
    } else {
        System.out.println("유저가 활성화되지 않았습니다.");
    }
}

 

▶ 모든 원시값과 문자열을 포장했는가?

  • 이유: 원시값이나 문자열을 직접 사용하는 것은 편리해 보이지만, 의미가 불분명해지고 코드가 복잡해진다.
  • 방법: 값을 객체로 포장하는 클래스를 사용하면 해당 값의 의미를 명확히 하고, 관련 로직을 클래스 내부에 캡슐화할 수 있다.

🔽 좋은 예시: 원시값을 객체로 포장

public class Age {
    private final int age;

    public Age(int age) {
        if (age < 0) {
            throw new IllegalArgumentException("나이는 음수일 수 없습니다.");
        }
        this.age = age;
    }
}

🔽 나쁜 예시: 단순한 int 값 사용

public class Person {
    private int age;
}

 

▶ 컬렉션에 대해 일급 컬렉션을 적용했는가?

  • 이유: 일급 컬렉션이란, 컬렉션을 감싸는 클래스를 만들어 해당 컬렉션을 처리하는 로직을 한 곳에 모으는 방법이다. 이를 통해 데이터 무결성을 유지하고, 관련 비즈니스 로직을 캡슐화할 수 있다. 또한, 불변성을 보장하고, 코드의 응집도가 높인다.

🔽 좋은 예시: 리스트를 일급 컬렉션으로 포장

public class Members {
    private final List<Member> members;

    public void add(Member member) {
        members.add(member);
    }

    public List<Member> getMembers() {
        return Collections.unmodifiableList(members);
    }
}
public class Team {
    private Members members;

    public void addMember(Member member) {
        members.add(member);
    }
}

🔽 나쁜 예시: 리스트를 직접 사용

public class Team {
    private List<Member> members;

    public void addMember(Member member) {
        members.add(member);
    }
}

 

▶ 3개 이상의 인스턴스 변수를 가진 클래스를 구현하지 않았는가?

  • 이유: 클래스가 3개 이상의 인스턴스 변수를 가지면, 단일 책임 원칙을 위반할 가능성이 높다. 이는 응집도를 낮추고 유지보수를 어렵게 만든다.
  • 방법: 관련 변수를 하나의 클래스로 묶어 새로운 객체로 분리한다.

🔽 좋은 예시: 객체 분리

public class Person {
    private String name;
    private int age;
    private ContactInfo contactInfo; // ContactInfo로 묶음
}

public class ContactInfo {
    private String address;
    private String phoneNumber;
}

🔽 나쁜 예시: 너무 많은 인스턴스 변수

public class Person {
    private String name;
    private int age;
    private String address;
    private String phoneNumber;
}

 

▶ getter/setter 없이 구현했는가? 

  • 이유: getter/setter는 객체의 상태를 외부에서 쉽게 변경하거나 접근할 수 있게 하므로 캡슐화를 위반할 수 있고, 데이터 무결성이 깨질 수 있다.
  • 방법: 객체는 스스로 상태를 관리하는 방식으로 설계한다.
단, DTO는 예외적으로 getter/setter 사용이 허용된다.

🔽 좋은 예시: 메서드를 통해 상태 관리

public class Account {
    private int balance;

    public void deposit(int amount) {
        if (amount <= 0) {
            throw new IllegalArgumentException("유효하지 않은 금액입니다.");
        }
        this.balance += amount;
    }

    public void withdraw(int amount) {
        if (amount > balance) {
            throw new IllegalArgumentException("잔고가 부족합니다.");
        }
        this.balance -= amount;
    }

    public int getCurrentBalance() {
        return balance;
    }
}

🔽 나쁜 예시: getter/setter 사용

public class Account {
    private int balance;

    // getter
    public int getBalance() {
        return balance;
    }

    // setter
    public void setBalance(int balance) {
        this.balance = balance;
    }
}

 

▶ 메서드의 인자 수를 제한했는가?

  • 이유: 메서드에 많은 인자를 전달하면 코드가 복잡해지고 가독성이 떨어진다. 또한, 인자의 순서나 역할을 헷갈릴 수 있어 유지보수와 테스트가 어렵다.
  • 방법: 4개 이상의 인자를 넘기지 않도록 하고, 필요하면 DTO나 객체로 묶어서 사용한다.

🔽 좋은 예시: 객체로 묶기

public class UserInfo {
    private String name;
    private int age;
    private String address;
    private String phoneNumber;
}

public void createUser(UserInfo userInfo) {
    // 유저 생성 로직
}

🔽 나쁜 예시: 4개 이상의 인자 전달

public void createUser(String name, int age, String address, String phoneNumber) {
    // 유저 생성 로직
}

 

▶ 코드 한 줄에 점(.)을 하나만 허용했는가? 

  • 이유: 코드 한 줄에 여러 개의 점(.)을 사용하면 메서드 체이닝이 발생해 코드가 복잡해지고 가독성이 떨어진다. 객체 간 결합도가 높아져 유지보수가 어려워진다.
  • 방법: 디미터 법칙을 지키기 위해 중간 객체에 관련된 로직을 처리하도록 메서드를 추가해 체이닝을 분리한다.
디미터 법칙(Law of Demeter): "친구하고만 대화해라"는 원칙으로, 객체는 직접 관계가 있는 객체와만 상호작용해야 하며, 그 객체의 내부 객체에 직접 접근해서는 안 된다는 원칙이다.

🔽 좋은 예시: 중간 객체 분리

public void printUserAddress(User user) {
    ContactInfo contactInfo = user.getContactInfo();
    Address address = contactInfo.getAddress();
    String street = address.getStreet();
    
    System.out.println(street);
}

🔽 나쁜 예시: 점(.) 여러 개 사용

public void printUserAddress(User user) {
    System.out.println(user.getContactInfo().getAddress().getStreet());
}

 

▶ 메서드가 한 가지 일만 담당하도록 구현했는가?

  • 이유: 메서드가 여러 가지 일을 동시에 처리하면, 응집도가 낮아지고, 가독성이 떨어진다. 이는 코드의 유지보수성과 테스트 용이성을 떨어뜨린다.
  • 방법: 단일 책임 원칙을 지키기 위해, 메서드는 하나의 역할만 담당하도록 하고, 여러 작업을 처리하려면 작은 메서드로 분리한다.

🔽 좋은 예시: 메서드 분리

public void saveUserData(User user) {
    validateUser(user);
    saveToDatabase(user);
}

public void sendWelcomeEmail(User user) {
    // 이메일 전송 로직
}

🔽 나쁜 예시: 여러 가지 일을 하는 메서드

public void saveUserData(User user) {
    validateUser(user);
    saveToDatabase(user);
    sendWelcomeEmail(user);
}

 

▶ 클래스를 작게 유지하기 위해 노력했는가?

  • 이유: 클래스가 크면 여러 책임을 동시에 지게 되어 응집도가 낮아지고, 유지보수가 어려워진다..
  • 방법: 단일 책임 원칙을 지키기 위해, 클래스가 너무 많은 책임을 가지지 않도록 작고 응집력 있는 클래스로 분리한다.

🔽 좋은 예시: 클래스를 역할에 따라 분리

public class OrderService {
    public void createOrder(Order order) {
        // 주문 생성 로직
    }
}

public class DiscountService {
    public void calculateDiscount(Order order) {
        // 할인 계산 로직
    }
}

public class InvoiceService {
    public void printInvoice(Order order) {
        // 송장 출력 로직
    }
}

🔽 나쁜 예시: 여러 책임을 지는 큰 클래스

public class OrderService {
    public void createOrder(Order order) {
        // 주문 생성 로직
    }
    
    public void calculateDiscount(Order order) {
        // 할인 계산 로직
    }

    public void printInvoice(Order order) {
        // 송장 출력 로직
    }
}

 

 

📍 참고 자료

soeun2537