안녕하세요 ~ 헤나입니다 ! 😄
저번 글은 블랙잭 미션 1단계 & 2단계 회고 였습니다.
이번에는 "체스 미션 1단계 & 2단계" 회고를 진행해보려고 합니다.
🟧 메서드 순서 정리
메서드 순서를 정리하자.
💁♂️ 리뷰어
전체적으로 메서드 순서 정리를 해주셨으면 합니다. (다른 클래스도)
아래 글에서 3번 항목을 참고해주셨으면 해요.
🔗 클린코딩/더 나은 코딩을 하는 10가지 방법
기존에 나는 메서드를 정리할 때 다음과 같은 순서로 정리했다.
- public 메서드를 최상단에 작성한다.
- private 메서드는 public 상단에서 관련된 순서로 작성한다.
- equals, hashcode, getter, setter 메서드는 왼쪽 순서대로 최하단에 작성한다.
이전 페어들과 같이 코드를 작성하면서 위와 같은 방식으로 메서드 순서를 정했었다.
페어가 나를 설득하려 했지만 역으로 페어를 설득했었던 적도 있고
먼저 말 없이 진행한 이후에 시간이 부족해서 그대로 제출한 경우도 있다.
현재 이 글을 작성하는 이유는
결국 여러 페어의 말이 조금 더 클린한 코드를 작성하는데 좋은 방법이라고 판단됐기 때문이다.
그와 관련돼서 리뷰어님이 걸어준 링크에서 3번 항목은 다음과 같다.
💁♂️ 3. Variables/Methods Declaration 변수와 메소드의 선언
모든 클래스의 변수들은 클래스의 맨 위에 선언되어야 한다.
이러한 접근은 변수들을 찾을 위치를 언제든 알 수 있고,
전체 클래스를 스크롤해서 선언된 곳을 찾을 필요가 없다.
만약 변수가 한 개의 메서드에서만 사용된다면 지역 변수로 선언해서 쓰는 것이 낫다.
메서드들은 쓰이는 순서대로 선언되어야 한다. (Top To Bottom)
예를 들면, 만약 현재 메서드에서 다른 메서드를 호출한다면,
메서드는 현재 메서드의 아래에서 선언되어야 한다.
또한, 더 중요한 메서드는 클래스의 위쪽으로 가고, 덜 중요한 것들은 끝으로 간다.
코드로 나타내면 다음과 같다.
먼저 수정전 기존 메서드 순서를 보자.
// 수정 전 코드
// public을 상단에 둔다.
public int plusTwice(int one, int two, int three, int four) {
return plus(one, two) + plus(three, four);
}
public int minusTwice(int one, int two, int three, int four) {
return minus(one, two) + minus(three, four);
}
// public 상단에서부터 관련된 private 메서드를 나열한다.
private int plus(int left, int right) {
return left + right;
}
private int minus(int left, int right) {
return left - right;
}
이제 수정후 메서드는 다음과 같다.
- public 메서드 바로 아래 관련된 메서드를 나열한다.
// 수정 코드
// public을 상단에 둔다.
public int plusTwice(int one, int two, int three, int four) {
return plus(one, two) + plus(three, four);
}
// 관련된 메서드를 바로 아래에 나열한다.
private int plus(int left, int right) {
return left + right;
}
public int minusTwice(int one, int two, int three, int four) {
return minus(one, two) + minus(three, four);
}
private int minus(int left, int right) {
return left - right;
}
🤔 외부에서 사용할 public 메서드를 모두 상단에 나열하는게 보기 더 편하지 않을까 ?
어차피 private은 사용하지 않을터이니 public이 잘 보이게 두는게 낫지않나 ?
사실 이전에 이런 생각 때문에 public을 모두 상단에 두었다.
하지만 다음과 같은 상황에서 단점을 바로 파악할 수 있다.
// 수정 전 코드
// plus 내부 구현이 어떻게 이루어졌는지 확인하려고 한다.
// a, b, c, d, e, f 메서드는 어떤 식으로 구현되어 있을까?
// 찾아보도록 하자 👀
public int plus(int one, int two) {
return a(one, two) + b(one, two) + c(one, two) + d(one, two) + e(one, two) + f(one, two);
}
public int minus(int one, int two) {
// ...
}
public int multiply(int one, int two) {
// ...
}
public int divide(int one, int two) {
// ...
}
public double tan(double one) {
// ...
}
public double sin(double one) {
// ...
}
public double cos(double one) {
// ...
}
public double sqrt(double one) {
// ...
}
public double max(double one, double two) {
// ...
}
public float max(float one, float two) {
// ...
}
public long max(long one, long two) {
// ...
}
public int max(int one, int two) {
// ...
}
public double min(double one, double two) {
// ...
}
public float min(float one, float two) {
// ...
}
public long min(long one, long two) {
// ...
}
public int min(int one, int two) {
// ...
}
public double pow(double one, double two) {
// ...
}
public double toDegrees(double one) {
// ...
}
// 발견했다 !!
private int a(int left, int right) {
return left + right;
}
private int b(int left, int right) {
return left + right;
}
private int c(int left, int right) {
return left + right;
}
private int d(int left, int right) {
return left + right;
}
private int e(int left, int right) {
return left + right;
}
private int f(int left, int right) {
return left + right;
}
기존에 public 메서드를 모두 상단에 위치하는 것은
내부 구현을 봐야할 때 메서드를 찾기 매우 어려운 단점이 있다.
public 메서드가 모두 간단하다면 기존처럼 나열해도 괜찮다고 생각한다.
하지만 메서드가 조금만 복잡해진다면
다른 개발자가 내가 구현한 기능을 사용할 때 내부 동작을 볼 것이다.
이 때 위와 같이 a, b, c, d, e, f 메서드를 찾는데 오래걸린다면 불만을 토할게 분명하다.
그러니 아래와 같이 수정하게 된 것이다.
plus메서드에 아래에 바로 사용한 메서드(a, b, c, d, e, f)가 있다면
바로 구현 내용을 알 수 있어 해당 메서드를 사용하는데 수정전 보다 편리하다.
// 수정 후 코드
// plus 내부 구현이 어떻게 이루어졌는지 확인하려고 한다.
// a, b, c, d, e, f 메서드는 어떤 식으로 구현되어 있을까?
// 찾아보도록 하자 👀
public int plus(int one, int two) {
return a(one, two) + b(one, two) + c(one, two) + d(one, two) + e(one, two) + f(one, two);
}
// 발견했다 !!
private int a(int left, int right) {
return left + right;
}
private int b(int left, int right) {
return left + right;
}
private int c(int left, int right) {
return left + right;
}
private int d(int left, int right) {
return left + right;
}
private int e(int left, int right) {
return left + right;
}
private int f(int left, int right) {
return left + right;
}
🟧 현실과 객체지향
체스 미션을 진행하면서 보드(Board)에 빈 칸을 Empty라는 객체로 나타냈다.
현실에서 Board에 빈 칸은 단순히 빈 칸인데 코드에서는 기물(Piece)로 판단했다.
Piece이지만 아무런 행동도 할 수 없는 기물이다.
객체지향과 현실은 다르다는 것을 알고 있지만
아직 어색한 느낌이 많이 들었기에 리뷰어님에게 여쭤보았다.
😄 헤나
01. 기물을 Empty라고 할 수 있을까 ?
보드판 (8 * 8) 에는 모두 기물로 채워져있다고 생각했습니다.
저희가 보는 기물이 없는 공간은 Empty라는 기물이 있다 가정하고 시작했습니다.
이런 식으로 편리함을 위해서 Empty 같은 클래스를 만들어도 괜찮을지 궁금합니다. 🤔
💁♂️ 리뷰어
구현하기 나름이겠지만 저는 충분히 좋은 방법이라고 생각합니다.
소프트웨어, 더군다나 객체지향 프로그래밍은 현실세계와 완전히 일치하지 않는다고 생각해요.
오히려 이를 바탕으로 자유롭게 새로 창조하는거죠.
기물이 empty라는 것은 현실세계로 봤을 때는 뭔가 어색할 수도 있겠지만
헤나가 구현한 프로그램에서 봤을 때는 board에 비어있는 공간을 담당할 적절한 기물 객체라고 생각합니다.
말씀해주신 것처럼 empty에서 piece가 수행할 수 있는 행위를 막아둠으로써 문제가 될 가능성도 막아두었고요.
만약에 지금 구조에서 이걸 사용하지 않는다면 또 어떤 방법이 있을까요?
당장 떠오르는건 null인데 그러면 코드가 더 지저분하고 치명적인 문제가 발생할 것 같네요ㅎㅎ
따라서 이를 대신할 적절한 객체를 만드셨다고 생각합니다.
다행히 리뷰어님도 적절한 판단이었다고 얘기해주셨다.
객체지향 프로그래밍은 현실세계와 완전히 일치하지 않다는 얘기는 토끼책에서도 나온 내용이었다.
하지만 이러한 방식으로 구현해본적이 거의 없었기 때문에 어색한 느낌이 컸다.
그리고 기능을 호출하면 UnsupportedOperationException이 발생하도록 막아두었다.
다만 이런식으로 사용하지 않는 메서드라고 작성해놓았다고 해도
다른 개발자가 직접 Empty 클래스의 코드를 확인하던지, 문서를 확인하는 작업이 필요하다는 부분이 신경쓰인다.
이러한 구현은 개발자에게 사용하지않음을 알려주고 있지만
다시 한 번 확인하고, 숙지한 상태여야 한다는 점이 번거로울 수 있다.
또한 컴파일 시에는 이 Empty 클래스가 통과될 수 있겠지만
어디선가 Empty 객체의 createMovablePositions 메서드를 호출하게 된다면 런타임 에러가 발생할 수 있음에 주의해야 한다.
Empty 객체의 코드는 아래와 같다.
public final class Empty extends Piece {
private Empty(final PieceType pieceType, final Color color) {
super(pieceType, color);
}
public static Empty create() {
return new Empty(PieceType.EMPTY, Color.EMPTY);
}
@Override
protected List<Position> createMovablePositions(final Position source, final Position target) {
throw new UnsupportedOperationException("Empty 기물은 사용하지 않는 createMovablePositions 메서드 입니다.");
}
}
🟧 인터페이스와 추상클래스, 확장성과 복잡성
Piece 추상 클래스가 Movable 인터페이스를 implements 하고 있는 상황이다.
public abstract class Piece implements Movable {
}
추상 클래스에서 abstract method를 이용하지 않고 인터페이스를 이용한 이유는 다음과 같았다.
- Piece 추상 클래스를 구현하는 자식 클래스는 인터페이스의 메서드를 구현한다.
- Piece 추상 클래스가 없어진다고 한들 자식 클래스의 메서드는 살아있다.
- 자식 클래스는 Piece 추상 클래스와의 강한 의존을 약하게 풀어줄 수 있다.
하지만 리뷰어의 커멘트가 다음과 같이 들어왔다.
💁♂️ 리뷰어
이 부분을 인터페이스로 분리하신 이유가 있을까요~?
public interface Movable {
List<Position> findPositions(final Position source, final Position target);
}
😄 헤나
처음에 Piece를 추상클래스로 두지 않고 진행하면서 Movable 인터페이스를 구현하게 했습니다.
public class Piece implements Movable
이후에 Piece를 추상클래스로 변경하고 Movable 인터페이스는 그대로 남았습니다.
public abstract class Piece implements Movable
여기서 Movable 메서드를 Piece에 합치지 않은 이유는
Piece가 Movable 기능을 갖는 것에 대한 명시적인 표기가 가능해서 였습니다.
현재로서는 Movable을 인터페이스로 뺀다고해서 큰 이점을 갖는 부분은 없어 보이는데 리뷰어님은 어떻게 생각하시는지 궁금합니다 🤔
💁♂️ 리뷰어
개인적인 생각으로 말씀해주신 것처럼 큰 이점은 없어보여요.
Position을 찾는 것은 현재 구조에서 Piece가 가지고 있어야 할 기능이고 이를 인터페이스로 분리할 이유는 없을 것 같습니다.
확장성을 고려할 것도 아니라서 오히려 복잡함을 야기시킬 것 같아요.
Piece로 이동해도 무방하지 않나 싶습니다ㅎㅎ
대화 내용을 요약하면 다음과 같다.
추상 클래스와 인터페이스를 이용해서 기능을 분리하면 자식 클래스와 추상 클래스의 의존이 약해질 수 있다.
하지만 현재 분리함으로서 얻을 수 있는 이점이 없다.
단지 복잡해보이기만 할 뿐이다.
🤔 추상 클래스와 인터페이스를 같이 사용한 이유가 뭐였을까 ?
자식 클래스가 추상 클래스에게 의존하는 것을 풀어주기 위해서 인터페이스를 이용했다.
public interface Movable {
List<Position> findPositions(final Position source, final Position target);
}
public abstract class Piece implements Movable {
}
그렇다면 인터페이스를 어떻게 활용할 수 있을까 ?
- List<Movable> 형식으로 인터페이스의 다형성을 활용할 수 있다.
- 추상클래스를 이용해서 중복되는 메서드를 구현해 놓을 수 있다.
- default 메서드를 이용하지 않고 추상클래스에서 구현한 이유 ?
- 추상 클래스를 이용하면 protected, private 제어자를 지정할 수 있기 때문이다.
더 많은 장점들이 있겠지만 대표적으로는 위와 같은 상황이 있을거 같다.
다만 현재 상황에서 Piece 추상 클래스와 Movable 인터페이스를 이용해서 분리했을 때 얻을 수 있는 이점은 없다.
Movable 인터페이스가 추가되었지만 이를 이용하지는 않았기 때문이다.
오히려 Movable 인터페이스가
- 어떤 일을 하고 있는 것인지
- 어떻게 사용해야할지
개발자 입장에서 더 복잡하다는 느낌만 들었다.
그래서 다음과 같이 수정했다.
// Piece와 Movable을 합쳤다.
public abstract class Piece {
abstract List<Position> findPositions(final Position source, final Position target);
}
인터페이스, 추상 클래스를 이용하면 확장성을 늘릴 수 있다.
하지만 확장성이 늘어날 때 코드가 복잡해지는 경우가 많아진다.
때문에 현재 상황에서 어떤 이점이 있을지 고려하는 것은 기본 중의 기본이니 주의하자.
'👨🚀 우아한테크코스 5기' 카테고리의 다른 글
[20230424] 우아한테크코스 5기 LEVEL 2 - 웹 자동차 경주 1단계 & 2단계 회고 (3) | 2023.04.16 |
---|---|
[20230329] 우아한테크코스 5기 LEVEL 1 - 레벨 인터뷰 회고 (0) | 2023.04.01 |
[20230313] 우아한테크코스 5기 LEVEL 1 - 블랙잭 1단계 & 2단계 회고 (0) | 2023.04.01 |
[20230311] 우아한테크코스 5기 LEVEL 1 - 블랙잭 페어 회고 (2) | 2023.03.11 |
[20230225] 우아한 테크코스 5기 LEVEL 1 - 사다리 타기 1단계 & 2단계 회고 (10) | 2023.02.25 |