상세 컨텐츠

본문 제목

[TDD] 시침과 분침 사이 각도 계산

IT/프로그래밍

by James Lee. 2015. 12. 6. 19:51

본문

요구사항 정리

input으로 시간과 분이 정수로 들어온다.

output으로 시침과 분침의 각도를 실수 소수점 1번째 자리까지 계산하여 반환한다.

시침과 분침의 각도는 같거나 더 작은것을 반환하도록 한다. (9시 00분을 예로 들면 90도 혹은 270도의 각도가 있다. 이때 90을 반환한다)

요구사항 테이블

시침분침각도
9090
95117.5(118)
930105

설계를 안하고 짜도 괜찮은가?

테스트 케이스 만들기

각도를 계산한다는 의미인 CalAngleTest 테스트 코드를 생성

프로덕션 코드 만들기

Caller Create 방식을 사용하여 CalAngle 클래스를 만들었다.

마찬가지 방식으로 CalAngle클래스에 calAngle메소드를 만들었다.

실패하는 테스트 코드 만들기

9시 0분을 검사하는 테스트 케이스를 만들었다. 

그런데 메소드가 중요하지 않아졌다. 라는 경고문구가 뜬다.

실수를 비교할수 없는 것 같다. 

그래서 우선은 정수로 검사하기로 하였다.

 @Test
 public void calAngleTest() {
  assertEquals(90, ca.calAngle(9, 0));
 }

테스트 코드 통과시키기

 public int calAngle(int inputHour, int inputMin) {
  return 90;
 }

리팩토링

없다.

코드 커밋하기

2번째 TDD Cycle

실패하는 테스트 케이스 만들기

 @Test
 public void calAngleTest() {
  assertEquals(90, ca.calAngle(9, 0));
  assertEquals(90, ca.calAngle(9, 30));
 }

테스트 코드 통과시키기

 public int calAngle(int inputHour, int inputMin) {
  if(inputHour == 9 && inputMin == 30)
  {
   return 105;
  }
  return 90;
 }

리팩토링

반환값을 angle이라는 변수로 명명한다.

90이 반환되는 경우에 생략된 조건문도 반영한다.

 public int calAngle(int inputHour, int inputMin) {
  int angle = 0;
  if (inputHour == 9 && inputMin == 30) {
   angle = 105;
  }
  if (inputHour == 9 && inputMin == 0) {
   angle = 90;
  }
  return angle;
 }

리팩토링

우리가 구하고자 하는 각도는 무엇인가?

시침과 분침 사이의 각도이다.

따라서 시침(hourAnlge)과 분침(minAngle) 변수를 추가한다.

 public int calAngle(int inputHour, int inputMin) {
  int angle = 0;
  int hourAngle = 0;
  int minAngle = 0;
  if (inputHour == 9 && inputMin == 30) {
   angle = 105;
  }
  if (inputHour == 9 && inputMin == 0) {
   angle = 90;
  }
  return angle;
 }

리팩토링

시침과 분침 사이의 각도는 어떻게 계산할까?

시침과 분침의 각도를 계산하는 방법은 2가지로 나눌 수 있다. 

시침과 분침의 각도의 차가 180보다 작은 경우 : (시침 - 분침)

시침과 분침의 각도의 차가 180보다 큰경우 : 360도 - (시침 - 분침)

 public int calAngle(int inputHour, int inputMin) {
  int angle = 0;
  int hourAngle = 0;
  int minAngle = 0;
  if (inputHour == 9 && inputMin == 30) { 
   hourAngle = 285;
   minAngle = 180; 
   angle = hourAngle - minAngle; //angle이 105도
  }
  if (inputHour == 9 && inputMin == 0) {
   hourAngle = 270;
   minAngle = 0;
   angle = hourAngle - minAngle; //angle이 270도로 180보다 큰경우
   if (angle > 180) {
    angle = 360 - angle;
   }
  }
  return angle;
 }

리팩토링

angle이 180보다 클 경우 처리하는 if구문은 동일하게 적용할 수 있다.

 public int calAngle(int inputHour, int inputMin) {
  int angle = 0;
  int hourAngle = 0;
  int minAngle = 0;
  if (inputHour == 9 && inputMin == 30) {
   hourAngle = 285;
   minAngle = 180;
   angle = hourAngle - minAngle;
   if (angle > 180) {
    angle = 360 - angle;
   }
  }
  if (inputHour == 9 && inputMin == 0) {
   hourAngle = 270;
   minAngle = 0;
   angle = hourAngle - minAngle;
   if (angle > 180) {
    angle = 360 - angle;
   }
  }
  return angle;
 }

리팩토링

시침과 분침을 어떻게 계산하는지 생각해보자.

간단한 분침부터 먼저 보자.

분침은(360 * 분)/60 의 공식으로 구할 수 있다.

 public int calAngle(int inputHour, int inputMin) {
  int angle = 0;
  int hourAngle = 0;
  int minAngle = 0;
  if (inputHour == 9 && inputMin == 30) {
   hourAngle = 285;
   minAngle = (360 * inputMin) / 60;
   angle = hourAngle - minAngle;
   if (angle > 180) {
    angle = 360 - angle;
   }
  }
  if (inputHour == 9 && inputMin == 0) {
   hourAngle = 270;
   minAngle = (360 * inputMin) / 60;
   angle = hourAngle - minAngle;
   if (angle > 180) {
    angle = 360 - angle;
   }
  }
  return angle;
 }

리팩토링

시침은 어떻게 구할 수 있을까?

시침은 시침 자체의 각도 + 분침에 따른 추가 각도로 계산된다.

이는 (360 * 시간)/12 + (30 * 분)/60으로 표현할 수 있다.

 public int calAngle(int inputHour, int inputMin) {
  int angle = 0;
  int hourAngle = 0;
  int minAngle = 0;
  if (inputHour == 9 && inputMin == 30) {
   hourAngle = (360 * inputHour) / 12 + (30 * inputMin) / 60;
   minAngle = (360 * inputMin) / 60;
   angle = hourAngle - minAngle;
   if (angle > 180) {
    angle = 360 - angle;
   }
  }
  if (inputHour == 9 && inputMin == 0) {
   hourAngle = (360 * inputHour) / 12 + (30 * inputMin) / 60;
   minAngle = (360 * inputMin) / 60;
   angle = hourAngle - minAngle;
   if (angle > 180) {
    angle = 360 - angle;
   }
  }
  return angle;
 }

리팩토링

양쪽 분기문의 내용이 완전히 동일해졌으므로 중복을 제거한다.

 public int calAngle(int inputHour, int inputMin) {
  int angle = 0;
  int hourAngle = 0;
  int minAngle = 0;
  hourAngle = (360 * inputHour) / 12 + (30 * inputMin) / 60;
  minAngle = (360 * inputMin) / 60;
  angle = hourAngle - minAngle;
  if (angle > 180) {
   angle = 360 - angle;
  }
  return angle;
 }

리팩토링

12, 360, 60, 30과 같은 상수는 의미를 알아보기 힘드므로 의미있는 이름으로 수정한다.

public class CalAngle {
 final int totAngle = 360;
 final int minUnitAngle = 30;
 final int hourUnitAngle = 12;
 final int totMin = 60;
 public int calAngle(int inputHour, int inputMin) {
  int angle = 0;
  int hourAngle = 0;
  int minAngle = 0;
  hourAngle = (totAngle * inputHour) / hourUnitAngle
    + (minUnitAngle * inputMin) / totMin;
  minAngle = (totAngle * inputMin) / totMin;
  angle = hourAngle - minAngle;
  if (angle > 180) {
   angle = totAngle - angle;
  }
  return angle;
 }
}

코드 커밋하기

세번째 TDD Cycle

실패하는 테스트 코드 만들기

솔루션이 일반화가 잘 되었는지 확인하기 위하여 여러가지 코드를 추가해본다.

 @Test
 public void calAngleTest() {
  assertEquals(90, ca.calAngle(9, 0));
  assertEquals(105, ca.calAngle(9, 30));
  assertEquals(117, ca.calAngle(9, 5)); //추가
 }

예상되는 값은 117.5인데 정수로 테스트했으므로 테스트를 할 수가 없다.

이부분은 어떻게 해결해야 할까?

문제 해결

assertEquals에서는 실수의 정확한 비교가 힘들어서 deprecated 경고가 뜨는 것이다. (아마 실수의 범위가 워낙 커서 그런 것 같다.)

그래서 어느정도의 오차값을 지정해줘야 하는데 그 방법은 아래와 같다.

 private static double DELTA = 1e-15;
 CalAngle ca = new CalAngle();
 @Test
 public void calAngleTest() {
  assertEquals(90, ca.calAngle(9, 0), DELTA);
  assertEquals(105, ca.calAngle(9, 30), DELTA);
  assertEquals(117.5, ca.calAngle(9, 5), DELTA);
 }

(구글링을 하니 stackoverflow맨 위에 뜬다. 역시 구글링은 중요하다.)

이제 프로덕션 코드의 calAngel메소드의 파라미터 및 지역변수들을 모두 double로 바꿔준다.

시침과 분침 구하는 공식도 괄호를 옮겨서 가독성이 조금더 명확해지도록 했다

(360 * 분)/12 -> 360 * (분/12)

 public double calAngle(double inputHour, double inputMin) {
  double angle = 0;
  double hourAngle = 0;
  double minAngle = 0;
  hourAngle = totAngle * (inputHour / hourUnitAngle) + minUnitAngle
    * (inputMin / totMin);
  minAngle = totAngle * (inputMin / totMin);
  angle = hourAngle - minAngle;
  if (angle > 180) {
   angle = totAngle - angle;
  }
  return angle;
 }

이제 테스트 코드가 무사히 실행된다.

따라서 일반화된 솔루션을 작성했다고 판단한다.

코드 커밋하기

테스트 코드

import static org.junit.Assert.*;
import org.junit.Test;
public class CalAngleTest {
 private static double DELTA = 1e-15;
 CalAngle ca = new CalAngle();
 @Test
 public void calAngleTest() {
  assertEquals(90, ca.calAngle(9, 0), DELTA);
  assertEquals(105, ca.calAngle(9, 30), DELTA);
  assertEquals(117.5, ca.calAngle(9, 5), DELTA);
 }
}

프로덕션 코드

public class CalAngle {
 final int totAngle = 360;
 final int minUnitAngle = 30;
 final int hourUnitAngle = 12;
 final int totMin = 60;
 public double calAngle(double inputHour, double inputMin) {
  double angle = 0;
  double hourAngle = 0;
  double minAngle = 0;
  hourAngle = totAngle * (inputHour / hourUnitAngle) + minUnitAngle
    * (inputMin / totMin);
  minAngle = totAngle * (inputMin / totMin);
  angle = hourAngle - minAngle;
  if (angle > 180) {
   angle = totAngle - angle;
  }
  return angle;
 }
}

2015-02-27 (금)

프로덕션 코드 추가 리팩토링

public class CalAngle {
 final int totAngle = 360;
 final int totMin = 60;
 public double calAngle(double inputHour, double inputMin) {
  double angle = hourAngle(inputHour, inputMin) - minuteAngle(inputMin);
  if (angle > 180) {
   angle = totAngle - angle;
  }
  return angle;
 }
 private double minuteAngle(double inputMin) {
  return totAngle * (inputMin / totMin);
 }
 private double hourAngle(double inputHour, double inputMin) {
  return 30 * (inputHour) + 0.5 * (inputMin);
 }
}

느낀점

나는 공책에 손으로 공식을 먼저 쓰고 프로그램을 작성했는데, 이것이 설계가 아닐까라고 생각했다.

하지만 이것은 설계라기보다는 계산이나 추론에 가까운 것이다.

구체적 설계란 것은 대부분, 클래스나 메소드를 구성할때 쓰는 것이다(클래스 다이어그램..)

이 추론은 TDD, 테스트 케이스를 이용해서도 할 수 있다.

바로 생각할 수 있는 심플한 테스트 케이스를 작성하는 것도 그 방법중 하나이다.

나는 시침과 분침을 알아내기 위하여 공책에다 먼저 공식을 계산하고 그것을 적용시켰다.

하지만 이사님께서는 시침과 분침을 알아내기 위하여 가장 단순하게 시침을 먼저 구하는 테스트케이스를 작성하고, 그다음엔 분침을 구하는 테스트코드를 작성하셨다.

그리고 문자도, 필요할때만 그자리에서 생성하도록 한다.



'IT > 프로그래밍' 카테고리의 다른 글

[Cucumber] 작동원리 분석  (0) 2015.12.06
Bit 연산 Leaning Test  (0) 2015.12.06
[TDD] 이진 검색  (0) 2015.12.06
웹 프로토콜 동작 원리  (0) 2015.12.06
URL 구조 분석  (0) 2015.12.06

관련글 더보기

댓글 영역