해결 시도 - yonghwankim-dev/be-java-ladder-max GitHub Wiki
-
결과로 받을 문자열 타입의 2차원 배열(board)의 가로(column)와 세로(row) 길이를 어떻게 계산할지 고민하였습니다.
- board의 가로 = 사람수 * 2 - 1
- board의 세로 = 입력받은 사다리의 최대 높이
-
board의 어느 위치에 사다리바("|")를 저장할지 고민하였습니다.
- board의 짝수열(0번째 열 포함)에 사다리바를 저장하였습니다.
-
board의 어느 위치에 사다리 라인("-")을 저장할지 고민하였습니다.
- board의 홀수열에 사다리바를 저장하였습니다.
-
단위 테스트시 Random 모듈을 사용하였는데 Random 모듈의 nextBoolean() 메서드 사용시 어떻게 모킹하는지 고민하였습니다.
- Mockito 모듈을 이용하여 모킹하였습니다.
testImplementation 'org.mockito:mockito-core:5.1.1'
class LadderTest{
@Test
@DisplayName("사람 2명과 사다리 높이2가 주어질 때 랜덤한 사다리 생성하는지 테스트")
public void generate_testcase1(){
//given
int n = 2;
int m = 2;
Ladder ladder = new Ladder(n, m);
//mocking
Random mockRandom = mock(Random.class);
when(mockRandom.nextBoolean()).thenReturn(true, false);
setRandomCreateLine(ladder, mockRandom);
//when
String[][] actual = ladder.generate();
//then
String[][] expected = {{"|","-","|"},
{"|"," ","|"}};
Assertions.assertThat(actual).isEqualTo(expected);
}
}-
사용자 입력시 부적절한 범위의 숫자나 문자 입력시 수행할 행동을 고민하였습니다.
- 커스텀 예외를 생성하여 경고 메시지를 출력하고 다시 입력하게 수행하였습니다.
-
기존 랜덤한 사다리 생성시 문자열 타입의 2차원 배열을 반환할 것을 출력을 위한 하나의 문자열로 변환하여 반환할지 고민하였습니다.
- 하나의 메서드에서 랜덤하게 사다리를 생성하고 변환한다면 메서드가 2가지 이상 일을 한다고 생각하여 LadderConverter라는 객체를 따로 분리하여 변환하도록 수행하였습니다.

- 사용자로부터 인원수 또는 최대 사다리 높이를 입력 받는 기능에서 어떻게 하면 기존 while문과 try문으로 depth=2인것을 depth1로 줄이도록 할 수 있을까 고민하였습니다.
- 기존 try문이 있는 부분을 메서드(readNumberOfPeopleTextAndToInt)로 추출하였습니다.
public class ConsoleInput {
// ...
public int inputPerson() {
String text;
while (true) {
try {
ConsoleOutput.printInputPersonIntro();
text = br.readLine();
if (!validator.validatePerson(text)) {
throw new InvalidPersonNumber();
}
break;
} catch (InvalidPersonNumber e) {
ConsoleOutput.printInvalidInputNumber(e.getMessage());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return Integer.parseInt(text);
}
}public class ConsoleInput {
// ...
public int readNumberOfPeople() {
int numberOfPeople = 0;
while (numberOfPeople == 0) {
ConsoleOutput.printInputPersonIntro();
numberOfPeople = readNumberOfPeopleTextAndToInt();
}
return numberOfPeople;
}
private int readNumberOfPeopleTextAndToInt() {
try {
String text = br.readLine();
text = validator.validateNumberOfPeople(text);
return toInt(text);
} catch (InvalidPersonNumber e) {
ConsoleOutput.printInvalidInputNumber(e.getMessage());
} catch (IOException e) {
throw new RuntimeException(e);
}
return 0;
}
}- 랜덤 사다리를 생성하는 부분에서 이중 반복문 dpeth2를 depth1로 어떻게 하면 줄일 수 있을까 고민하였습니다.
- 메서드를 따로 추출하여 각각의 행(2차원 배열의 랜덤 사다리 행)을 생성하여 반환하도록 개선하였습니다.
- 어떻게 하면 LadderPartGenerator와 Ladder 객체간의 협력을 객체지향적으로 구현할까 고민하였습니다.
- 기존 LadderPartGenerator 객체 생성시 Ladder 객체를 주입하는 것에서 LadderPartGenerator.generate(ladder)와 같이 메서드 호출시 매개변수로 전달하는 방향으로 개선하였습니다.
public class LadderPartGenerator {
// ...
public String[][] generate(Ladder ladder) {
String[][] board = ladder.createEmptyLadderBoard();
for (int i = 0; i < board.length; i++) {
board[i] = createRandomLadderByLadder(ladder);
}
return board;
}
private String[] createRandomLadderByLadder(Ladder ladder) {
String[] ladderColumns = ladder.createEmptyLadderColumns();
for (int col = 0; col < ladderColumns.length; col += 2) {
ladderColumns[col] = generateBar();
}
for (int col = 1; col < ladderColumns.length; col += 2) {
ladderColumns[col] = generateBridge();
}
return ladderColumns;
}
}- 어떻게 하면 사다리의 한 라인에서 브릿지가 2개 이상 연속적으로 오지 못하게 할지 고민하였습니다.
- 특정한 열 A를 입력받았을때 A가 3열(0번째 열 포함)이상이고 A-2열이 브릿지가 있는지 검사하는 메서드를 구현하였습니다.
public class LadderPartGenerator {
//...
private boolean existBridgeOnLeft(List<String> partLine, int col) {
return col >= 3 && partLine.get(col - 2).equals(BRIDGE);
}
}다음 코드는 한 라인에 브릿지가 2개 이상 연속으로 오는지 테스트하는 단위테스트입니다.
class LadderPartGeneratorTest {
@Test
@DisplayName("사다리의 한 라인에 브리지가 2개 이상 연속적으로 나오지 않는지 테스트")
public void generateLadderPart_testcase3() {
//given
List<String> namesOfPeople = new ArrayList<>(List.of("pobi", "honux", "crong"));
m = 1;
random = new Random();
ladder = new Ladder(namesOfPeople, m);
//mocking
Random mockRandom = mock(random.getClass());
when(mockRandom.nextBoolean()).thenReturn(true, true);
generator = new LadderPartGenerator(mockRandom);
//when
List<List<String>> actual = generator.generateLadderPart(ladder);
//then
List<List<String>> expected = new ArrayList<>();
expected.add(new ArrayList<>(List.of("|", "-----", "|", " ", "|")));
Assertions.assertThat(actual).isEqualTo(expected);
}
}- 위 코드 중 모킹한 Random 객체를 생성자로 주입하는 부분이 있습니다.
- random 객체는 nextBoolean 메서드 호출할 때 [true, true] 순서로 반환할 것입니다.
- 하지만 연속적으로 브릿지가 될 수 없기 때문에 3열은 공백(" ")이 저장됩니다.
- 사람인원수, 최대 사다리 높이와 같은 정보들을 어떤 객체가 가지게 할것인지 고민하였습니다.
- 최대 사다리 높이를 의미하는 maximumHeight 정수는 기존 설계에서는 Ladder 객체가 가지고 있었지만 랜덤 사다리를 생성하는데 LadderGenerator에 주는 것이 더 적합하다고 판단하였습니다.
- 단위 테스트를 실행할때 Mocking된 Random 객체가 조건문의 뒤쪽에서 nextBoolean() 메서드를 수행하여 브릿지를 생성할지 말지를 판단하였는데 내가 생각했던 사다리 모양과 다르게 생성되는 문제가 발생하였습니다.
- 문제의 원인으로는 랜덤한 브릿지를 생성하는 메서드 수행시 조건문 안에 2개의 조건문을 명시하여 두 조건문이 참인 경우에만 브릿지를 반환하도록 설계하였습니다. 그러나 앞쪽의 조건문이 false가 되면 전체가 false가 되어 뒤쪽 조건문에 있는 random.nextBoolean() 메서드가 수행되지 않아 제가 생각했던 사다리 모양과 다른 결과가 발생한 것이었습니다.
- 다음 코드는 기존 문제가 되었던 코드와 개선한 코드입니다.
public class Line {
//...
private Point buildRandomBridge(int col, Random random) {
if (!existBridgeOnLeft(col) && random.nextBoolean()){
return Point.ofBridge();
}
return Point.ofEmpty();
}
}public class Line {
//...
private Point buildRandomBridge(int col, Random random) {
if (!random.nextBoolean()) {
return Point.ofEmpty();
}
if (!existBridgeOnLeft(col)) {
return Point.ofBridge();
}
return Point.ofEmpty();
}
}- 실행 결과 문자열의 길이가 5글자를 초과하면 출력시 맞지 않는 문제가 있었습니다.
- 요구사항에 실행결과 입력 길이를 최대 5글자로 제한한다는 것을 명시하였습니다.
- 사다리 타기 알고리즘 구현을 어떻게 할지 고민하였습니다.
- 기존 브릿지 정보만 저장하던 Point 객체에 사다리바("|") 정보도 저장하도록 변경하였습니다.
- 변경된 Point 객체를 기반으로 DFS 알고리즘을 구한하여 해결하였습니다.
public class Points implements Iterable<Point> {
//...
public int climb(int col) {
boolean[] visited = new boolean[points.size()];
return climbUtil(col, visited);
}
// TODO: depth2 -> depth1 줄이기
private int climbUtil(int col, boolean[] visited) {
visited[col] = true;
List<Integer> adjacentColumns = getAdjacentColumns(col);
for (int adjacentColumn : adjacentColumns) {
if (visited[adjacentColumn]) {
continue;
}
if (points.get(adjacentColumn).isEmpty()) {
continue;
}
return climbUtil(adjacentColumn, visited);
}
return col;
}
}- 기존 사용자의 사다리 정보 입력을 받는 LadderConsoleReader 객체는 사용자로부터 잘못된 입력을 받을때 다시 입력을 받게 하고 객체 생성시 예외처리도 하고 객체를 생성하는 역할을 하나의 메서드에서 수행하도록 하였습니다. 이때 생각한 것이 한 객체가 너무 많은 역할을 수행한다고 생각하였습니다.
- LadderConsoleReaderController라는 이름의 객체로 분리하고 해당 객체가 사용자로부터 잘못된 입력시 다시 입력하도록 제어할 수 있게 구현하였습니다.
- LadderConsoleReader 객체는 사용자로부터 문자열을 입력받고 그대로 정수로 변환한다든지 원시 타입 배열로 변환하여 반환하는 정도로 책임을 낮추었습니다.