일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- exception
- RestControllerAdvice
- SQL
- Filter
- mybatis
- 스프링 시큐리티
- 자바
- 관점지향프로그래밍
- 스프링
- 스프링부트
- Spring
- 트랜잭션
- OOP
- http
- git
- Redis
- 인터셉터
- java
- Interceptor
- Spring Security
- network
- aop
- 디자인패턴
- aspect
- request
- MYSQL
- spring boot
- proxy pattern
- response
- 객체지향프로그래밍
- Today
- Total
장쫄깃 기술블로그
(Java) 객체지향 생활 체조 9가지 원칙 (feat. 소트웍스 앤솔로지) 본문
객체지향 생활 체조의 9가지 원칙
1. 한 메서드에 오직 한 단계의 들여쓰기만 한다.
한 메소드에 여러 들여쓰기가 존재하면, 해당 메소드는 여러 가지 일을 처리한다고 봐도 무방하다. 그리고 이는 곧 코드를 분리해야 할 때가 되었음을 의미한다.
해당 원칙을 지키기 위해 코드를 각각의 책임과 역할에 따라 분리하면 자연스럽게 가독성과 유지보수에 용이한 코드를 작성할 수 있다.
AS-IS
private void test() {
testRepository.save(
Test.builder()
.name("test")
.age(20)
);
}
TO-BE
private void test() {
testRepository.save(createTest());
}
private Test createTest() {
return Test.builder()
.name("test")
.age(20);
}
2. else 키워드를 쓰지 않는다.
else 키워드는 의도를 명확하게 파악하기 쉽지 않다. else 대신에 early return을 사용하여 의도를 분명히 나타낼 수 있다.
분기를 사용할 때 해당 분기에 맞는 코드를 여기저기 분산시키는 것이 아니라, 필요한 일을 응집하여 해결하고 return 한다면 분기를 파악하기 쉬워진다.
AS-IS
private void test(String id) {
if(!id.isEmpty()) {
go();
}
else {
throw new IllegalArgumentException();
}
}
TO-BE
private void test(String id) {
if(id.isEmpty()) {
throw new IllegalArgumentException();
}
go();
}
3. 모든 원시값과 문자열을 포장(wrap)한다.
원시값과 문자열을 포장하지 않을 경우 Human Error의 가능성이 올라가고, 타입의 설계 의도와 벗어날 수 있게 된다.
설계 의도나 특정 행동을 명확히 표현해야 할 때, 이를 VO 클래스 등으로 캡슐화하여 사용할 수 있다. 이를 통해 설계 의도를 숨기면서도, 동시에 그 의도를 명확하게 구현할 수 있다.
AS-IS
private void test(String name, int age) {
// ...
}
// 메소드 요청 시 정상적인 동작을 기대할 수 없음
test("", -150);
TO-BE
private void test(InfoVO info) {
// ...
}
// 메소드 요청 시 개발자가 의도한 예외 발생
InfoVO infoVO = new InfoVO("", -150);
test(infoVO);
// Info VO class
class InfoVO {
private String name;
private int age;
public InfoVO(String name, int age) {
checkNameVal(name);
checkAgeVal(age);
this.name = name;
this.age = age;
}
// check name validation
private void checkNameValidation(name) {
if(name.isEmpty()) {
throw new IllegalArgumentException();
}
}
// check age validation
private void checkAgeValidation(age) {
if(age <= 0 || age >= 150) {
throw new IllegalArgumentException();
}
}
}
4. 한 줄에 점을 하나만 찍는다.
. 을 통해 클래스나 메소드의 내부 객체에 접근하는 경우가 있다. 이때, . 을 단 한 번만 사용해 연결하는 것이 좋다.
이 원칙은 디미터의 법칙 혹은 최소 지식 원칙과 연관이 있다. a -> b -> c의 형태의 경우 a가 b 뿐만 아니라 c까지 알고 있어야 한다. 이는 결합도가 높아지고 좋지 않은 설계를 야기한다.
디미터의 법칙은 다른 객체가 어떠한 자료를 갖고 있는지 속사정을 몰라야 한다는 것을 의미한다. 이는 곧 낮은 결합도와 좋은 설계로 이어질 수 있다.
AS-IS
private boolean checkName(User user) {
return "장쫄깃".equals(user.getInfo().getName());
}
class User {
Info info;
// ...
}
class Info {
String name;
// ...
}
TO-BE
private boolean checkName(User user) {
return user.isEqualNameJangjjolkit();
}
class User {
Info info;
// ...
public boolean isEqualNameJangjjolkit() {
return "장쫄깃".equals(info.getName());
}
}
class Info {
String name;
// ...
}
5. 줄여 쓰지 않는다.
변수명이나 메소드명을 지나치게 축약하면 가독성이 떨어질 수 있다. 간결함보다는 명확함을 우선시하는 것이 좋으며, 가능한 한 원어를 사용하는 것이 바람직하다.
메소드 이름이 지나치게 길다면, 이는 메소드가 너무 많은 책임을 가지고 있을 가능성이 크다. 이럴 때는 메소드의 책임을 분리하면, 자연스럽게 메소드명을 더 깔끔하고 짧게 줄일 수 있다.
AS-IS
private void userValAndSave(User user) {
// ...
}
TO-BE
private void userSave(User user) {
userValidation(user);
// ...
}
private void userValidation(User user) {
// ...
}
6. 모든 엔티티를 작게 유지한다.
하나의 클래스는 50줄이 넘지 않도록 하고, 하나의 패키지에는 10개 이상의 파일이 들어가지 않도록 유지하는 것이 좋다.
패키지 (하나의 목적을 달성하기 위한 연관된 클래스들의 모임)를 작게 유지하면 패키지가 진정한 정체성을 갖게 된다고 한다.
7. 3개 이상의 인스턴스 변수를 가진 클래스를 쓰지 않는다.
인스턴스 변수를 많이 가지는 클래스는 응집도가 낮아진다. 또한 인스턴스 변수가 많아진다는 것은 해당 Method가 많은 일을 할 확률이 높아진다는 말이기도 하다.
인스턴스 변수가 3개 이상이라면, 객체가 여러 가지 역할을 수행할 가능성이 크므로, 해당 객체의 책임을 분리하여 쪼갤 수 있는 기회를 고려해야 한다. (쉽지 않지만...)
저서 Clean Code에서 이상적인 변수 개수는 0개로 소개한다.
AS-IS
class User {
String name;
int age;
String zip;
String address;
public User(String name, int age, String zip, String address) {
this.name = name;
this.age = age;
this.zip = zip;
this.address = address;
}
}
TO-BE
class User {
Info info;
Address address;
public User(Info info, Address address) {
this.info = info;
this.address = address;
}
}
class Info {
String name;
int age;
public Info(String name, int age) {
this.name = name;
this.age = age;
}
}
class Address {
String zip;
String address;
public Address(String zip, String address) {
this.zip = zip;
this.address = address;
}
}
8. 일급 컬렉션을 사용한다.
일급 컬렉션은 하나의 컬렉션을 감싸는 클래스로, 해당 컬렉션과 관련된 모든 비즈니스 로직을 그 클래스 안에 캡슐화하는 개념이다.
일급 컬렉션을 사용하면 응집도를 높이고, 객체의 책임을 명확히 하며, 유지보수성과 가독성을 향상시킬 수 있다.
예를 들어, User 객체 안에 info라고 하는 Map이 존재하는 경우, 해당 Map을 private으로 선언하여 직접 접근을 막고, 임의의 매핑된 메소드로 접근 가능하게 해야 한다.
class User {
private Map<String, String> info = new HashMap<>();
public put(String key, String value) {
info.put(key, value);
}
}
User.put("name", "장쫄깃");
9. getter/setter/property를 쓰지 않는다.
getter/setter/property를 쓰지 않으려는 이유는 객체지향의 핵심인 캡슐화, 정보 은닉, 그리고 객체의 자율성을 지키기 위함이다. 이러한 원칙은 객체가 자신의 상태를 스스로 관리하고, 외부에서 직접적으로 그 상태에 접근하거나 변경하지 못하게 하는 것을 목표로 한다.
AS-IS
// Getter와 Setter를 사용하는 경우
public class User {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
TO-BE
// Getter/Setter 대신 행동을 사용하는 경우
public class User {
private String name;
public User(String name) {
this.name = name;
}
public void changeName(String newName) {
// 이름 변경 시 필요한 검증 및 로직을 여기에 작성
this.name = newName;
}
}
'Programming Language > Java' 카테고리의 다른 글
[Java] static method만 있는 유틸리티 클래스에 private 생성자를 사용해야 하는 이유 (0) | 2024.08.02 |
---|---|
[Java] Record란? (0) | 2024.07.26 |
[Java] Java Thread 종류 (0) | 2023.06.20 |
[Java] Atomic 변수 (0) | 2022.06.23 |
[Java] String & StringBuffer & StringBuilder 이해하기 (0) | 2022.06.23 |