Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[4기 - 최지연] 1~2주차 과제 : 계산기 구현 미션 제출합니다. #166

Open
wants to merge 5 commits into
base: ddongpuri
Choose a base branch
from

Conversation

ddongpuri
Copy link

java-calculator

과제설명

  • Caculator 클래스가 계산기 자체라고 생각하고, 각 기능을 담당하는 객체들을 Calculator 클래스가 멤버변수로 가지도록 했습니다.
  • 사칙연산 구현에 대한 이해 부족으로, 곱셉/나눗셈 처리 메서드 호출 -> 덧셈/나눔셈을 처리 메서드를 호출 방식으로 연산 우선순위를 구현했습니다.

요구사항

  • 콘솔로 구현입니다.(스윙으로 구현하시는 분들 계실까봐)
  • 객체지향적인 코드로 계산기 구현하기
    • 더하기
    • 빼기
    • 곱하기
    • 나누기
    • 우선순위(사칙연산)
  • 테스트 코드 구현하기
  • 계산 이력을 맵으로 데이터 저장기능 만들기
    • 애플리케이션이 동작하는 동안 데이터베이스 외에 데이터를 저장할 수 있는 방법을 고안해보세요.
  • (선택) 정규식 사용

구현할 때 궁금했던 점

  • 계산식을 입력받을 때 유효한 식인지 검증하는 과정이 복잡한데, 정규식을 잘 활용하면 이 단계를 줄일 수 있는지 궁금합니다.
  • 클래스를 나눌 때, 어떤 생각과 기준을 가지고 나누면 좋을지 궁금합니다.
  • 이번 과제와 같은 규모의 코드를 짤 때, 적절한 클래스의 개수가 궁금합니다.

@maenguin
Copy link

지연님이 궁금한점만 일단 빠르게 답변드릴께요~!

계산식을 입력받을 때 유효한 식인지 검증하는 과정이 복잡한데, 정규식을 잘 활용하면 이 단계를 줄일 수 있는지 궁금합니다.

네 문자열을 다룰때 정규식을 잘 활용하시면 유효성 검증과 토큰화 과정을 짧은 코드로 줄일 수 있습니다.

클래스를 나눌 때, 어떤 생각과 기준을 가지고 나누면 좋을지 궁금합니다.

보통 단일 책임 원칙이라고 해서 객체가 하나의 책임만을 가질 수 있게 분리하라고 하는데요
말만 들어선 너무 어려우니 리뷰 드리면서 이게 왜 필요한지 같이 알아가보시죠~!

이번 과제와 같은 규모의 코드를 짤 때, 적절한 클래스의 개수가 궁금합니다.

적절한 클래스 개수는 정의할 수 없을 것 같은데요 이것도 리뷰 드리면서~!

Comment on lines +142 to +159
public boolean isValidFormula(String inputRefined) {

boolean startWithNum = true;
boolean onlyAllowed = true;

char firstChar = inputRefined.charAt(0);

if (firstChar == '+' || firstChar == '*' || firstChar == '/') {
System.out.println("유효하지 않은 수식입니다!");
startWithNum = false;
}

if (!inputRefined.matches("^([0-9/./+/*///-]*)[0-9]$")) {
onlyAllowed = false;
}

return startWithNum && onlyAllowed;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"2*4--3" 을 입력해도 true가 반환되네요

Comment on lines +11 to +28
private String formalize(String expression) {
StringBuilder sb = new StringBuilder();
char c;

for (int i = 0; i < expression.length(); i++) {
c = expression.charAt(i);

if (!Character.isDigit(c)) sb.append(' ').append(c).append(' ');
else sb.append(c);
}

if (sb.length() > 1 && sb.charAt(1) == '-') {
sb.deleteCharAt(2);
sb.deleteCharAt(0);
}

return sb.toString();
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

공백이 제거된 연산식에 공백을 끼워넣는 작업으로 보이는데요
원래 입력된 연산식을 그대로 넣어 줄 수 는 없을까요~?

Comment on lines +1 to +4
public interface CalculatorFunction {

String name = null;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 name이라는 변수가 오직 로그를 찍기 위해서 CalculatorFunction 인터페이스를 포함해서 CalculatorFunction의 구현체인 CalculateService의 인스턴스를 생성하는 과정에서부터 포함되고 있는데요, 로그를 찍는게 중요할 수도 있지만, 설령 중요하더라도 이런 변수를 인터페이스에 선언할 이유는 없고, 지금 찍고 계신 로그는 별로 중요한 내용은 아닌 것 같아서요! ("계산", "test" 등)
이걸 위해서 인터페이스에서부터 변수를 추가할 필요가 있는지 생각해봐야할 것 같습니다.

Comment on lines +20 to +22
String expression;
LinkedList<String> expForCal;
String result;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이런식으로 지역 변수를 선언하면서 초기화 하지 않는 코드는 가급적 지양하는게 좋습니다.
예외 처리 때문에 이렇게 되었겠죠?
이렇게 나뉘지 않도록 만들어보세요. :)

Comment on lines +42 to +44
public String calculate(LinkedList<String> expForCal) throws Exception {
return plusOrMinus(divideOrMultiple(expForCal));
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 코드가 public인 이유는 무엇일까요?
아마 테스트 코드 때문에 이렇게 하셨을 것 같은데, 이렇게 따로 테스트하고 싶어지는 경우 CalculateService처럼 너무 클래스가 하는 일이 많을 가능성이 높습니다.
실제로 CalculateService는 현재 160여줄이나 될 정도로 길죠.
클래스를 한번 나눠보시기 바랍니다.
나눌 때의 방향성은 팀 미팅때도 이야기드렸던 것처럼, 가급적 입력과 출력에 의존적이지 않도록 바꿔보세요. 현재에는 다분히 키보드에서 입력되는 표준 입력에 의존적인 코드로 보입니다.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

그래도 연산자 우선순위를 메서드의 호출 순서를 통해 해결하신건 인상적이네요.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

예외 관련해서는 Checked Exception 말고 Unchecked Exception으로 바꿔보시기 바랍니다.

Comment on lines +5 to +37
public class Calculator {

private Integer optionNumber = 2;
private ResultSaveService resultSaveService;
private CalculateService calculateService;
private static Map<Integer, CalculatorFunction> menus = new HashMap<>();
private StringBuilder menusBuilder = new StringBuilder();
private CalculatorFunction calculatorFunction;

public Calculator(ResultSaveService resultSaveService, CalculateService calculateService) {
this.resultSaveService = resultSaveService;
this.calculateService = calculateService;
}

public static void main(String[] args) {

ResultSaveService saveService = new ResultSaveService("조회");
CalculateService calService = new CalculateService("계산");
Calculator calculator = new Calculator(saveService, calService);

calculator.menus.put(1, saveService);
calculator.menus.put(2, calService);


for (int i = 1; i <= calculator.menus.size(); i++) {
calculator.menusBuilder.append(i + ". " + menus.get(i).getName() + "\n");
}

calculator.menusBuilder.append("\n");
calculator.menusBuilder.append("선택 : ");

calculator.run();
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

입력과 출력으로부터 코드를 분리하는 것의 일환으로 main 메서드가 있는 클래스에서 입출력이 이뤄지지 않는게 좋습니다.
입출력을 처리하는 클래스를 따로 빼보시고, main가 있는 이 클래스는 그 클래스를 생성하여 사용하는 형태로 바꿔보세요. :)

Comment on lines +15 to +27
if (results.size() == 0) {

System.out.println("[ 조회된 계산이력 없음 ]\n");
} else {
StringBuilder savedResults = new StringBuilder();
Iterator<Integer> iterator = results.keySet().iterator();

while(iterator.hasNext()) {
Result result = results.get(iterator.next());
savedResults.append(result.fullEquation());
}
System.out.println(savedResults);
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

간단한 스킬이지만, 이렇게 if-else 문은 조금 더 가독성 좋게 바꿔볼 수 있습니다.
early return이라고 부르는 방법인데요, 아래와 같이 코드를 바꾸는겁니다.

        if (results.size() == 0) {
            System.out.println("[ 조회된 계산이력 없음 ]\n");
            return;
        }

        StringBuilder savedResults = new StringBuilder();
        Iterator<Integer> iterator = results.keySet().iterator();

        while(iterator.hasNext()) {
            Result result = results.get(iterator.next());
            savedResults.append(result.fullEquation());
        }

        System.out.println(savedResults);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이렇게 처리하면 else 쪽 블록을 없앨 수가 있겠죠?

Comment on lines +12 to +13
class CalculateServiceTest {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

테스트 코드 관련해서는 현재 상태로도 문제 없습니다.
다만, 위에 리뷰드린 것처럼 private 이어도 충분한 메서드를 테스트를 위해 public으로 바꾼 부분에 대해 '어떻게하면 private을 public으로 바꾸지 않고도 테스트할 수 있을지?' 고민해보시기 바랍니다.
사실 지금 단계에서 생각하기는 꽤 어려운 주제일 수도 있어요. 그래도 한번 고민해보세요!

@lleellee0
Copy link

@ddongpuri

최지연님 수고하셨습니다. :)
리뷰 확인해보시고, 맹희님이 따로 봐주신다고하니 잘 이야기 해보세요.
저한테 궁금한 내용 있으면 따로 나중에 질문 주셔도 됩니다.

아래는 궁금하다고 적어주신 내용에 대한 답변입니다.

계산식을 입력받을 때 유효한 식인지 검증하는 과정이 복잡한데, 정규식을 잘 활용하면 이 단계를 줄일 수 있는지 궁금합니다.

-> 지금도 일부 정규식을 활용하고 계신 것 같은데요, 말씀해주신대로 정규식을 활용하면 검증 과정을 더 개선해볼 수 있습니다!
근데 이건 사실상 정규식에 대한 활용 측면이기 때문에 혼자 고민하시기 보다는 다른 백둥이분들은 어떻게 검증하고 있는지 확인해보시면 좋을 것 같아요. :)

클래스를 나눌 때, 어떤 생각과 기준을 가지고 나누면 좋을지 궁금합니다.

-> 이건, 팀 미팅때와 리뷰에 적어뒀다고 생각합니다. 지연님 코드에서 간단히 요약하자면,

  • 입력과 출력으로부터 계산기의 핵심적인 로직(연산 등)을 분리
  • private 메서드여도 충분한 메서드를 public으로 바꾸지 않고도 테스트 될 수 있도록 코드를 작은 단위로 유지

이 2가지가 쟁점일 것 같습니다. 이 외에도 클래스를 나누는 기준은 많이 있긴해요. 그러나 우선 이정도에서 시작해보시면 좋을 것 같습니다!

이번 과제와 같은 규모의 코드를 짤 때, 적절한 클래스의 개수가 궁금합니다.

저는 적절한 개수라고 생각하긴 했어요. 물론 조금은 더 나뉘어도 좋겠다고 싶었던 부분은 있었습니다. 다만, 위에 2가지 포인트 이야기 드린건 좀 분리되면 더 적절할 것 같다고 생각이 들긴했습니다. 그러나 지금처럼 작성하셔도 문제가 있는건 아니에요.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants