프리코스 3주 과정을 되돌아보며
우아한 테크코스 프리코스 3주차 미션에 대한 회고와 그동안 제가 공부해왔던 것들을 간단하게 정리하여 공유해보고자 합니다!! 👏👏👏
🚩 3주차 미션 회고
✍ 다형성을 이용한 클래스 분리
private boolean menu() {
MainOutputView.showMenu();
String inputMenu = inputMainMenu();
if (Menu.CLOSE.equals(inputMenu)) {
MainOutputView.informClose();
return true;
}
selectMenuOne(inputMenu);
selectMenuTwo(inputMenu);
selectMenuThree(inputMenu);
selectMenuFour(inputMenu);
return false;
}
private void selectMenuFour(String inputMenu) {
if (Menu.FOUR.equals(inputMenu)) {
showLineMap();
}
}
private void selectMenuThree(String inputMenu) {
if (Menu.THREE.equals(inputMenu)) {
sectionController.start();
}
}
private void selectMenuTwo(String inputMenu) {
if (Menu.TWO.equals(inputMenu)) {
lineController.start();
}
}
위 코드는 처음 제가 짰던 메인 메뉴 코드입니다.🤣
private boolean menu() {
MainOutputView.showMenu();
String inputMenu = inputMainMenu();
if (Menu.CLOSE.equals(inputMenu)) {
MainOutputView.informClose();
return true;
}
selectMenuOne(inputMenu);
selectMenuTwo(inputMenu);
selectMenuThree(inputMenu);
selectMenuFour(inputMenu);
return false;
}
private void selectMenuFour(String inputMenu) {
if (Menu.FOUR.equals(inputMenu)) {
showLineMap();
}
}
private void selectMenuThree(String inputMenu) {
if (Menu.THREE.equals(inputMenu)) {
sectionController.start();
}
}
private void selectMenuTwo(String inputMenu) {
if (Menu.TWO.equals(inputMenu)) {
lineController.start();
}
}
위 코드는 처음 제가 짰던 메인 메뉴 코드입니다.🤣
보시는 바와 같이, 메뉴를 검색하는 것만으로 이미 함수 라인이 15줄을 넘어갑니다.
이 부분을 없애기위해 온갖 분리를 다 해보았지만, 메뉴별로 검사하는 if
문이 들어가 있는 이상 15라인을 무조건 넘어갔습니다. (심지어 이미 메뉴를 선택했는데 또 다른 메뉴를 검사해야하는 불필요한 기능을 수행합니다.)
프로그래밍 요구사항 👇
그때부터, 6일간 이것만 하루종일 고민하며 찾아 해멨습니다.
이 프리코스 과정을 진행하면서 처음으로 “고통스럽다”고 생각했습니다.
아무리 찾아봐도 어떻게 분리해야될지 감이 오질 않았고, 정말 수많은 검색과 자바 API들을 찾아보는 등 해볼 수 있는 모든 시도들을 해보았습니다.
잘하시는 분들의 코드를 참고해 Enum의 리팩토링을 통해 메뉴 입력에 맞는 결과값을 제공해주는 방법을 발견하였습니다.
사실 발견하긴 했지만 그것을 이해하는 것도 쉽지 않았습니다. 😭
제가 구현할 수 있는 수준은 반복문, 기본적인 API 정도인데 람다식과 함수형 인터페이스를 유연하게 이용해야 했고,
'그냥 원래 코드로 쓸까? 하나정도는 15라인 넘어도 되지않을까?'
라는 정신 승리의 유혹에 빠질 뻔 하기도 하였습니다.
하지만 제 경험상 프로그래밍은 집요하게 파고들면 분명 결과물은 나왔기에, 포기하지않고 우선 내가 할 수 있는 최선으로 만들어보자고 생각하였습니다.
// Enum Menu 리팩토링
public enum MainMenu {
STATION("1", new StationController()),
LINE("2", new LineController()),
SECTION("3", new SectionController()),
PRINT_ALL("4", new MainController()),
EXIT("Q", null);
...
public static MainMenu findMenu(String inputMenu) {
return Arrays.stream(MainMenu.values())
.filter(menu -> menu.equals(inputMenu))
.findAny()
.orElseThrow(() -> new NoSuchMenuException());
}
}
그렇게 Enum
에 대한 리팩토링이 되었고, 아직 제 코드가 많이 어설프다는 것이 느껴지긴 하지만 이 리팩토링을 통해 함수를 15라인 이내로 미션의 요구사항을 지킬 수 있었습니다.
-
📌 최종 클래스 분리 결과
📌 예외 클래스를 활용하여 코드 줄이기
private void registerSection(Menu menu) {
String line = inputSectionLine(menu);
if (line == null) {
return;
}
String sectionStation = inputSectionStation(menu, line);
if (sectionStation == null) {
return;
}
String order = inputSectionOrder(line);
if (order == null) {
return;
}
addSection(line, Integer.valueOf(order), sectionStation);
}
위 코드는 null
값을 검사하는 if
문이 총 3개로, null
값 검사에만 9줄이 사용됩니다.
이러한 여러 if
문이 반복되어 코드가 길어지는 것을 방지하고 싶어 예외 처리 클래스를 따로 두게 되었습니다.
public static boolean add() {
try {
String line = inputAddLine();
String station = inputAddStation(line);
String order = inputAddOrder(line);
LineRepository.addSection(line, station, order);
SectionOutputView.successAdd();
} catch (NullPointerException e) {
return false;
}
return true;
}
private static String inputAddLine() {
try {
SectionOutputView.registerSectionLineName();
String line = InputView.input();
InputValidator.validLineName(line);
LineRepository.notExistLineName(line);
return line;
} catch (InvalidLineNameException | NotExistLineException e) {
throw new NullPointerException();
}
}
...
다음과 같이 사용자 예외처리를 사용한 결과 반복되던 if
문이 사라지고, 중간에 하나라도 NullException
이 발생할 경우 add
메소드가 제대로 처리되지 않습니다.
뿐만 아니라, 예외처리를 명확히 표시할 수 있어 어떤 예외가 반환되는 것인지도 확인해줄 수 있었습니다.
🚩 자바에 대한 기본기
이번 우아한 테크코스를 진행하면서, 제가 아는 자바는 정말 100분의 1정도라는 것을 깨달았습니다.
이후 혹시라도 내가 놓치고 있는 기본 개념은 없는 지 확인하기 위해 자바의 기본 개념들을 공부하고 블로그에 정리하였습니다.
뿐만 아니라 자바에 많이 사용되는 도구인 InteliJ를 사용하여 좀 더 나은 코드를 위해 기능을 활용하려고 노력했습니다.
아직은 제가 글도 서투르고 정리에 익숙치 않아, 기록한 글들을 반복해서 다시 보면서 많은 사람들이 이해하고 도움이 될 수 있는 글이 되도록 보완할 계획입니다. 💪
📌 자바의 기본 개념 정리
📌 자바의 API 개념 정리
📌 InteliJ 사용해보기
🚩 컨벤션에 대하여
친구들과 프로젝트를 진행하면서 어떻게 하면 친구들이 조금이라도 더 쉽게 내 코드를 알아볼 수 있을 지 애쓰며 코딩했던 적이 있었습니다.
이번 우테코 과정에서 컨벤션에 관한 공부를 하면서 ‘당연히 이런 규칙이 있었을 텐데, 왜 몰랐을까?’ 하는 생각과 함께,
전부터 느껴왔던 ‘어떻게 하면 더 보기 쉽게 코딩할 수 있을까?’에 대한 의구심이 차츰 해결되는 것을 느꼈습니다.
제가 했던 공부들을 잊지 않고 기억하기 위하여 블로그에 글을 정리해두었습니다.
📌 컨벤션 정리 목록
🚩 객체 지향에 대하여
사실 자바의 객체 지향, 많이 들어서 익숙하긴 하지만 왜 중요한 것인지 이해하기 힘들었습니다.
그러나 우아한 테크코스 과정에서 주어진 피드백을 따라가다 보면 저절로 객체지향을 찾게 되는 걸 보면서 객체 지향의 중요성을 조금씩 이해하게 되었습니다.
아직 완벽하게 파악했다라고 말할 수는 없겠지만, 천천히 친해지는 과정이라고 생각하고 계속해서 공부해보려고 합니다. (객체지향.. 친하게 지내자🤝)
📌 객체 지향 정리 목록
🚩 입력값 검증 시의 순서
- 등록할 역 이름 검증하기
- `순서 주의`
- 한글 이외의 입력값 검사하기 👉 한글이 아니라면 에러처리
- 역 이름 2자 이상 👉 범위를 만족하지 못하면 에러처리
- 역 이름 중복 검사 하기 👉 중복된다면 에러처리
- 등록할 역 이름 검증하기
- `순서 주의`
- 한글 이외의 입력값 검사하기 👉 한글이 아니라면 에러처리
- 역 이름 2자 이상 👉 범위를 만족하지 못하면 에러처리
- 역 이름 중복 검사 하기 👉 중복된다면 에러처리
검증 순서에 따라 위와 같이 불필요한 검증 수행을 줄일 수 있습니다.
또는 패턴을 이용하여 이 검증 차례를 한번에 검사할 수도 있습니다.
public boolean validStationName(String input) {
return Pattern.matches("[가-힣]+역", input);
}
훨씬 간단하고 보기 쉽습니다. 👍
패턴은 Stream
과 비슷하게 알면 알수록 쉽게 사용할 수 있는 편리한 API라고 생각합니다.
🚩 입력 예외처리에 대하여
- 역 이름 입력시, "역"이 마지막에 포함되는가?
- 노선 이름 입력시, "선"이 마지막에 포함되는가?
- 노선 이름 입력시, 숫자도 포함할수 있음에 주의
- 상행/하행 종점역 입력시 두 역의 이름이 중복되는가?
- 만약 다른 이름의 노선, 똑같은 상행/하행역이 생긴다면? 👉 추가되는 구간에 따라 다른 노선이 될 수 있으므로 예외처리하지 않는다.
- 역 이름 입력시, "역"이 마지막에 포함되는가?
- 노선 이름 입력시, "선"이 마지막에 포함되는가?
- 노선 이름 입력시, 숫자도 포함할수 있음에 주의
- 상행/하행 종점역 입력시 두 역의 이름이 중복되는가?
- 만약 다른 이름의 노선, 똑같은 상행/하행역이 생긴다면? 👉 추가되는 구간에 따라 다른 노선이 될 수 있으므로 예외처리하지 않는다.
위의 목록들은 제가 3주차 미션을 진행하면서 까다로웠던 예외 처리입니다.
특히, 이름 입력에 대해서는 한글만을 입력받았는데 노선은 숫자도 입력될 수 있다는 점을 놓칠 뻔하였습니다.
항상 입력 가능 범위에 대해서 꼼꼼히 체크해야합니다!!
🚩 Getter/Setter에 대하여
1주차 미션때부터 Getter/Setter
를 지양하라는 글을 보고 항상 염두해두며 최대한 사용하지 않는 방향으로 코딩을 했습니다.
setter/getter은 외부에서 데이터에 대한 접근이 쉬워질 수있기때문에 최소화하는 것을 요망하지만(get을 지양해야하는 이유)
, 그러나 get이 쓰여야할 때가 있습니다.
가령 view
를 사용하는 경우 outputview
내에서 데이터를 출력하도록, 즉 outputview
가 본인의 역할을 하기 위해서는 get
을 통해 데이터를 가져와야합니다.
그렇다면 get
을 최소화 할수 있는 것은 무엇일까요?
바로 '상태'를 나타내는 경우입니다.
데이터를 관리하는 도메인 내에서 확인하고 그에 맞는 정보를 제공할 수 있는 경우가 그렇습니다.
다음과 같이 객체에 메시지를 보내 어떤 '상태'인지 확인할 수 있습니다.
저는 이것을 참고하여 '상태'를 통해 결과값을 받을 수 있는 경우는 get
대신 객체에 메시지를 보내는 방식으로 코드를 구현하였습니다.
// 역 이름이 존재하는 지에 대한 '상태'를 묻는다.
private static void existStationName(String name) {
stations.stream()
.filter(station -> Objects.equals(station.getName(), name))
.findAny()
.ifPresent(s -> {
throw new AlreadyExistStationException();
});
}
🚩 Interface 사용하기
📌 첫번째 예제
1주차 미션에서 입력에 대한 검증을 위해 validator
인터페이스를 만들어주었습니다.
사용할 검증 클래스에 따라 객체를 갈아끼우며(?) 1개의 변수만으로 여러 검증 객체를 사용할 수 있었습니다.
private void validate(String input) {
validator = new NumberValidator();
validator.excute(input);
validator = new DuplicateValidator();
validator.excute(input);
}
📌 두번째 예제
public interface Controller {
void start();
}
public enum MainMenu {
STATION("1", new StationController()),
LINE("2", new LineController()),
SECTION("3", new SectionController()),
PRINT_ALL("4", new MainController()),
EXIT("Q", null);
...
public static MainMenu findMenu(String inputMenu) {
return Arrays.stream(MainMenu.values())
.filter(menu -> menu.equals(inputMenu))
.findAny()
.orElseThrow(() -> new NoSuchMenuException());
}
}
3주차 미션 컨트롤러에 대한 인터페이스입니다.
모든 컨트롤러들이 상속받는 Controller
인터페이스를 구현하고 findMenu
를 통해서 자식 컨트롤러 객체를 전달하면 그에 맞는 start()
함수를 실행하도록 구현하였습니다.
menu.getController().start(); // 컨트롤러 걱 클래스 별로 다른 start() 기능을 수행
🚩 주석에 대하여
프리코스를 진행하면서 함수와 변수로만 설명할 수 있는 힘을 기르고 싶었기에 일부러 어떤 주석도 달지 않았습니다.
물론 꼭 주석이 필요한 경우가 있겠지만, 주석을 못 다는 상황이라고 생각하고 함수와 변수 네이밍에 더 많은 시간을 고민했습니다.
🚩 커밋 단위에 대하여
git의 commit 단위는 앞 단계에서 README.md 파일에 정리한 기능 목록 단위로 추가한다.
git의 commit 단위는 앞 단계에서 README.md 파일에 정리한 기능 목록 단위로 추가한다.
위의 내용은 진행 요구사항에 일부분입니다.
처음 미션을 진행할 당시 저는 기능 목록 단위를 생각하지 않고 코드가 수정될 때마다 커밋하였습니다.
그러나 기능 단위로 커밋하는 경우 코드를 읽는 입장에서는 의도를 알고 코드를 볼 수 있기 때문에 훨씬 더 이해하기 쉽겠다는 것을 유추할 수 있었습니다.
그렇다면 커밋 단위는 얼마나 세세하게 나누어야할까요?
이 부분에서도 많은 고민과 검색을 해보았는데요, 아직 저에게는 애매한 기준입니다..
그래서 저는 프리코스 미션에서 요구하는 것처럼 기능 목록 단위로 커밋할 수 있도록 노력하였습니다.
하나 문제점이 있다면 코드가 갑자기 여러 다른 기능들이 한꺼번에 수정될 때 커밋 단위를 나누기 어려운 경우가 있었습니다. 😅 (특히 마지막 미션을 하면서 거의 커밋 단위를 지키기 어려웠습니다..😥)
코드를 작성할 때 항상 커밋 단위에 대한 염두를 둘 수 있도록 주의해야겠습니다.
🛴 마무리
3주간 프리코스를 마치면서, 제가 제일 먼저 떠오르는 것은 "프리코스에 대한 나의 목표를 잘 지켰는가?
"였습니다.
프리코스를 처음 시작할 당시 저는 "후회가 남지 않을 만큼 할 수 있는 최선을 다하여 즐겁게 배우자"라는 마음가짐으로 도전하였습니다.
프리코스를 시작하면서 배움의 방향을 잡기 위해 피드백에 집중하였고, 그에 걸맞는 프로그래밍을 해내기 위하여 하루종일 고민하였습니다.
첫 주차에는 낯선 경험이 힘들기도 했지만 매주 미션을 진행할수록 스스로가 이 미션에 몰입해가고 있다는 생각이 들었습니다.
프리코스 미션에 몰입하여 코딩하다가 시간이 늦었으니 나가달라는 스터디 카페 사장님의 이야기를 듣고 훌쩍 지난 시간에 깜짝 놀라기도 하였습니다.
(나중가서는 꿈에서도 클래스 분리를 하는 경지에 도달했습니다..☠ 일어나면 하나도 기억 안 나는 건 함정)
이렇게 몰입해가며 공부하다보니 3주가 정말 시간 가는 줄 모르고 지나갔습니다.
스스로 최선을 다하며 즐겁게 배운 이 과정이 저에게 너무 소중하고 값진 경험이 되었습니다.
그리고 다른 분들의 코드를 보며 새로운 배움이 있었고 저 또한 누군가에게 도움을 줄 수 있으면 좋겠다고 생각하였습니다.
그래서 제가 배운 내용을 공유하고자 글을 작성하였고 처음 보시는 분들도 쉽게 이해하실 수 있는 수준으로 작성하기 위해 노력하였습니다. (혹시 글 중에 이해가 안 가거나 잘못된 내용이 있다면 언제든 댓글 달아주세요 🤗)
저의 부족한 실력의 코드와 긴 글들을 항상 읽어주시는 우아한 테크코스에도 진심으로 감사드리고, 코로나로 힘든 상황임에도 학습에 동기부여를 주는 우테코 지원자분들께도 정말 감사했습니다.
다들 정말 고생 많으셨고 좋은 결과가 있으시길 진심으로 응원합니다!!
모두들 마지막까지 화이팅!!!! 👏👏
Author And Source
이 문제에 관하여(프리코스 3주 과정을 되돌아보며), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@bosl95/프리코스-3주-과정을-되돌아보며저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)