우아한 테크코스/회고

[우테코 프리코스 회고] 3주차를 마치며

 

 

3주간의 우테코 프리코스가 마무리되었다.

길다면 길고, 짧다면 짧을 3주였지만, 정말 치열하게 살았다고 생각한다.

 

 

혹시 누군가 이 포스팅을 본다면, 그리고 이런 프리코스의 과정에 관심이 생겼다면, 한번쯤은 그냥 3주간의 코스를 따라가보는 것도 좋을 것 같다고 생각할 정도로, 많은 것들이 바뀔 수 있는 시간이었다.

(심지어 좋은 분들의 코드도 나와있고, 포스팅을 올려주시는 분들도 계시니, 정말 많은 것들을 배워갈 수 있을 것이라고 장담한다. 나도 미리 알았더라면 해보았을텐데, 늦게나마 알아낸 것에 감사하고 있는 중이다.)

 

그리고 다른 분들의 코드를 보다보면, '저 사람은 왜 저렇게 짠 건지 이야기를 들어보고 싶다'  라는 생각이 종종 든다.

내가 느낀 그 사람의 좋은 점을 공유하고, 또 본받으며 성장하고 싶다는 생각이 드는 좋은 개발자들과 3주간 성장했다는 점에서 묘한 동질감도 느껴진다.

합격해서 함께 짝 프로그래밍을 할때, 많은 것들을 배워갈 수 있는 크루가 스스로 되고싶다는 생각을 했다.

 

 

오늘도 긴 서론을 마치고... 

 

 

이번주차의 문제는 이번 기수에 새롭게 나온 자판기 미션이었다. (아마 3주차는 매번 새로운 미션으로 나오는 것 같다.)

개인적으로 이전 주차의 문제들에 비해 난도가 높아졌다는게 체감되었던 문제였다.

 

 

 

 

피드백 및 적용한 내용

이번주차에도 피드백내용을 간단하게 정리하자면, 다음과 같다.

  • 기능목록 구현을 재검토한다
    저번주 피드백과 동일한 내용이다. 기능목록을 확실하게 정리하고, 예외사항까지 자세히 구현하는 것이 얼마나 중요한지 다시금 생각하게 된 것 같다.
  • 값을 하드코딩하지마라
    문자열이나 숫자등의 값을 하드코딩 하지 않고, java 상수 구현 방식을 통해 구현한다. (나같은 경우는 constants 패키지 내에 상수값들을 따로 관리하였다)
  • 축약하지마라
    의도를 드러낼 수 있다면 이름이 길어져도 괜ㅊ낳다.
  • commit메시지에 "#번호"를 추가하지 않는다.
  • 예외케이스에 대해 고민한다
  • 주석은 꼭 필요한 경우만 남긴다 (이름으로 알 수 있어야 한다.)
  • git을 통해 관리할 자원에 대해서도 고려한다
    .idea 폴더나 .metadata와 같은 폴더는 굳이 git으로 관리하지 않아도 된다.
  • Pull Request 보내기 전, 브랜치를 확인한다.
  • java에서 제공하는 api를 적극 활용한다.
  • 배열대신 java collection을 사용하라
  • 객체에 메시지를 보내라
  • 필드(인스턴스 변수)의 수를 줄이기 위해 노력한다
  • 비즈니스 로직과 UI로직을 분리해라
    비즈니스 로직과 UI로직을 한 클래스가 담당하지 않도록 한다.

이번 피드백을 보면서, 자바 api를 좀더 찾아볼까? 라는 생각을 했다. collection을 활용하는 것 역시 해당 collection에서 제공하는 함수들을 적극적으로 활용할 수 있기 때문인 것이라고 느꼈다.

내가 만드는 코드보다 효율적인 방식이 있다면 충분히 활용하고, 그 이외의 것에 조금 더 신경쓰는 것이 보다 효과적인 방안일 것이라고 생각했다. 이번 주차에는 그런 API의 활용에 조금 더 초점을 맞췄던 것 같다.

또한, 상수값을 constants로 따로 처리하고 있었는데, 이 방식이 맞는 것 같아서 조금 안심한 것도 있었다. 

 

전체적인 피드백이다보니, 아무래도 내 코드에 대한 상세한 피드백은 아니지만, 이를 통해서도 충분히 배워갈 점들을 알아갈 수 있어서 좋았던 것 같다. 

 

3주차 미션 안내 메일의 일부

또한 목표를 정함에 있어 "클래스간의 관계를 맺어 하나의 프로그램을 완성하는 경험" , "극단적인 연습" 이라는 두가지 키워드를 잡고, 무엇을 더 배우면 좋을지 고민했다. 따라서, 2주차에 고민했던, 그리고 적용했던 내용들에 더해서 몇가지 목표를 추가했다.

 

 

목표

  1. Enum 써보기
  2. Stream Api 사용해보기
  3. regex 사용해보기
  4. 자바 Object 클래스 오버라이딩 해보기

해당 목표들을 제외하고, 2주차에서 진행했던 MVC 패턴이나 일급컬렉션, 커밋과 적절한 이름짓기의 내용들을 이어서 가져갔다.

또한, 최대한 getter를 줄이고, 메서드에게 묻는 방식으로 구현하고자 노력했다. (이번 과제 전체에서 사용한 getter 메소드는 총 한개이다)

 

 

공부 및 적용한 내용

1. Enum 

Enum이란 Enumeration의 앞 글자로 열거라는 의미를 가지며, 관련이 있는 상수들의 집합이다.
기존에 나는 constant 파일 내에 상수를 constant class 내에 넣는 방식으로 진행하였는데, 이는 서비스가 커지는 것에 따라 네이밍이 겹치거나, 불필요하게 많아지는 문제가 생길 수도 있다. 이를 보완하며 나온 것이 Enum이다.

다음과 같은 방식으로 enum type의 객체를 생성할 수 있다.

class와 유사하게 생성자나 추가적인 속성, 메서드 등을 추가할 수 있다.

특히, 자바8부터 활용 가능한 함수형 인터페이스를 활용해 함수를 활용할 수 있다. 이를 적절히 활용하면 보다 효율적인 방식으로 코드를 구현할 수 있을 것으로 보인다.

public enum Coin {
	COIN_500(500),
	COIN_100(100),
	COIN_50(50),
	COIN_10(10);

	private final int amount;

	Coin(final int amount) {
		this.amount = amount;
	}

	public static Coin findByAmount(int amount) {
		return Arrays.stream(Coin.values())
			.filter(coin -> coin.checkAmount(amount))
			.findFirst()
			.orElseThrow(() -> new IllegalArgumentException(COIN_NOT_FOUND_ERROR));
	}

	private boolean checkAmount(int amount) {
		return this.amount == amount;
	}

	@Override
	public String toString() {
		return amount + KOR_MONETARY_UNIT + DASH_WITH_SPACE;
	}
}

해당 내용은 이번 주차에 제시된 Coin을 수정한 내용이다. 만들면서 getter를 적게 사용하고(객체에 메시지를 보낸다!), 적절한 메서드들을 추가하고자 노력하였다.

 

추가적인 활용방안 및 장점

  1. 서로 관련있는 상수값들을 모아 구현하는 경우 유용하다
  2. 데이터의 그룹화 및 관리에 유용하다. (상태와 행위를 한곳에서 관리할 수 있다.)
  3. Lambda를 활용해 사용을 극대화 할 수 있다.

 

 

 

참고자료

https://velog.io/@kyle/자바-Enum-기본-및-활용

https://honbabzone.com/java/java-enum/

 

 

2. Stream API

자바는 기본적으로 객체지향 언어이기 때문에, 함수형 프로그래밍이 불가능하다고 생각할 수 있다. 

하지만, JDK 8부터는 Stream API와 람다식, 함수형 인터페이스등을 지원해 Java를 이용해 함수형으로 프로그래밍할 수 있는 API들을 제공해주고 있다. 그중, Stream API는 데이터를 추상화하고, 처리하는데 자주 사용되는 함수들을 정의해두었다. 여기서 데이터를 추상화하였다는 것은 데이터의 종류에 상관 없이 같은 방식으로 데이터를 처리할 수 있다는 것을 의미하며, 그에 따라 재사용성을 높일 수 있다.

 

// Stream 사용 전
String[] nameArr = {"IronMan", "Captain", "Hulk", "Thor"}
List<String> nameList = Arrays.asList(nameArr);

// 원본의 데이터가 직접 정렬됨
Arrays.sort(nameArr);
Collections.sort(nameList);

for (String str: nameArr) {
  System.out.println(str);
}

for (String str : nameList) {
  System.out.println(str);
}

// 코드 출처 : https://mangkyu.tistory.com/112

만약 이런식으로 배열을 저장했었다면, 이번에는 stream을 사용해, 보다 가독성있는  함수형 방식으로 리팩토링한다면 다음과 같은 방식이 된다.

String[] nameArr = {"IronMan", "Captain", "Hulk", "Thor"} 
List<String> nameList = Arrays.asList(nameArr); 

// 원본의 데이터가 아닌 별도의 Stream을 생성함 
Stream<String> nameStream = nameList.stream(); 
Stream<String> arrayStream = Arrays.stream(nameArr); 

// 복사된 데이터를 정렬하여 출력함 
nameStream.sorted().forEach(System.out::println);
arrayStream.sorted().forEach(System.out::println);

// 코드 출처: https://mangkyu.tistory.com/112

 

이렇게 사용하게 되면, 우테코에서 제한하는 함수 내의 15줄 제한에도 효과적이고, 가독성도 높아진다.

 

추가적으로, Stream API의 특징은 다음과 같다. 

 

  • 원본의 데이터를 변경하지 않는다. (읽기만 할 뿐, 변경하지 않는다.)
  • 일회용이다.
  • 내부 반복으로 작업을 처리한다.

참고자료

https://velog.io/@new_wisdom/Java-Stream-1

 

https://mangkyu.tistory.com/112 

https://codechacha.com/ko/java8-stream-find-match/

 

 

 

3. regex

정규 표현식은 줄여서 정규식이라고도 하며, 영어로는 Regular Expression, 줄여서 regex, regexp라고도 한다. 정규 표현식은 특정한 규칙을 가진 문자열의 집합을 표현하기 위해 쓰이는 형식언어이다.

이번 과제에서 정규식을 써야겠다! 라고 생각한 것은 이번 과제의 입력 부분에 있어서 입력 문자열의 조건이 굉장히 까다로웠기 때문이다.

쉼표, 대괄호, 세미콜론 등 조건이 까다롭다.

이를 전부 if문으로 처리한다면... 코드의 양이 많아질 뿐만 아니라, 제대로 탐지하지 못할 것 같다!! 라는 생각이 들었다.

이런 상황에서 효과적으로 사용할 수 있는 것이 바로 regex, 정규표현식이다.

 

Regex에서는 다음과 같은 방식으로 패턴을 사전에 정의하게 된다. 이런식으로 입력을 넣을 것이다, 라고 미리 선언을 한 뒤 확인하는 방식으로 진행된다.

Regular Expression Description
. 어떤 문자 1개를 의미
^regex ^ 다음 regex로 line을 시작하는지
regex$ $ 앞의 regex가 line의 마지막으로 끝나는지
[abc] a, b, c 중의 문자 1개
[abc][vz] a, b, c 중에 문자 1개와 v, z 중에 문자 1개의 조합
[^abc] a, b, c를 제외한 문자 1개
[a-d1-7] ad, 17 사이의 문자 1개
X|Z X 또는 Z
\d 0~9 사이의 숫자, [0-9]와 동일
\D 숫자가 아닌 어떤 문자, [^0-9]와 동일
\s whitespace 1개, [\t\n\x0b\r\f]와 동일
\S whitespace를 제외한 문자
\w Alphanumeric(alphabet, 숫자) 문자, [a-zA-Z_0-9]와 동일
\W Alphanumeric을 제외한 문자(whitespace 등)
\S+ whitespace를 제외한 여러 문자
\b 단어의 경계(공백)를 찾습니다

 

자바 문자열 객체에서 정규식을 사용할 때, 가장 많이 사용되는 메소드는 다음과 같다. 

 

- boolean matches(String regex)

  인자로 주어진 정규식에 매칭되는 값이 있는지 확인합니다.

 

- String replaceAll(String regex, String replacement)

  문자열내에 있는 정규식 regex와 매치되는 모든 문자열을 replacement문자열로 바꾼 문자열을 반환합니다.

 

- String[] split(String regex)

  인자로 주어진 정규식과 매치되는 문자열을 구분자로 분할합니다

 

 

이중에서, 나는 matches메소드를 사용해서 인자로 주어진 정규식에 매칭되는 값이 있는지 확인하는 과정을 거쳤다.

	private static void isMatchRegex(String inputContent) {
		if (!Pattern.matches(REGEX, inputContent)) {
			throw new IllegalArgumentException(PRODUCT_FORMAT_ERROR);
		}
	}

이런 방식으로, 쉽게 입력값의 조건을 확인할 수 있다.

 

 

참고자료

https://medium.com/depayse/java-%EC%A0%95%EA%B7%9C-%ED%91%9C%ED%98%84%EC%8B%9D-regular-expression-%EC%9D%98-%EC%9D%B4%ED%95%B4-31419561e4eb

https://offbyone.tistory.com/400

 

 

 

 

4 Object 클래스 오버라이딩

Object 클래스는 모든 클래스의 최상위 클래스로, 아무것도 상속받지 않으면 자동으로 Object를 상속받기 때문에, Object가 가지고 있는 메소드는 모든 클래스에서 다 사용할 수 있다는 것을 의미한다. 

이때, Object가 가지고 있는 메소드 중에서 가장 많이 사용되는 메소드는 equals, toString, hashCode가 있는데, 이를 사용하려면 용도에 맞게 적절히 오버라이딩하여 사용하는 것이 필요하다.

 

• equals : 객체가 가진 값을 비교할 때 사용

• toString : 객체가 가진 값을 문자열로 반환

• hashCode : 객체의 해시코드를 구할 때 사용

 

나의 경우에는, equals와 toString을 활용하였다.

 

// VendingMachineProduct.java

	@Override
	public boolean equals(Object o) {
		if (!(o instanceof VendingMachineProduct) || o.getClass() != getClass()) {
			return false;
		}
		VendingMachineProduct vendingMachineProduct = (VendingMachineProduct)o;
		return vendingMachineProduct.isSameName(name);
	}
    
    
    
// Coin.java
	@Override
	public String toString() {
		return amount + KOR_MONETARY_UNIT + DASH_WITH_SPACE;
	}

 

먼저 equals의 경우에는 두 상품의 이름이 같은지 비교하는 용도로 사용하였다.

vendingMachineProduct내에는 이름, 가격, 양이라는 세가지 값이 들어가는데, 그중 이름을 중심으로 비교하도록 구성한 것이다. 이를 통해서 물리적으로 다른 메모리에 위치하는 객체여도, 이름값이 같다면 논리적으로 동일함을 구현할 수 있게 된다.

 

다음으로 toString의 경우는 객체의 정보를 문자열 형태로 보다 쉽게 표현하고자 사용하였다.

이는 getter를 줄이기 위한 한가지 방안이기도 하였다.

기본적으로 toString()의 원형은 getClass().getName() + '@' + Integer.toHexString(hashCode())이다.

이를 출력하고 싶은 방식으로 재구성하여 return함으로써, getter없이 객체를 출력하는 것만으로도 적절한 메시지를 줄 수 있도록 구성하였다.

 


참고자료
https://thefif19wlsvy.tistory.com/163 

https://atoz-develop.tistory.com/entry/%EC%9E%90%EB%B0%94-Object-%ED%81%B4%EB%9E%98%EC%8A%A4-%EC%A0%95%EB%A6%AC-toString-equals-hashCode-clone

 

 

 

정리하며

사실 이번 과제에서 테스트코드까지 짜보고 싶은 생각이 있었다. 이는 내 욕심이기도 했는데, 기말이나 다른 시간에 쫓기고, 이전에 비해 높은 난도의 문제를 접하다보니, 선택의 기로에 놓여있었다.

'공부를 조금 얕게 하더라도 테스트까지 해볼 것인가'

'기능 구현과 효율적인 코드를 좀 더 고민해보고, 테스트는 뒤에 공부할 것인가'

 

일단 내 답은, 기능 구현에 조금 더 신경을 쓰는 것이었다.

우아한 테크코스에서 바라는 방식은 일단 기능 구현이 잘 되고, 스스로 무엇이 부족한지 깨달으며 공부를 계속해나가는 방식이라고 생각했다. 오히려 테스트나 다른 기능들, 눈에 띄는 것들에 급급하다가 본질을 놓쳐서는 안된다는 생각이 들었다. 이것이 어쩌면 부족함으로 보일지도 모르겠지만, 하나씩 천천히, 충분히 고민하고 부딪히며 공부하는 것이 나의 템포에는 맞다고 생각했다.

 

따라서, 특히 getter를 줄이거나, 기존 API를 최대한 활용해보고자 노력하였다.

보다 효율적인, 그리고 가독성이 좋은 코드에 대한 고민을 이어나가고, 조금 더 노력하다보면 언젠가 나도 남들이 보았을 때 '이사람의 코드는 정말 깔끔하구나!' 라고 이야기할 수 있는 사람이 되지 않을까.

 

시험기간과 완전히 겹치다보니, 내일 또 시험이 있다. 바쁘고 피곤한 나날임에도, 정리하면서 뿌듯함을 느끼는 것은 아마 내가 이 시간에 스스로 거짓이 없었다고 느끼기 때문일 것이다.

 

이제 정말 최종 코딩테스트만이 남았다. 그 사이에도 기존 코드를 복기하고 되새기며 계속 공부할 예정이다.

 

지금까지 함께 달려주신 우테코 여러분들과 지원자분들께 전부 감사하는 마음으로, 마지막까지 열정을 품고, 이번주 회고를 마치려고 한다.