SlideShare ist ein Scribd-Unternehmen logo
1 von 94
의식적인 연습으로
TDD, 리팩토링 연습하기
박재성(자바지기)
넥스트스텝(NextStep) 이름으로 온/오프라인 코드리뷰 중심 교육
SLiPP(https://www.slipp.net) 커뮤니티 운영
이 발표에서는
TDD와 리팩토링을 왜 해야 하는지 알고 있다.
는 가정 하에 진행한다.
이 발표에서는
TDD와 리팩토링을 비슷한 비중으로 다룬다.
어쩌면 TDD < 리팩토링 일지도 모른다.
개발 현장을 떠나 교육자로 산지 6년.
이 발표의 상당 수가 구라일 수 있으니 주의해야 한다.
발표 내용
• 의식적인 연습이란?
• 의식적인 연습으로 TDD, 리팩토링 연습 과정
의식적인 연습이란?
TDD, 리팩토링을 잘 하려면…
연습, 연습, 연습 …
무조건 연습을 많이 한다고 잘할 수 있을까?
TDD, 리팩토링과 관련해 5, 6년을 도전한 후에야
테스트하기 쉬운 코드와 테스트하기 어려운 코드를 보는 눈
테스트하기 어려운 코드를 테스트하기 쉬운 코드로 설계하는 감
(sense)
TDD, 리팩토링. 멋져 보인다.
하지만 생각만큼 쉽게 연습할 수 있는 녀석이 아니다.
교육자로서 최근의 고민
좀 더 효과적으로 연습할 수 있는 방법은 없을까?
아마추어와 프로의 결정적 차이
목적 의식 있는 연습
에 얼마나 많은 시간을 투자했느냐?
의식적인 연습의 7가지 원칙
• 첫째, 효과적인 훈련 기법이 수립되어 있는 기술 연마
• 둘째, 개인의 컴포트 존을 벗어난 지점에서 진행, 자신의 현재 능력
을
살짝 넘어가는 작업을 지속적으로 시도
• 셋째, 명확하고 구체적인 목표를 가지고 진행
의식적인 연습의 7가지 원칙
• 넷째, 신중하고 계획적이다. 즉, 개인이 온전히 집중하고 '의식적'으로
행동할 것을 요구
• 다섯째, 피드백과 피드백에 따른 행동 변경을 수반
• 여섯째, 효과적인 심적 표상을 만들어내는 한편으로 심적 표상에 의존
• 일곱째, 기존에 습득한 기술의 특정 부분을 집중적으로 개선함으로
써
발전시키고, 수정하는 과정을 수반
의식적인 연습으로 효과적으로 연습하자.
의식적인 연습으로
TDD, 리팩토링 연습하는 과정
1단계 - 단위 테스트 연습
내가 사용하는 API 사용법을 익히기 위한 학습 테스트에서 시
작
• 자바 String 클래스의 다양한 메소드(함수) 사용법
• 자바 ArrayList에 데이터를 추가, 수정, 삭제하는 방법
import static org.assertj.core.api.Assertions.assertThat;
public class StringTest {
@Test
public void split() {
String[] values = "1".split(",");
assertThat(values).contains("1");
values = "1,2".saplit(",");
assertThat(values).containsExactly("1", "2");
}
@Test
public void substring() {
String input = "(1,2)";
String result = input.substring(1, input.length() - 1);
assertThat(result).isEqualTo("1,2");
}
}
import static org.assertj.core.api.Assertions.assertThat;
public class CollectionTest {
@Test
public void arrayList() {
ArrayList<String> values = new ArrayList<>();
values.add("first");
values.add("second");
assertThat(values.add("third")).isTrue();
assertThat(values.size()).isEqualTo(3);
assertThat(values.get(0)).isEqualTo("first");
assertThat(values.contains("first")).isTrue();
assertThat(values.remove(0)).isEqualTo("first");
assertThat(values.size()).isEqualTo(2);
}
}
연습 효과
• 단위테스트 방법을 학습할 수 있다.
• 단위테스트 도구(xUnit)의 사용법을 익힐 수 있다.
• 사용하는 API에 대한 학습 효과가 있다.
내가 구현하는 메소드(함수) 중
Input과 Output이 명확한 클래스 메소드(보통 Util 성격의 메소
드)
에 대한 단위 테스트 연습
알고리즘을 학습한다면 알고리즘 구현에 대한 검증을 단위 테스트로 한다.
알고리즘은 Input, Output이 명확하기 때문에 연습하기 좋다.
2단계 - TDD 연습
지켜야 할 원칙 1
회사 프로젝트에 연습하지 말고 장난감 프로젝트를 활용해 연습하자.
지켜야 할 원칙 2
웹, 모바일 UI나 DB에 의존관계를 가지지 않는 요구사항으로 연습한다.
문자열 덧셈 계산기 요구사항
쉼표(,) 또는 콜론(:)을 구분자로 가지는 문자열을 전달하는 경우
구분자를 기준으로 분리한 각 숫자의 합을 반환
문자열 덧셈 계산기 요구사항
쉼표(,) 또는 콜론(:)을 구분자로 가지는 문자열을 전달하는 경우
구분자를 기준으로 분리한 각 숫자의 합을 반환
입력(input) 출력(output)
null 또는 “” 0
“1” 1
“1,2” 3
“1,2:3” 6
이미지 출처: https://mynetdev.wordpress.com/2016/01/05/tdd-to-bdd/
public class StringCalculatorTest {
@Test
public void null_또는_빈값() {
assertThat(StringCalculator.splitAndSum(null)).isEqualTo(0);
assertThat(StringCalculator.splitAndSum("")).isEqualTo(0);
}
@Test
public void 값_하나() {
assertThat(StringCalculator.splitAndSum("1")).isEqualTo(1);
}
@Test
public void 쉼표_구분자() {
assertThat(StringCalculator.splitAndSum("1,2")).isEqualTo(3);
}
@Test
public void 쉼표_콜론_구분자() {
assertThat(StringCalculator.splitAndSum("1,2:3")).isEqualTo(6);
}
}
public class StringCalculator {
public static int splitAndSum(String text) {
int result = 0;
if (text == null || text.isEmpty()) {
result = 0;
} else {
String[] values = text.split(",|:");
for (String value : values) {
result += Integer.parseInt(value);
}
}
return result;
}
}
어려운 문제를 해결하는 것이 목적이 아니라 TDD 연습이 목적
난이도가 낮거나 자신에게 익숙한 문제로 시작하는 것을 추천
3단계 - 리팩토링 연습
리팩토링 연습 – 메소드 분리
public class StringCalculator {
public static int splitAndSum(String text) {
int result = 0;
if (text == null || text.isEmpty()) {
result = 0;
} else {
String[] values = text.split(",|:");
for (String value : values) {
result += Integer.parseInt(value);
}
}
return result;
}
}
테스트 코드는 변경하지 말고
테스트 대상 코드(프로덕션 코드)를 개선하는 연습을 한다.
막막함
멘붕
막막함
멘붕
의식적인 연습 7가지 원칙
셋째, 명확하고 구체적인 목표를 가지고 진행
다섯째, 피드백과 피드백에 따른 행동 변경을 수반
막막함
멘붕
정성적인 기준보다는 정량적이고 측정 가능한 방법으로 연습
public class StringCalculator {
public static int splitAndSum(String text) {
int result = 0;
if (text == null || text.isEmpty()) {
result = 0;
} else {
String[] values = text.split(",|:");
for (String value : values) {
result += Integer.parseInt(value);
}
}
return result;
}
}
한 메서드에 오직 한 단계의 들여쓰기(indent)만 한다.
public class StringCalculator {
public static int splitAndSum(String text) {
int result = 0;
if (text == null || text.isEmpty()) {
result = 0;
} else {
String[] values = text.split(",|:");
for (String value : values) {
result += Integer.parseInt(value);
}
}
return result;
}
}
한 메서드에 오직 한 단계의 들여쓰기(indent)만 한다.
들여쓰기가 2인 곳
public class StringCalculator {
public static int splitAndSum(String text) {
int result = 0;
if (text == null || text.isEmpty()) {
result = 0;
} else {
String[] values = text.split(",|:");
result = sum(values);
}
return result;
}
private static int sum(String[] values) {
int result = 0;
for (String value : values) {
result += Integer.parseInt(value);
}
return result;
}
}
public class StringCalculator {
public static int splitAndSum(String text) {
int result = 0;
if (text == null || text.isEmpty()) {
result = 0;
} else {
String[] values = text.split(",|:");
result = sum(values);
}
return result;
}
private static int sum(String[] values) {
int result = 0;
for (String value : values) {
result += Integer.parseInt(value);
}
return result;
}
}
else 예약어를 쓰지 않는다.
public class StringCalculator {
public static int splitAndSum(String text) {
if (text == null || text.isEmpty()) {
return 0;
}
String[] values = text.split(",|:");
return sum(values);
}
private static int sum(String[] values) {
int result = 0;
for (String value : values) {
result += Integer.parseInt(value);
}
return result;
}
}
else 예약어를 쓰지 않는다.
public class StringCalculator {
public static int splitAndSum(String text) {
if (text == null || text.isEmpty()) {
return 0;
}
String[] values = text.split(",|:");
return sum(values);
}
private static int sum(String[] values) {
int result = 0;
for (String value : values) {
result += Integer.parseInt(value);
}
return result;
}
}
메소드가 한 가지 일만 하도록 구현하기
public class StringCalculator {
[…]
private static int[] toInts(String[] values) {
int[] numbers = new int[values.length];
for (int i = 0; i < values.length; i++) {
numbers[i] = Integer.parseInt(values[i]);
}
return numbers;
}
private static int sum(int[] numbers) {
int result = 0;
for (int number : numbers) {
result += number;
}
return result;
}
}
메소드가 한 가지 일만 하도록 구현하기
public class StringCalculator {
public static int splitAndSum(String text) {
if (text == null || text.isEmpty()) {
return 0;
}
String[] values = text.split(",|:");
int[] numbers = toInts(values);
return sum(numbers);
}
private static int[] toInts(String[] values) {
[…]
}
private static int sum(int[] numbers) {
[…]
}
}
메소드가 한 가지 일만 하도록 구현하기
public class StringCalculator {
public static int splitAndSum(String text) {
if (text == null || text.isEmpty()) {
return 0;
}
String[] values = text.split(",|:");
int[] numbers = toInts(values);
return sum(numbers);
}
private static int[] toInts(String[] values) {
[…]
}
private static int sum(int[] numbers) {
[…]
}
}
로컬 변수가 정말 필요한가?
public class StringCalculator {
public static int splitAndSum(String text) {
if (text == null || text.isEmpty()) {
return 0;
}
return sum(toInts(text.split(",|:")));
}
private static int[] toInts(String[] values) {
[…]
}
private static int sum(int[] numbers) {
[…]
}
}
로컬 변수가 정말 필요한가?
compose method 패턴 적용
메소드(함수)의 의도가 잘 드러나도록 동등한 수준의 작업을 하는 여러
단계로 나눈다.
public class StringCalculator {
public static int add(String text) {
if (isBlank(text)) {
return 0;
}
return sum(toInts(split(text)));
}
private static boolean isBlank(String text) {
}
private static String[] split(String text) {
}
private static int[] toInts(String[] values) {
}
private static int sum(int[] numbers) {
}
}
compose method 패턴 적용
public class StringCalculator {
public static int add(String text) {
int result = 0;
if (text == null || text.isEmpty()) {
result = 0;
} else {
String[] values = text.split(",|:");
for (String value : values) {
result += Integer.parseInt(value);
}
}
return result;
}
}
public class StringCalculator {
public static int add(String text) {
if (isBlank(text)) {
return 0;
}
return sum(toInts(split(text)));
}
private static boolean isBlank(String text) {
}
private static String[] split(String text) {
}
private static int[] toInts(String[] values) {
}
private static int sum(int[] numbers) {
}
}
add() 메소드를 처음 읽는 사람에게 어느 코드가 더 읽기 좋을
까?
한 번에 모든 원칙을 지키면서 리팩토링하려고 연습하지 마라.
한 번에 한 가지 명확하고 구체적인 목표를 가지고 연습하라.
연습은 극단적인 방법으로 연습하는 것도 좋다.
예를 들어 한 메소드의 라인 수 제한을 15라인 -> 10라인으로 줄여가면
서 연습하는 것도 좋은 방법이다.
리팩토링 연습 – 클래스 분리
다시 문자열 덧셈 계산기 요구사항
쉼표(,) 또는 콜론(:)을 구분자로 가지는 문자열을 전달하는 경우 구분자를 기준으로 분리
한
각 숫자의 합을 반환
문자열 계산기에 숫자 이외의 값 또는 음수를 전달하는 경우
RuntimeException 예외를 throw한다.
입력(input) 출력(output)
null 또는 “” 0
“1” 1
“1,2” 3
“1,2:3” 6
“-1,2:3” RuntimeException
public class StringCalculatorTest {
[…]
@Test
public void 쉼표_구분자() {
assertThat(StringCalculator.splitAndSum("1,2")).isEqualTo(3);
}
@Test
public void 쉼표_콜론_구분자() {
assertThat(StringCalculator.splitAndSum("1,2:3")).isEqualTo(6);
}
@Test(expected = RuntimeException.class)
public void 음수값() {
StringCalculator.splitAndSum("-1,2:3");
}
}
public class StringCalculator {
public static int splitAndSum(String text) {
[…]
}
private static int[] toInts(String[] values) {
int[] numbers = new int[values.length];
for (int i = 0; i < values.length; i++) {
numbers[i] = toInt(values[i]);
}
return numbers;
}
private static int toInt(String value) {
int number = Integer.parseInt(value);
if (number < 0) {
throw new RuntimeException();
}
return number;
}
}
public class StringCalculator {
[…]
private static int[] toInts(String[] values) {
int[] numbers = new int[values.length];
for (int i = 0; i < values.length; i++) {
numbers[i] = toInt(values[i]);
}
return numbers;
}
private static int toInt(String value) {
int number = Integer.parseInt(value);
if (number < 0) {
throw new RuntimeException();
}
return number;
}
}
모든 원시값과 문자열을 포장한다.
public class Positive {
private int number;
public Positive(String value) {
int number = Integer.parseInt(value);
if (number < 0) {
throw new RuntimeException();
}
this.number = number;
}
}
모든 원시값과 문자열을 포장한다.
public class Positive {
private int number;
public Positive(String value) {
this(Integer.parseInt(value));
}
public Positive(int number) {
if (number < 0) {
throw new RuntimeException();
}
this.number = number;
}
}
모든 원시값과 문자열을 포장한다.
public class StringCalculator {
이전 코드와 같음
private static Positive[] toInts(String[] values) {
Positive[] numbers = new Positive[values.length];
for (int i = 0; i < values.length; i++) {
numbers[i] = new Positive(values[i]);
}
return numbers;
}
private static int sum(Positive[] numbers) {
Positive result = new Positive(0);
for (Positive number : numbers) {
result = result.add(number);
}
return result.getNumber();
}
}
모든 원시값과 문자열을 포장한다.
public class Positive {
private int number;
[…]
public Positive add(Positive other) {
return new Positive(this.number + other.number);
}
public int getNumber() {
return number;
}
}
모든 원시값과 문자열을 포장한다.
클래스 분리 연습을 위해 활용할 수 있는 원칙
• 일급 콜렉션을 쓴다.
• 3개 이상의 인스턴스 변수를 가진 클래스를 쓰지 않는다.
public class Lotto {
private static final int LOTTO_SIZE = 6;
private final Set<LottoNumber> lotto;
private Lotto(Set<LottoNumber> lotto) {
if (lotto.size() != LOTTO_SIZE) {
throw new IllegalArgumentException();
}
this.lotto = lotto;
}
}
일급 콜렉션을 쓴다.
public class WinningLotto {
private final Lotto lotto;
private final LottoNumber no;
public WinningLotto(Lotto lotto, LottoNumber no) {
if (lotto.contains(no)) {
throw new IllegalArgumentException();
}
this.lotto = lotto;
this.no = no;
}
public Rank match(Lotto userLotto) {
int matchCount = lotto.match(userLotto);
boolean matchBonus = userLotto.contains(no);
return Rank.valueOf(matchCount, matchBonus);
}
}
3개 이상의 인스턴스 변수를 가진 클래스를 쓰지 않는다.
4단계 - 장난감 프로젝트
난이도 높이기
점진적으로 요구사항이 복잡한 프로그램을 구현한다.
앞에서 지켰던 기준을 지키면서 프로그래밍 연습을 한다.
TDD, 리팩토링 연습하기 좋은 프로그램 요구사항
• 게임과 같이 요구사항이 명확한 프로그램으로 연습
• 의존관계(모바일 UI, 웹 UI, 데이터베이스, 외부 API와 같은 의존관계)
가 없이 연습
• 약간은 복잡한 로직이 있는 프로그램
연습하기 좋은 예
• 로또(단, UI는 콘솔)
• 사다리 타기(단, UI는 콘솔)
• 볼링 게임 점수판(단, UI는 콘솔)
• 체스 게임(단, UI는 콘솔)
• 지뢰 찾기 게임(단, UI는 콘솔)
5단계 - 의존관계 추가를 통한
난이도 높이기
웹, 모바일 UI, 데이터베이스와 같은 의존관계를 추가
이때 필요한 역량은
테스트하기 쉬운 코드와 테스트하기 어려운 코드를 보는 눈
테스트하기 어려운 코드를 테스트하기 쉬운 코드로 설계하는 감
(sense)
앞 단계 연습을 잘 소화했다면 테스트하기 쉬운 코드와 어려운
코드를 분리하는 역량이 쌓였을 것이다.
한 단계 더 나아간 연습하기
한 단계 더 나아간 연습을 하고 싶다면
• 컴파일 에러를 최소화하면서 리팩토링하기
• ATDD 기반으로 응용 애플리케이션 개발하기
• 레거시 애플리케이션에 테스트 코드 추가해 리팩토링하기
구체적인 연습 목표 찾기
객체지향 생활체조 규칙
• 규칙 1: 한 메서드에 오직 한 단계의 들여쓰기만 한다.
• 규칙 2: else 예약어를 쓰지 않는다.
• 규칙 3: 모든 원시값과 문자열을 포장한다.
• 규칙 4: 한 줄에 점을 하나만 찍는다.
• 규칙 5: 줄여쓰지 않는다(축약 금지).
• 규칙 6: 모든 엔티티를 작게 유지한다.
• 규칙 7: 3개 이상의 인스턴스 변수를 가진 클래스를 쓰지 않는
다.
• 규칙 8: 일급 콜렉션을 쓴다.
• 규칙 9: 게터/세터/프로퍼티를 쓰지 않는다.
메소드 인수 개수
메소드(함수)에서 이상적인 인자 개수는 0개(무항)이다. 다음
은 1개이고, 다음은 2개이다.
3개는 가능한 피하는 편이 좋다.
4개 이상은 특별한 이유가 있어도 사용하면 안된다.
클래스
클래스를 만들 때 첫 번째 규칙은 크기다.
클래스는 작아야 한다.
두 번째 규칙도 크기다.
더 작아야 한다.
마치며
TDD, 리팩토링 연습을 위해 필요한 것은?
• 조급함 대신 마음의 여유
• 나만의 장난감 프로젝트
• 같은 과제를 반복적으로 구현할 수 있는 인내력
가장 필요한 것은 가보지 않는 길에 꾸준히 도전할 수 있는 용기
부록 - 예제를 통해
테스트하기 쉬운 코드와
어려운 코드 분리
Q&A 게시판에서 질문 삭제 요구사항
• 질문 데이터를 완전히 삭제하는 것이 아니라 데이터의 상태를 삭제 상태
(deleted 상태를 false -> true)로 변경한다.
• 로그인 사용자와 질문한 사람이 같은 경우 삭제 가능하다.
• 답변이 없는 경우 삭제가 가능하다.
• 질문자와 답변 글의 모든 답변자 같은 경우 삭제가 가능하다.
• 질문을 삭제할 때 답변 또한 삭제해야 하며, 답변의 삭제 또한 삭제 상태
(deleted)를 변경한다.
• 질문자와 답변자가 다른 경우 답변을 삭제할 수 없다.
Q&A 게시판에서 질문 삭제 요구사항
• 질문 데이터를 완전히 삭제하는 것이 아니라 데이터의 상태를 삭제 상태
(deleted 상태를 false -> true)로 변경한다.
• 로그인 사용자와 질문한 사람이 같은 경우 삭제 가능하다.
• 답변이 없는 경우 삭제가 가능하다.
• 질문자와 답변 글의 모든 답변자 같은 경우 삭제가 가능하다.
• 질문을 삭제할 때 답변 또한 삭제해야 하며, 답변의 삭제 또한 삭제 상태
(deleted)를 변경한다.
• 질문자와 답변자가 다른 경우 답변을 삭제할 수 없다.
Q&A 게시판에서 질문 삭제 요구사항
질문 답변 삭제 여부
로그인 사용자 != 질문자 답변 유무와 무관 X
로그인 사용자 == 질문자 답변이 없음 O
로그인 사용자 == 질문자 로그인 사용자 == 모든 답변자 O
로그인 사용자 == 질문자 로그인 사용자 != 모든 답변자 X
public void deleteQuestion(User loginUser, long questionId) throws
CannotDeleteException {
Question question = questionRepository.findOne(questionId);
if (question == null) {
return;
}
if (!loginUser.equals(question.getWriter())) {
throw new CannotDeleteException("질문을 삭제할 수 없습니다.");
}
List<Answer> answers = question.getAnswers();
boolean canDelete = true;
for (Answer answer : answers) {
if (!loginUser.equals(answer.getWriter())) {
throw new CannotDeleteException("답변을 삭제할 수 없습니다.");
}
}
question.delete();
for (Answer answer : answers) {
answer.delete();
}
}
테스트 가능한 부분을 분리하고,
객체지향 설계 원칙에 따라 역할과 책임을 분리해 구현한다.
public void deleteQuestion(User loginUser, long questionId) throws
CannotDeleteException {
Question question = questionRepository.findOne(questionId);
if (question == null) {
return;
}
question.delete(loginUser);
}
public class Question {
public void delete(User loginUser) throws CannotDeleteException {
if (!isOwner(loginUser)) {
throw new CannotDeleteException("다른 사람의 글은 삭제할 수 없다.");
}
answers.delete(loginUser);
this.deleted = true;
}
}
public class Answers {
private List<Answer> answers = new ArrayList<>();
public void delete(User loginUser) throws CannotDeleteException {
for (Answer answer : answers) {
answer.delete(loginUser);
}
}
}
일급 콜렉션을 쓴다.
단위 테스트를 먼저 만드느냐, 뒤에 만드느냐는 중요하지 않다.
단위 테스트 하기 쉬운 구조로 설계하고, 단위 테스트를 추가하
는 것이 핵심이다.
단위 테스트를 먼저 만드느냐, 뒤에 만드느냐는 중요하지 않다.
단위 테스트 하기 쉬운 구조로 설계하고, 단위 테스트를 추가하
는 것이 핵심이다.
테스트하기 쉬운 코드와 테스트하기 어려운 코드를 보는 눈
테스트하기 어려운 코드를 테스트하기 쉬운 코드로 설계하는 감
(sense)

Weitere ähnliche Inhalte

Was ist angesagt?

닷넷프레임워크에서 Redis 사용하기
닷넷프레임워크에서 Redis 사용하기닷넷프레임워크에서 Redis 사용하기
닷넷프레임워크에서 Redis 사용하기흥배 최
 
고려대학교 컴퓨터학과 특강 - 대학생 때 알았더라면 좋았을 것들
고려대학교 컴퓨터학과 특강 - 대학생 때 알았더라면 좋았을 것들고려대학교 컴퓨터학과 특강 - 대학생 때 알았더라면 좋았을 것들
고려대학교 컴퓨터학과 특강 - 대학생 때 알았더라면 좋았을 것들Chris Ohk
 
スマートフォン向けサービスにおけるサーバサイド設計入門
スマートフォン向けサービスにおけるサーバサイド設計入門スマートフォン向けサービスにおけるサーバサイド設計入門
スマートフォン向けサービスにおけるサーバサイド設計入門Hisashi HATAKEYAMA
 
Rustで楽しむ競技プログラミング
Rustで楽しむ競技プログラミングRustで楽しむ競技プログラミング
Rustで楽しむ競技プログラミングyoshrc
 
자바 직렬화 (Java serialization)
자바 직렬화 (Java serialization)자바 직렬화 (Java serialization)
자바 직렬화 (Java serialization)중선 곽
 
オブジェクト指向できていますか?
オブジェクト指向できていますか?オブジェクト指向できていますか?
オブジェクト指向できていますか?Moriharu Ohzu
 
정수론적 알고리즘 - Sogang ICPC Team, 2020 Winter
정수론적 알고리즘 - Sogang ICPC Team, 2020 Winter정수론적 알고리즘 - Sogang ICPC Team, 2020 Winter
정수론적 알고리즘 - Sogang ICPC Team, 2020 WinterSuhyun Park
 
Domain Driven Design with the F# type System -- F#unctional Londoners 2014
Domain Driven Design with the F# type System -- F#unctional Londoners 2014Domain Driven Design with the F# type System -- F#unctional Londoners 2014
Domain Driven Design with the F# type System -- F#unctional Londoners 2014Scott Wlaschin
 
우아하게 준비하는 테스트와 리팩토링 - PyCon Korea 2018
우아하게 준비하는 테스트와 리팩토링 - PyCon Korea 2018우아하게 준비하는 테스트와 리팩토링 - PyCon Korea 2018
우아하게 준비하는 테스트와 리팩토링 - PyCon Korea 2018Kenneth Ceyer
 
애자일 스크럼과 JIRA
애자일 스크럼과 JIRA 애자일 스크럼과 JIRA
애자일 스크럼과 JIRA Terry Cho
 
ドメイン駆動設計 本格入門
ドメイン駆動設計 本格入門ドメイン駆動設計 本格入門
ドメイン駆動設計 本格入門増田 亨
 
ドメイン駆動設計の捉え方 20150718
ドメイン駆動設計の捉え方 20150718ドメイン駆動設計の捉え方 20150718
ドメイン駆動設計の捉え方 20150718Mao Ohnishi
 
ちいさなオブジェクトでドメインモデルを組み立てる
ちいさなオブジェクトでドメインモデルを組み立てるちいさなオブジェクトでドメインモデルを組み立てる
ちいさなオブジェクトでドメインモデルを組み立てる増田 亨
 
두 번째 startlink.live: 오현석 (algoshipda) - 초심자를 위한 알고리즘 공부 전략
두 번째 startlink.live: 오현석 (algoshipda) - 초심자를 위한 알고리즘 공부 전략두 번째 startlink.live: 오현석 (algoshipda) - 초심자를 위한 알고리즘 공부 전략
두 번째 startlink.live: 오현석 (algoshipda) - 초심자를 위한 알고리즘 공부 전략Startlink
 
関数型プログラミングのデザインパターンひとめぐり
関数型プログラミングのデザインパターンひとめぐり関数型プログラミングのデザインパターンひとめぐり
関数型プログラミングのデザインパターンひとめぐりKazuyuki TAKASE
 
ソースコードの品質向上のための効果的で効率的なコードレビュー
ソースコードの品質向上のための効果的で効率的なコードレビューソースコードの品質向上のための効果的で効率的なコードレビュー
ソースコードの品質向上のための効果的で効率的なコードレビューMoriharu Ohzu
 
객체지향 개념 (쫌 아는체 하기)
객체지향 개념 (쫌 아는체 하기)객체지향 개념 (쫌 아는체 하기)
객체지향 개념 (쫌 아는체 하기)Seung-June Lee
 
イミュータブルデータモデル(世代編)
イミュータブルデータモデル(世代編)イミュータブルデータモデル(世代編)
イミュータブルデータモデル(世代編)Yoshitaka Kawashima
 

Was ist angesagt? (20)

닷넷프레임워크에서 Redis 사용하기
닷넷프레임워크에서 Redis 사용하기닷넷프레임워크에서 Redis 사용하기
닷넷프레임워크에서 Redis 사용하기
 
고려대학교 컴퓨터학과 특강 - 대학생 때 알았더라면 좋았을 것들
고려대학교 컴퓨터학과 특강 - 대학생 때 알았더라면 좋았을 것들고려대학교 컴퓨터학과 특강 - 대학생 때 알았더라면 좋았을 것들
고려대학교 컴퓨터학과 특강 - 대학생 때 알았더라면 좋았을 것들
 
スマートフォン向けサービスにおけるサーバサイド設計入門
スマートフォン向けサービスにおけるサーバサイド設計入門スマートフォン向けサービスにおけるサーバサイド設計入門
スマートフォン向けサービスにおけるサーバサイド設計入門
 
Rustで楽しむ競技プログラミング
Rustで楽しむ競技プログラミングRustで楽しむ競技プログラミング
Rustで楽しむ競技プログラミング
 
자바 직렬화 (Java serialization)
자바 직렬화 (Java serialization)자바 직렬화 (Java serialization)
자바 직렬화 (Java serialization)
 
オブジェクト指向できていますか?
オブジェクト指向できていますか?オブジェクト指向できていますか?
オブジェクト指向できていますか?
 
정수론적 알고리즘 - Sogang ICPC Team, 2020 Winter
정수론적 알고리즘 - Sogang ICPC Team, 2020 Winter정수론적 알고리즘 - Sogang ICPC Team, 2020 Winter
정수론적 알고리즘 - Sogang ICPC Team, 2020 Winter
 
Domain Driven Design with the F# type System -- F#unctional Londoners 2014
Domain Driven Design with the F# type System -- F#unctional Londoners 2014Domain Driven Design with the F# type System -- F#unctional Londoners 2014
Domain Driven Design with the F# type System -- F#unctional Londoners 2014
 
우아하게 준비하는 테스트와 리팩토링 - PyCon Korea 2018
우아하게 준비하는 테스트와 리팩토링 - PyCon Korea 2018우아하게 준비하는 테스트와 리팩토링 - PyCon Korea 2018
우아하게 준비하는 테스트와 리팩토링 - PyCon Korea 2018
 
애자일 스크럼과 JIRA
애자일 스크럼과 JIRA 애자일 스크럼과 JIRA
애자일 스크럼과 JIRA
 
ドメイン駆動設計 本格入門
ドメイン駆動設計 本格入門ドメイン駆動設計 本格入門
ドメイン駆動設計 本格入門
 
HashMapとは?
HashMapとは?HashMapとは?
HashMapとは?
 
ドメイン駆動設計の捉え方 20150718
ドメイン駆動設計の捉え方 20150718ドメイン駆動設計の捉え方 20150718
ドメイン駆動設計の捉え方 20150718
 
ちいさなオブジェクトでドメインモデルを組み立てる
ちいさなオブジェクトでドメインモデルを組み立てるちいさなオブジェクトでドメインモデルを組み立てる
ちいさなオブジェクトでドメインモデルを組み立てる
 
두 번째 startlink.live: 오현석 (algoshipda) - 초심자를 위한 알고리즘 공부 전략
두 번째 startlink.live: 오현석 (algoshipda) - 초심자를 위한 알고리즘 공부 전략두 번째 startlink.live: 오현석 (algoshipda) - 초심자를 위한 알고리즘 공부 전략
두 번째 startlink.live: 오현석 (algoshipda) - 초심자를 위한 알고리즘 공부 전략
 
Java8でRDBMS作ったよ
Java8でRDBMS作ったよJava8でRDBMS作ったよ
Java8でRDBMS作ったよ
 
関数型プログラミングのデザインパターンひとめぐり
関数型プログラミングのデザインパターンひとめぐり関数型プログラミングのデザインパターンひとめぐり
関数型プログラミングのデザインパターンひとめぐり
 
ソースコードの品質向上のための効果的で効率的なコードレビュー
ソースコードの品質向上のための効果的で効率的なコードレビューソースコードの品質向上のための効果的で効率的なコードレビュー
ソースコードの品質向上のための効果的で効率的なコードレビュー
 
객체지향 개념 (쫌 아는체 하기)
객체지향 개념 (쫌 아는체 하기)객체지향 개념 (쫌 아는체 하기)
객체지향 개념 (쫌 아는체 하기)
 
イミュータブルデータモデル(世代編)
イミュータブルデータモデル(世代編)イミュータブルデータモデル(世代編)
イミュータブルデータモデル(世代編)
 

Ähnlich wie [OKKYCON] 박재성 - 의식적인 연습으로 TDD, 리팩토링 연습하기

Droid knights android test @Droid Knights 2018
Droid knights android test @Droid Knights 2018Droid knights android test @Droid Knights 2018
Droid knights android test @Droid Knights 2018KyungHo Jung
 
TDD&Refactoring Day 03: TDD
TDD&Refactoring Day 03: TDDTDD&Refactoring Day 03: TDD
TDD&Refactoring Day 03: TDDSuwon Chae
 
[WELC] 22. I Need to Change a Monster Method and I Can’t Write Tests for It
[WELC] 22. I Need to Change a Monster Method and I Can’t Write Tests for It[WELC] 22. I Need to Change a Monster Method and I Can’t Write Tests for It
[WELC] 22. I Need to Change a Monster Method and I Can’t Write Tests for It종빈 오
 
테스트 가능한 소프트웨어 설계와 TDD작성 패턴 (Testable design and TDD)
테스트 가능한 소프트웨어 설계와 TDD작성 패턴 (Testable design and TDD)테스트 가능한 소프트웨어 설계와 TDD작성 패턴 (Testable design and TDD)
테스트 가능한 소프트웨어 설계와 TDD작성 패턴 (Testable design and TDD)Suwon Chae
 
안드로이드 설계코드 노하우 및 개발방법
안드로이드 설계코드 노하우 및 개발방법안드로이드 설계코드 노하우 및 개발방법
안드로이드 설계코드 노하우 및 개발방법mosaicnet
 
Python vs Java @ PyCon Korea 2017
Python vs Java @ PyCon Korea 2017Python vs Java @ PyCon Korea 2017
Python vs Java @ PyCon Korea 2017Insuk (Chris) Cho
 
08장 객체와 클래스 (기본)
08장 객체와 클래스 (기본)08장 객체와 클래스 (기본)
08장 객체와 클래스 (기본)유석 남
 
Refactoring - Chapter 8.2
Refactoring - Chapter 8.2Refactoring - Chapter 8.2
Refactoring - Chapter 8.2Ji Ung Lee
 
[SOPT] 데이터 구조 및 알고리즘 스터디 - #01 : 개요, 점근적 복잡도, 배열, 연결리스트
[SOPT] 데이터 구조 및 알고리즘 스터디 - #01 : 개요, 점근적 복잡도, 배열, 연결리스트[SOPT] 데이터 구조 및 알고리즘 스터디 - #01 : 개요, 점근적 복잡도, 배열, 연결리스트
[SOPT] 데이터 구조 및 알고리즘 스터디 - #01 : 개요, 점근적 복잡도, 배열, 연결리스트S.O.P.T - Shout Our Passion Together
 
TDD.JUnit.조금더.알기
TDD.JUnit.조금더.알기TDD.JUnit.조금더.알기
TDD.JUnit.조금더.알기Wonchang Song
 
나에 첫번째 자바8 람다식 지앤선
나에 첫번째 자바8 람다식   지앤선나에 첫번째 자바8 람다식   지앤선
나에 첫번째 자바8 람다식 지앤선daewon jeong
 
불어오는 변화의 바람, From c++98 to c++11, 14
불어오는 변화의 바람, From c++98 to c++11, 14 불어오는 변화의 바람, From c++98 to c++11, 14
불어오는 변화의 바람, From c++98 to c++11, 14 명신 김
 
C++ 프로젝트에 단위 테스트 도입하기
C++ 프로젝트에 단위 테스트 도입하기C++ 프로젝트에 단위 테스트 도입하기
C++ 프로젝트에 단위 테스트 도입하기Heo Seungwook
 
120908 레거시코드활용전략 4장5장
120908 레거시코드활용전략 4장5장120908 레거시코드활용전략 4장5장
120908 레거시코드활용전략 4장5장tedypicker
 
Clean code
Clean codeClean code
Clean codebbongcsu
 
읽기 좋은 코드가 좋은 코드다 Part one
읽기 좋은 코드가 좋은 코드다   Part one읽기 좋은 코드가 좋은 코드다   Part one
읽기 좋은 코드가 좋은 코드다 Part oneJi Hun Kim
 

Ähnlich wie [OKKYCON] 박재성 - 의식적인 연습으로 TDD, 리팩토링 연습하기 (20)

Scala
ScalaScala
Scala
 
Droid knights android test @Droid Knights 2018
Droid knights android test @Droid Knights 2018Droid knights android test @Droid Knights 2018
Droid knights android test @Droid Knights 2018
 
TDD&Refactoring Day 03: TDD
TDD&Refactoring Day 03: TDDTDD&Refactoring Day 03: TDD
TDD&Refactoring Day 03: TDD
 
[WELC] 22. I Need to Change a Monster Method and I Can’t Write Tests for It
[WELC] 22. I Need to Change a Monster Method and I Can’t Write Tests for It[WELC] 22. I Need to Change a Monster Method and I Can’t Write Tests for It
[WELC] 22. I Need to Change a Monster Method and I Can’t Write Tests for It
 
테스트 가능한 소프트웨어 설계와 TDD작성 패턴 (Testable design and TDD)
테스트 가능한 소프트웨어 설계와 TDD작성 패턴 (Testable design and TDD)테스트 가능한 소프트웨어 설계와 TDD작성 패턴 (Testable design and TDD)
테스트 가능한 소프트웨어 설계와 TDD작성 패턴 (Testable design and TDD)
 
안드로이드 설계코드 노하우 및 개발방법
안드로이드 설계코드 노하우 및 개발방법안드로이드 설계코드 노하우 및 개발방법
안드로이드 설계코드 노하우 및 개발방법
 
Python vs Java @ PyCon Korea 2017
Python vs Java @ PyCon Korea 2017Python vs Java @ PyCon Korea 2017
Python vs Java @ PyCon Korea 2017
 
08장 객체와 클래스 (기본)
08장 객체와 클래스 (기본)08장 객체와 클래스 (기본)
08장 객체와 클래스 (기본)
 
Refactoring - Chapter 8.2
Refactoring - Chapter 8.2Refactoring - Chapter 8.2
Refactoring - Chapter 8.2
 
[SOPT] 데이터 구조 및 알고리즘 스터디 - #01 : 개요, 점근적 복잡도, 배열, 연결리스트
[SOPT] 데이터 구조 및 알고리즘 스터디 - #01 : 개요, 점근적 복잡도, 배열, 연결리스트[SOPT] 데이터 구조 및 알고리즘 스터디 - #01 : 개요, 점근적 복잡도, 배열, 연결리스트
[SOPT] 데이터 구조 및 알고리즘 스터디 - #01 : 개요, 점근적 복잡도, 배열, 연결리스트
 
TDD.JUnit.조금더.알기
TDD.JUnit.조금더.알기TDD.JUnit.조금더.알기
TDD.JUnit.조금더.알기
 
나에 첫번째 자바8 람다식 지앤선
나에 첫번째 자바8 람다식   지앤선나에 첫번째 자바8 람다식   지앤선
나에 첫번째 자바8 람다식 지앤선
 
(닷넷, C#기초교육)C#선택적인수, 명명된 인수
(닷넷, C#기초교육)C#선택적인수, 명명된 인수(닷넷, C#기초교육)C#선택적인수, 명명된 인수
(닷넷, C#기초교육)C#선택적인수, 명명된 인수
 
불어오는 변화의 바람, From c++98 to c++11, 14
불어오는 변화의 바람, From c++98 to c++11, 14 불어오는 변화의 바람, From c++98 to c++11, 14
불어오는 변화의 바람, From c++98 to c++11, 14
 
Light Tutorial Python
Light Tutorial PythonLight Tutorial Python
Light Tutorial Python
 
C++ 프로젝트에 단위 테스트 도입하기
C++ 프로젝트에 단위 테스트 도입하기C++ 프로젝트에 단위 테스트 도입하기
C++ 프로젝트에 단위 테스트 도입하기
 
파이썬 데이터 분석 (18년)
파이썬 데이터 분석 (18년)파이썬 데이터 분석 (18년)
파이썬 데이터 분석 (18년)
 
120908 레거시코드활용전략 4장5장
120908 레거시코드활용전략 4장5장120908 레거시코드활용전략 4장5장
120908 레거시코드활용전략 4장5장
 
Clean code
Clean codeClean code
Clean code
 
읽기 좋은 코드가 좋은 코드다 Part one
읽기 좋은 코드가 좋은 코드다   Part one읽기 좋은 코드가 좋은 코드다   Part one
읽기 좋은 코드가 좋은 코드다 Part one
 

Mehr von OKKY

[OKKY 세미나] 신용장 예제와 함께 살펴보는 하이퍼레저 패브릭과 컴포저 - 엔터프라이즈 블록체인
[OKKY 세미나] 신용장 예제와 함께 살펴보는 하이퍼레저 패브릭과 컴포저 - 엔터프라이즈 블록체인[OKKY 세미나] 신용장 예제와 함께 살펴보는 하이퍼레저 패브릭과 컴포저 - 엔터프라이즈 블록체인
[OKKY 세미나] 신용장 예제와 함께 살펴보는 하이퍼레저 패브릭과 컴포저 - 엔터프라이즈 블록체인OKKY
 
OKKY Jobs Guideline v.2.5
OKKY Jobs Guideline v.2.5OKKY Jobs Guideline v.2.5
OKKY Jobs Guideline v.2.5OKKY
 
[OKKY 세미나] 정진욱 - 테스트하기 쉬운 코드로 개발하기
[OKKY 세미나] 정진욱 - 테스트하기 쉬운 코드로 개발하기[OKKY 세미나] 정진욱 - 테스트하기 쉬운 코드로 개발하기
[OKKY 세미나] 정진욱 - 테스트하기 쉬운 코드로 개발하기OKKY
 
[OKKY 세미나] 신현묵 - SI / 대기업에서 스타트업으로 이직하기
[OKKY 세미나] 신현묵 - SI / 대기업에서 스타트업으로 이직하기[OKKY 세미나] 신현묵 - SI / 대기업에서 스타트업으로 이직하기
[OKKY 세미나] 신현묵 - SI / 대기업에서 스타트업으로 이직하기OKKY
 
개발자 이직, 몇 년까지 괜찮을까 by eBrain
개발자 이직, 몇 년까지 괜찮을까 by eBrain개발자 이직, 몇 년까지 괜찮을까 by eBrain
개발자 이직, 몇 년까지 괜찮을까 by eBrainOKKY
 
OKKY 11월 정기모임 - 화성에서 온 개발자 금성에서 온 비개발자 by 신대철 베이사이드 대표
OKKY 11월 정기모임 - 화성에서 온 개발자 금성에서 온 비개발자 by 신대철 베이사이드 대표OKKY 11월 정기모임 - 화성에서 온 개발자 금성에서 온 비개발자 by 신대철 베이사이드 대표
OKKY 11월 정기모임 - 화성에서 온 개발자 금성에서 온 비개발자 by 신대철 베이사이드 대표OKKY
 

Mehr von OKKY (6)

[OKKY 세미나] 신용장 예제와 함께 살펴보는 하이퍼레저 패브릭과 컴포저 - 엔터프라이즈 블록체인
[OKKY 세미나] 신용장 예제와 함께 살펴보는 하이퍼레저 패브릭과 컴포저 - 엔터프라이즈 블록체인[OKKY 세미나] 신용장 예제와 함께 살펴보는 하이퍼레저 패브릭과 컴포저 - 엔터프라이즈 블록체인
[OKKY 세미나] 신용장 예제와 함께 살펴보는 하이퍼레저 패브릭과 컴포저 - 엔터프라이즈 블록체인
 
OKKY Jobs Guideline v.2.5
OKKY Jobs Guideline v.2.5OKKY Jobs Guideline v.2.5
OKKY Jobs Guideline v.2.5
 
[OKKY 세미나] 정진욱 - 테스트하기 쉬운 코드로 개발하기
[OKKY 세미나] 정진욱 - 테스트하기 쉬운 코드로 개발하기[OKKY 세미나] 정진욱 - 테스트하기 쉬운 코드로 개발하기
[OKKY 세미나] 정진욱 - 테스트하기 쉬운 코드로 개발하기
 
[OKKY 세미나] 신현묵 - SI / 대기업에서 스타트업으로 이직하기
[OKKY 세미나] 신현묵 - SI / 대기업에서 스타트업으로 이직하기[OKKY 세미나] 신현묵 - SI / 대기업에서 스타트업으로 이직하기
[OKKY 세미나] 신현묵 - SI / 대기업에서 스타트업으로 이직하기
 
개발자 이직, 몇 년까지 괜찮을까 by eBrain
개발자 이직, 몇 년까지 괜찮을까 by eBrain개발자 이직, 몇 년까지 괜찮을까 by eBrain
개발자 이직, 몇 년까지 괜찮을까 by eBrain
 
OKKY 11월 정기모임 - 화성에서 온 개발자 금성에서 온 비개발자 by 신대철 베이사이드 대표
OKKY 11월 정기모임 - 화성에서 온 개발자 금성에서 온 비개발자 by 신대철 베이사이드 대표OKKY 11월 정기모임 - 화성에서 온 개발자 금성에서 온 비개발자 by 신대철 베이사이드 대표
OKKY 11월 정기모임 - 화성에서 온 개발자 금성에서 온 비개발자 by 신대철 베이사이드 대표
 

[OKKYCON] 박재성 - 의식적인 연습으로 TDD, 리팩토링 연습하기

  • 1. 의식적인 연습으로 TDD, 리팩토링 연습하기 박재성(자바지기)
  • 4. 이 발표에서는 TDD와 리팩토링을 왜 해야 하는지 알고 있다. 는 가정 하에 진행한다.
  • 5. 이 발표에서는 TDD와 리팩토링을 비슷한 비중으로 다룬다. 어쩌면 TDD < 리팩토링 일지도 모른다.
  • 6. 개발 현장을 떠나 교육자로 산지 6년. 이 발표의 상당 수가 구라일 수 있으니 주의해야 한다.
  • 7. 발표 내용 • 의식적인 연습이란? • 의식적인 연습으로 TDD, 리팩토링 연습 과정
  • 9. TDD, 리팩토링을 잘 하려면… 연습, 연습, 연습 …
  • 10. 무조건 연습을 많이 한다고 잘할 수 있을까?
  • 11. TDD, 리팩토링과 관련해 5, 6년을 도전한 후에야 테스트하기 쉬운 코드와 테스트하기 어려운 코드를 보는 눈 테스트하기 어려운 코드를 테스트하기 쉬운 코드로 설계하는 감 (sense)
  • 12. TDD, 리팩토링. 멋져 보인다. 하지만 생각만큼 쉽게 연습할 수 있는 녀석이 아니다.
  • 13. 교육자로서 최근의 고민 좀 더 효과적으로 연습할 수 있는 방법은 없을까?
  • 14. 아마추어와 프로의 결정적 차이 목적 의식 있는 연습 에 얼마나 많은 시간을 투자했느냐?
  • 15. 의식적인 연습의 7가지 원칙 • 첫째, 효과적인 훈련 기법이 수립되어 있는 기술 연마 • 둘째, 개인의 컴포트 존을 벗어난 지점에서 진행, 자신의 현재 능력 을 살짝 넘어가는 작업을 지속적으로 시도 • 셋째, 명확하고 구체적인 목표를 가지고 진행
  • 16. 의식적인 연습의 7가지 원칙 • 넷째, 신중하고 계획적이다. 즉, 개인이 온전히 집중하고 '의식적'으로 행동할 것을 요구 • 다섯째, 피드백과 피드백에 따른 행동 변경을 수반 • 여섯째, 효과적인 심적 표상을 만들어내는 한편으로 심적 표상에 의존 • 일곱째, 기존에 습득한 기술의 특정 부분을 집중적으로 개선함으로 써 발전시키고, 수정하는 과정을 수반
  • 19. 1단계 - 단위 테스트 연습
  • 20. 내가 사용하는 API 사용법을 익히기 위한 학습 테스트에서 시 작 • 자바 String 클래스의 다양한 메소드(함수) 사용법 • 자바 ArrayList에 데이터를 추가, 수정, 삭제하는 방법
  • 21. import static org.assertj.core.api.Assertions.assertThat; public class StringTest { @Test public void split() { String[] values = "1".split(","); assertThat(values).contains("1"); values = "1,2".saplit(","); assertThat(values).containsExactly("1", "2"); } @Test public void substring() { String input = "(1,2)"; String result = input.substring(1, input.length() - 1); assertThat(result).isEqualTo("1,2"); } }
  • 22. import static org.assertj.core.api.Assertions.assertThat; public class CollectionTest { @Test public void arrayList() { ArrayList<String> values = new ArrayList<>(); values.add("first"); values.add("second"); assertThat(values.add("third")).isTrue(); assertThat(values.size()).isEqualTo(3); assertThat(values.get(0)).isEqualTo("first"); assertThat(values.contains("first")).isTrue(); assertThat(values.remove(0)).isEqualTo("first"); assertThat(values.size()).isEqualTo(2); } }
  • 23. 연습 효과 • 단위테스트 방법을 학습할 수 있다. • 단위테스트 도구(xUnit)의 사용법을 익힐 수 있다. • 사용하는 API에 대한 학습 효과가 있다.
  • 24. 내가 구현하는 메소드(함수) 중 Input과 Output이 명확한 클래스 메소드(보통 Util 성격의 메소 드) 에 대한 단위 테스트 연습
  • 25. 알고리즘을 학습한다면 알고리즘 구현에 대한 검증을 단위 테스트로 한다. 알고리즘은 Input, Output이 명확하기 때문에 연습하기 좋다.
  • 26. 2단계 - TDD 연습
  • 27. 지켜야 할 원칙 1 회사 프로젝트에 연습하지 말고 장난감 프로젝트를 활용해 연습하자.
  • 28. 지켜야 할 원칙 2 웹, 모바일 UI나 DB에 의존관계를 가지지 않는 요구사항으로 연습한다.
  • 29. 문자열 덧셈 계산기 요구사항 쉼표(,) 또는 콜론(:)을 구분자로 가지는 문자열을 전달하는 경우 구분자를 기준으로 분리한 각 숫자의 합을 반환
  • 30. 문자열 덧셈 계산기 요구사항 쉼표(,) 또는 콜론(:)을 구분자로 가지는 문자열을 전달하는 경우 구분자를 기준으로 분리한 각 숫자의 합을 반환 입력(input) 출력(output) null 또는 “” 0 “1” 1 “1,2” 3 “1,2:3” 6
  • 32. public class StringCalculatorTest { @Test public void null_또는_빈값() { assertThat(StringCalculator.splitAndSum(null)).isEqualTo(0); assertThat(StringCalculator.splitAndSum("")).isEqualTo(0); } @Test public void 값_하나() { assertThat(StringCalculator.splitAndSum("1")).isEqualTo(1); } @Test public void 쉼표_구분자() { assertThat(StringCalculator.splitAndSum("1,2")).isEqualTo(3); } @Test public void 쉼표_콜론_구분자() { assertThat(StringCalculator.splitAndSum("1,2:3")).isEqualTo(6); } }
  • 33. public class StringCalculator { public static int splitAndSum(String text) { int result = 0; if (text == null || text.isEmpty()) { result = 0; } else { String[] values = text.split(",|:"); for (String value : values) { result += Integer.parseInt(value); } } return result; } }
  • 34. 어려운 문제를 해결하는 것이 목적이 아니라 TDD 연습이 목적 난이도가 낮거나 자신에게 익숙한 문제로 시작하는 것을 추천
  • 36. 리팩토링 연습 – 메소드 분리
  • 37. public class StringCalculator { public static int splitAndSum(String text) { int result = 0; if (text == null || text.isEmpty()) { result = 0; } else { String[] values = text.split(",|:"); for (String value : values) { result += Integer.parseInt(value); } } return result; } } 테스트 코드는 변경하지 말고 테스트 대상 코드(프로덕션 코드)를 개선하는 연습을 한다.
  • 39. 막막함 멘붕 의식적인 연습 7가지 원칙 셋째, 명확하고 구체적인 목표를 가지고 진행 다섯째, 피드백과 피드백에 따른 행동 변경을 수반
  • 41. public class StringCalculator { public static int splitAndSum(String text) { int result = 0; if (text == null || text.isEmpty()) { result = 0; } else { String[] values = text.split(",|:"); for (String value : values) { result += Integer.parseInt(value); } } return result; } } 한 메서드에 오직 한 단계의 들여쓰기(indent)만 한다.
  • 42. public class StringCalculator { public static int splitAndSum(String text) { int result = 0; if (text == null || text.isEmpty()) { result = 0; } else { String[] values = text.split(",|:"); for (String value : values) { result += Integer.parseInt(value); } } return result; } } 한 메서드에 오직 한 단계의 들여쓰기(indent)만 한다. 들여쓰기가 2인 곳
  • 43. public class StringCalculator { public static int splitAndSum(String text) { int result = 0; if (text == null || text.isEmpty()) { result = 0; } else { String[] values = text.split(",|:"); result = sum(values); } return result; } private static int sum(String[] values) { int result = 0; for (String value : values) { result += Integer.parseInt(value); } return result; } }
  • 44. public class StringCalculator { public static int splitAndSum(String text) { int result = 0; if (text == null || text.isEmpty()) { result = 0; } else { String[] values = text.split(",|:"); result = sum(values); } return result; } private static int sum(String[] values) { int result = 0; for (String value : values) { result += Integer.parseInt(value); } return result; } } else 예약어를 쓰지 않는다.
  • 45. public class StringCalculator { public static int splitAndSum(String text) { if (text == null || text.isEmpty()) { return 0; } String[] values = text.split(",|:"); return sum(values); } private static int sum(String[] values) { int result = 0; for (String value : values) { result += Integer.parseInt(value); } return result; } } else 예약어를 쓰지 않는다.
  • 46. public class StringCalculator { public static int splitAndSum(String text) { if (text == null || text.isEmpty()) { return 0; } String[] values = text.split(",|:"); return sum(values); } private static int sum(String[] values) { int result = 0; for (String value : values) { result += Integer.parseInt(value); } return result; } } 메소드가 한 가지 일만 하도록 구현하기
  • 47. public class StringCalculator { […] private static int[] toInts(String[] values) { int[] numbers = new int[values.length]; for (int i = 0; i < values.length; i++) { numbers[i] = Integer.parseInt(values[i]); } return numbers; } private static int sum(int[] numbers) { int result = 0; for (int number : numbers) { result += number; } return result; } } 메소드가 한 가지 일만 하도록 구현하기
  • 48. public class StringCalculator { public static int splitAndSum(String text) { if (text == null || text.isEmpty()) { return 0; } String[] values = text.split(",|:"); int[] numbers = toInts(values); return sum(numbers); } private static int[] toInts(String[] values) { […] } private static int sum(int[] numbers) { […] } } 메소드가 한 가지 일만 하도록 구현하기
  • 49. public class StringCalculator { public static int splitAndSum(String text) { if (text == null || text.isEmpty()) { return 0; } String[] values = text.split(",|:"); int[] numbers = toInts(values); return sum(numbers); } private static int[] toInts(String[] values) { […] } private static int sum(int[] numbers) { […] } } 로컬 변수가 정말 필요한가?
  • 50. public class StringCalculator { public static int splitAndSum(String text) { if (text == null || text.isEmpty()) { return 0; } return sum(toInts(text.split(",|:"))); } private static int[] toInts(String[] values) { […] } private static int sum(int[] numbers) { […] } } 로컬 변수가 정말 필요한가?
  • 51. compose method 패턴 적용 메소드(함수)의 의도가 잘 드러나도록 동등한 수준의 작업을 하는 여러 단계로 나눈다.
  • 52. public class StringCalculator { public static int add(String text) { if (isBlank(text)) { return 0; } return sum(toInts(split(text))); } private static boolean isBlank(String text) { } private static String[] split(String text) { } private static int[] toInts(String[] values) { } private static int sum(int[] numbers) { } } compose method 패턴 적용
  • 53. public class StringCalculator { public static int add(String text) { int result = 0; if (text == null || text.isEmpty()) { result = 0; } else { String[] values = text.split(",|:"); for (String value : values) { result += Integer.parseInt(value); } } return result; } } public class StringCalculator { public static int add(String text) { if (isBlank(text)) { return 0; } return sum(toInts(split(text))); } private static boolean isBlank(String text) { } private static String[] split(String text) { } private static int[] toInts(String[] values) { } private static int sum(int[] numbers) { } } add() 메소드를 처음 읽는 사람에게 어느 코드가 더 읽기 좋을 까?
  • 54. 한 번에 모든 원칙을 지키면서 리팩토링하려고 연습하지 마라. 한 번에 한 가지 명확하고 구체적인 목표를 가지고 연습하라.
  • 55. 연습은 극단적인 방법으로 연습하는 것도 좋다. 예를 들어 한 메소드의 라인 수 제한을 15라인 -> 10라인으로 줄여가면 서 연습하는 것도 좋은 방법이다.
  • 56. 리팩토링 연습 – 클래스 분리
  • 57. 다시 문자열 덧셈 계산기 요구사항 쉼표(,) 또는 콜론(:)을 구분자로 가지는 문자열을 전달하는 경우 구분자를 기준으로 분리 한 각 숫자의 합을 반환 문자열 계산기에 숫자 이외의 값 또는 음수를 전달하는 경우 RuntimeException 예외를 throw한다. 입력(input) 출력(output) null 또는 “” 0 “1” 1 “1,2” 3 “1,2:3” 6 “-1,2:3” RuntimeException
  • 58. public class StringCalculatorTest { […] @Test public void 쉼표_구분자() { assertThat(StringCalculator.splitAndSum("1,2")).isEqualTo(3); } @Test public void 쉼표_콜론_구분자() { assertThat(StringCalculator.splitAndSum("1,2:3")).isEqualTo(6); } @Test(expected = RuntimeException.class) public void 음수값() { StringCalculator.splitAndSum("-1,2:3"); } }
  • 59. public class StringCalculator { public static int splitAndSum(String text) { […] } private static int[] toInts(String[] values) { int[] numbers = new int[values.length]; for (int i = 0; i < values.length; i++) { numbers[i] = toInt(values[i]); } return numbers; } private static int toInt(String value) { int number = Integer.parseInt(value); if (number < 0) { throw new RuntimeException(); } return number; } }
  • 60. public class StringCalculator { […] private static int[] toInts(String[] values) { int[] numbers = new int[values.length]; for (int i = 0; i < values.length; i++) { numbers[i] = toInt(values[i]); } return numbers; } private static int toInt(String value) { int number = Integer.parseInt(value); if (number < 0) { throw new RuntimeException(); } return number; } } 모든 원시값과 문자열을 포장한다.
  • 61. public class Positive { private int number; public Positive(String value) { int number = Integer.parseInt(value); if (number < 0) { throw new RuntimeException(); } this.number = number; } } 모든 원시값과 문자열을 포장한다.
  • 62. public class Positive { private int number; public Positive(String value) { this(Integer.parseInt(value)); } public Positive(int number) { if (number < 0) { throw new RuntimeException(); } this.number = number; } } 모든 원시값과 문자열을 포장한다.
  • 63. public class StringCalculator { 이전 코드와 같음 private static Positive[] toInts(String[] values) { Positive[] numbers = new Positive[values.length]; for (int i = 0; i < values.length; i++) { numbers[i] = new Positive(values[i]); } return numbers; } private static int sum(Positive[] numbers) { Positive result = new Positive(0); for (Positive number : numbers) { result = result.add(number); } return result.getNumber(); } } 모든 원시값과 문자열을 포장한다.
  • 64. public class Positive { private int number; […] public Positive add(Positive other) { return new Positive(this.number + other.number); } public int getNumber() { return number; } } 모든 원시값과 문자열을 포장한다.
  • 65. 클래스 분리 연습을 위해 활용할 수 있는 원칙 • 일급 콜렉션을 쓴다. • 3개 이상의 인스턴스 변수를 가진 클래스를 쓰지 않는다.
  • 66. public class Lotto { private static final int LOTTO_SIZE = 6; private final Set<LottoNumber> lotto; private Lotto(Set<LottoNumber> lotto) { if (lotto.size() != LOTTO_SIZE) { throw new IllegalArgumentException(); } this.lotto = lotto; } } 일급 콜렉션을 쓴다.
  • 67. public class WinningLotto { private final Lotto lotto; private final LottoNumber no; public WinningLotto(Lotto lotto, LottoNumber no) { if (lotto.contains(no)) { throw new IllegalArgumentException(); } this.lotto = lotto; this.no = no; } public Rank match(Lotto userLotto) { int matchCount = lotto.match(userLotto); boolean matchBonus = userLotto.contains(no); return Rank.valueOf(matchCount, matchBonus); } } 3개 이상의 인스턴스 변수를 가진 클래스를 쓰지 않는다.
  • 68. 4단계 - 장난감 프로젝트 난이도 높이기
  • 69. 점진적으로 요구사항이 복잡한 프로그램을 구현한다. 앞에서 지켰던 기준을 지키면서 프로그래밍 연습을 한다.
  • 70. TDD, 리팩토링 연습하기 좋은 프로그램 요구사항 • 게임과 같이 요구사항이 명확한 프로그램으로 연습 • 의존관계(모바일 UI, 웹 UI, 데이터베이스, 외부 API와 같은 의존관계) 가 없이 연습 • 약간은 복잡한 로직이 있는 프로그램
  • 71. 연습하기 좋은 예 • 로또(단, UI는 콘솔) • 사다리 타기(단, UI는 콘솔) • 볼링 게임 점수판(단, UI는 콘솔) • 체스 게임(단, UI는 콘솔) • 지뢰 찾기 게임(단, UI는 콘솔)
  • 72. 5단계 - 의존관계 추가를 통한 난이도 높이기
  • 73. 웹, 모바일 UI, 데이터베이스와 같은 의존관계를 추가
  • 74. 이때 필요한 역량은 테스트하기 쉬운 코드와 테스트하기 어려운 코드를 보는 눈 테스트하기 어려운 코드를 테스트하기 쉬운 코드로 설계하는 감 (sense)
  • 75. 앞 단계 연습을 잘 소화했다면 테스트하기 쉬운 코드와 어려운 코드를 분리하는 역량이 쌓였을 것이다.
  • 76. 한 단계 더 나아간 연습하기
  • 77. 한 단계 더 나아간 연습을 하고 싶다면 • 컴파일 에러를 최소화하면서 리팩토링하기 • ATDD 기반으로 응용 애플리케이션 개발하기 • 레거시 애플리케이션에 테스트 코드 추가해 리팩토링하기
  • 79. 객체지향 생활체조 규칙 • 규칙 1: 한 메서드에 오직 한 단계의 들여쓰기만 한다. • 규칙 2: else 예약어를 쓰지 않는다. • 규칙 3: 모든 원시값과 문자열을 포장한다. • 규칙 4: 한 줄에 점을 하나만 찍는다. • 규칙 5: 줄여쓰지 않는다(축약 금지). • 규칙 6: 모든 엔티티를 작게 유지한다. • 규칙 7: 3개 이상의 인스턴스 변수를 가진 클래스를 쓰지 않는 다. • 규칙 8: 일급 콜렉션을 쓴다. • 규칙 9: 게터/세터/프로퍼티를 쓰지 않는다.
  • 80. 메소드 인수 개수 메소드(함수)에서 이상적인 인자 개수는 0개(무항)이다. 다음 은 1개이고, 다음은 2개이다. 3개는 가능한 피하는 편이 좋다. 4개 이상은 특별한 이유가 있어도 사용하면 안된다. 클래스 클래스를 만들 때 첫 번째 규칙은 크기다. 클래스는 작아야 한다. 두 번째 규칙도 크기다. 더 작아야 한다.
  • 82. TDD, 리팩토링 연습을 위해 필요한 것은? • 조급함 대신 마음의 여유 • 나만의 장난감 프로젝트 • 같은 과제를 반복적으로 구현할 수 있는 인내력
  • 83. 가장 필요한 것은 가보지 않는 길에 꾸준히 도전할 수 있는 용기
  • 84. 부록 - 예제를 통해 테스트하기 쉬운 코드와 어려운 코드 분리
  • 85. Q&A 게시판에서 질문 삭제 요구사항 • 질문 데이터를 완전히 삭제하는 것이 아니라 데이터의 상태를 삭제 상태 (deleted 상태를 false -> true)로 변경한다. • 로그인 사용자와 질문한 사람이 같은 경우 삭제 가능하다. • 답변이 없는 경우 삭제가 가능하다. • 질문자와 답변 글의 모든 답변자 같은 경우 삭제가 가능하다. • 질문을 삭제할 때 답변 또한 삭제해야 하며, 답변의 삭제 또한 삭제 상태 (deleted)를 변경한다. • 질문자와 답변자가 다른 경우 답변을 삭제할 수 없다.
  • 86. Q&A 게시판에서 질문 삭제 요구사항 • 질문 데이터를 완전히 삭제하는 것이 아니라 데이터의 상태를 삭제 상태 (deleted 상태를 false -> true)로 변경한다. • 로그인 사용자와 질문한 사람이 같은 경우 삭제 가능하다. • 답변이 없는 경우 삭제가 가능하다. • 질문자와 답변 글의 모든 답변자 같은 경우 삭제가 가능하다. • 질문을 삭제할 때 답변 또한 삭제해야 하며, 답변의 삭제 또한 삭제 상태 (deleted)를 변경한다. • 질문자와 답변자가 다른 경우 답변을 삭제할 수 없다.
  • 87. Q&A 게시판에서 질문 삭제 요구사항 질문 답변 삭제 여부 로그인 사용자 != 질문자 답변 유무와 무관 X 로그인 사용자 == 질문자 답변이 없음 O 로그인 사용자 == 질문자 로그인 사용자 == 모든 답변자 O 로그인 사용자 == 질문자 로그인 사용자 != 모든 답변자 X
  • 88. public void deleteQuestion(User loginUser, long questionId) throws CannotDeleteException { Question question = questionRepository.findOne(questionId); if (question == null) { return; } if (!loginUser.equals(question.getWriter())) { throw new CannotDeleteException("질문을 삭제할 수 없습니다."); } List<Answer> answers = question.getAnswers(); boolean canDelete = true; for (Answer answer : answers) { if (!loginUser.equals(answer.getWriter())) { throw new CannotDeleteException("답변을 삭제할 수 없습니다."); } } question.delete(); for (Answer answer : answers) { answer.delete(); } }
  • 89. 테스트 가능한 부분을 분리하고, 객체지향 설계 원칙에 따라 역할과 책임을 분리해 구현한다.
  • 90. public void deleteQuestion(User loginUser, long questionId) throws CannotDeleteException { Question question = questionRepository.findOne(questionId); if (question == null) { return; } question.delete(loginUser); }
  • 91. public class Question { public void delete(User loginUser) throws CannotDeleteException { if (!isOwner(loginUser)) { throw new CannotDeleteException("다른 사람의 글은 삭제할 수 없다."); } answers.delete(loginUser); this.deleted = true; } }
  • 92. public class Answers { private List<Answer> answers = new ArrayList<>(); public void delete(User loginUser) throws CannotDeleteException { for (Answer answer : answers) { answer.delete(loginUser); } } } 일급 콜렉션을 쓴다.
  • 93. 단위 테스트를 먼저 만드느냐, 뒤에 만드느냐는 중요하지 않다. 단위 테스트 하기 쉬운 구조로 설계하고, 단위 테스트를 추가하 는 것이 핵심이다.
  • 94. 단위 테스트를 먼저 만드느냐, 뒤에 만드느냐는 중요하지 않다. 단위 테스트 하기 쉬운 구조로 설계하고, 단위 테스트를 추가하 는 것이 핵심이다. 테스트하기 쉬운 코드와 테스트하기 어려운 코드를 보는 눈 테스트하기 어려운 코드를 테스트하기 쉬운 코드로 설계하는 감 (sense)