우아한테크코스 6기 최종 코딩테스트를 준비하면서 작성된 글입니다.
아래의 우아한테크코스의 프리코스 과제를 수행해 오면서 정리한 내용들로 이루어져 있습니다.
oncall - 최종 코딩 테스트!subway-pathpairmatching-precoursebridgebaseballmenuchristmaslottoracingcarvendingmachineonboarding객체는 항상 생성부터 소멸 시점까지 유효한 데이터를 가져야 한다.
키가 99999 인 사람은 존재하지 않기 때문에 비현실적이다.
이를 막기 위해서 생성 시부터 제약을 가해야 한다.
객체 지향에서의 객체는 생명체던 비생명체던 모두 자아를 가지고 스스로 동작할 수 있는 존재라고 생각하자.
추상화
캡슐화
상속, 다형성
처음부터 객체지향을 준수하여 프로그래밍하기는 어려운것이 사실이다.
따라서소트웍스 앤솔러지라는 책에서 제시하는 객체지향 프로그래밍을 잘 하기 위한 9가지 원칙을 먼저 정리하므로써 객체지향에 대한 기본 틀과 구성 방식에 대해 알아보자.
else와 같이 조건없이 모든 경우를 열어주는 코드는 큰 버그를 초래할 수 있기 때문에 지양하는 것이 좋다.Collection으로 선언한 변수도 포장한다.) -> 일급 컬렉션int age = 20; // 원시타입의 변수
Age age = new Age(20) // 원시 타입의 변수를 객체로 포장한 변수
User 클래스의 필드는 단 2개만 존재하지만,User클래스가 해야할 일은 굉장히 많다public class User {
private String name;
private int age;
public User(String nameValue, String ageValue) {
int age = Integer.parseInt(ageValue);
validateAge(age);
validateName(nameValue);
this.name = nameValue;
this.age = age;
}
private void validateName(String name) {
if (name.length() < 2) {
throw new RuntimeException("이름은 두 글자 이상이어야 합니다.");
}
}
private void validateAge(int age) {
if (age < 0) {
throw new RuntimeException("나이는 0살부터 시작합니다.");
}
}
}
User가 해야할 일을 덜어 줄 수있다public class User {
private Name name;
private Age age;
public User(String name, String age) {
this.name = new Name(name);
this.age = new Age(age);
}
}
public class Name {
private String name;
public Name(String name) {
if (name.length() < 2) {
throw new RuntimeException("이름은 두 글자 이상이어야 합니다.");
}
this.name = name;
}
}
public class Age() {
private int age;
public Age(String input) {
int age = Integer.parseInt(input);
if(age < 0) {
throw new RuntimeException("나이는 0살부터 시작합니다.");
}
}
}
이 말은 박싱된 기본타입을 쓰라는 말이랑은 다르다!
(EffectiveJava: 박싱된 기본 타입보다는 기본 타입을 사용하라)
위의 예시를 들어 설명하자면, Age부분에서 원시타입 객체를 쓴것을 볼 수 있다.
거의 모든 경우에 박싱된 기본타입(Integer)과 같은 경우 보다는 기본 타입(int)을 사용해야 하고, 무조건 박싱된 기본타입을 써야하는 경우는 아래와 같다.
1. 제네릭
2. 리플렉션
3. DTO (null을 허용하기 때문에)
정리하자면,
하나의 객체 내부에 필드가 존재하는데, 필드 하나가 아닌 필드가 2개이상인 경우에
해당 필드에 유효성검증과 같은 검증 로직이 들어가야 한다면,
해당 필드를 원시타입으로 유지하지 말고, 포장하자!
Map<String, String> map = new HashMap<>();
map.put("1", "A");
map.put("2", "B");
map.put("3", "C");
Wrapping하는 것) → 하나의 자료구조가 된다.Collection을 Wrapping하면서, 그 외 다른 멤버변수가 없는 상태를 일급컬렉션이라 한다.public class GameRanking {
private Map<String, String> ranks;
public GameRanking(Map<String, String> ranks) {
this.ranks = ranks;
}
}
public class Car {
private String brand;
private String model; // 인스턴스 변수 2개
public Car(String brand, String model) {
this.brand = brand;
this.model = model;
}
public String getBrand() {
return brand;
}
public String getModel() {
return model;
}
}
public class Engine {};
public class Light {};
public class Wiper {};
public class Brake {};
// ..
CleanCode에서는 인스턴스 변수 뿐 아니라 메서드의 가장 이상적인 파라미터 개수는 0개라고 한다.getter로 객체 내부의 상태를 꺼내와 외부에서 상태를 바꾸는 것은, 객체의 상태값을 바꾼다는 판단을 외부에 위임한 것이다!
객체의 상태값을 바꾼다는 판단을 외부에 맡기지 말자!
이는 곧 독립적인 객체 설계에 위배되는 행위이다.
즉, 객체의 상태가 변경되는 것은 객체 스스로의 행동에 의해야 한다.
자율적인 객체가 되고 외부의 영향을 받지 않음으로써 느슨한 결합과 유연한 협력을 이룰 수 있는 것이다.
getter와 setter는 자신의 상태 정보를 외부에 노출하는 격이 되고 이것은 외부의 영향으로 상태 정보가 변할 수 있는 가능성을 열어두게된다.
따라서, getter/setter의 사용은 지양하자
데이터의 이동은 DTO를 이용하자!
View에서 단지 출력용도로 사용하기 위해서는 getter를 쓸 수도 있다.
public class DefensiveCopyingExample {
private List<String> internalList;
public DefensiveCopyingExample(List<String> originalList) {
// 방어적 복사를 통해 원본 리스트를 보호
this.internalList = new ArrayList<>(originalList);
}
public List<String> getInternalList() {
// 방어적 복사를 통해 내부 리스트를 외부로 노출하지 않음
return new ArrayList<>(internalList);
}
public static void main(String[] args) {
List<String> originalList = new ArrayList<>();
originalList.add("Item 1");
originalList.add("Item 2");
DefensiveCopyingExample example = new DefensiveCopyingExample(originalList);
// 외부에서 원본 리스트에 접근
List<String> externalList = example.getInternalList();
// 외부에서 리스트에 아이템 추가
externalList.add("Item 3");
// 원본 리스트에 변화 없음을 확인
System.out.println("Original List: " + originalList); // [Item 1, Item 2]
System.out.println("External List: " + externalList); // [Item 1, Item 2, Item 3]
}
}
단순히 라인에 존재하는 점의 개수를 수치적으로 줄이라는 의미보다는
필드나 메서드를 통해 인스턴스에 접근하는 방식 자체를 재고해보라는 뜻이다.
점의 개수가 많다는 것은 대상 객체의 내부에 깊이 접근하겠다는 의도를 나타낸다.
이는 일반적으로 호출자와 피호출자 사이의 강한 결합도를 바탕으로 메서드의 응집력을 떨어뜨리고 있을 확률이 높기 때문이다.
🧨 디미터의 법칙("친구하고만 대화하라")
자기 소유의 장난감, 자기가 만든 장난감, 그리고 누군가 자기에게 준 장난감하고만 놀 수 있다.
절대 장난감의 장난감과 놀면 안된다.
즉, 객체 간의 관계에서 이웃하지 않는 낯선 객체와 메세지를 보내는 설계는 피하라는 것이다.
물론 가독성 측면에서도 문제가 있다.
String result = someObject.getA().getB().getC().calculate();
A a = someObject.getA();
B b = a.getB();
C c = b.getC();
String result = c.calculate();
점의 개수가 많다면
1. 대상 객체의 내부에 깊이 접근한것은 아닌지,
2. 디미터의 법칙을 위배한 것은 아닌지 경계하고,
3. 가독성 측면에서도 문제가 있으니 수정을 요한다!
(3개를 초과하는 인자는 허용하지 않으며, 3개도 가능하면 줄이기 위해 노력해 본다)
하나의 메서드가 여러 책임을 담당한다면 코드의 길이가 늘어나게 되며,
다른 개발자가 해당 메서드의 역할을 이해하기 어렵게 된다.
(메서드 이름도 애매해진다)
(메서드당 line을 10까지만 허용하며 길이가 길어지면 메서드로 분리시킨다.)
상수 (static final)로 선언하지 않은 숫자를 매직 넘버, 문자열을 매직 리터럴이라 한다.ONE으로 이름을 짓는 것과 같은 의미없은 상수 변환은 피하도록 하자.
진짜 숭실대 + 우테코 + 솝트 최고 존잘남 윤주호 폼 미쵸따