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

[jwon] typescript-calculator #4

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open

Conversation

jwon42
Copy link
Member

@jwon42 jwon42 commented Jun 3, 2021

데모페이지 링크
https://jwon42.github.io/javascript-calculator/


  • model, view, controller로 기능을 분리하고자 했습니다.
    • model 은 데이터에 관한 로직을 담기 위해 공식 만드는 함수인 makeFormula, 공식을 이용해 계산하는 함수인 calculator 가 위치합니다.
    • view 는 사용자에게 보여지는 화면을 조작하기 위한 함수가 위치합니다.
    • controller 는 이벤트 생성, 입력 값 유효성 체크를 위한 함수가 위치합니다.
  • function만을 사용했습니다.
  • 한 함수에서 최대한 한 기능만을 책임지되, 가독성을 해치는 너무 세세한 분리는 지양하려 노력했습니다.
  • cypress를 이용해 테스트 주도 개발 방식을 사용하면서 사용자 입장에서 생각해야하고 테스트 코드를 작성하는 것이 프로그램 설계에도 깊은 관련이 있다고 느꼈습니다.

테스트파일에서 확인 가능한 내용은 다음과 같습니다.

  1. 숫자 입력 시 3자리까지만 결과창에 표시
  2. AC 버튼 입력 시 0으로 초기화
  3. 연산자는 1개만 입력 가능 ([숫자][연산자][연산자] 불가)
  4. [숫자][연산자][숫자] 포맷 이후 연산자가 올 수 없음
  5. 한 자릿수 사칙연산
  6. 두 자릿수 사칙연산
  7. 세 자릿수 사칙연산

그 외에 문제에서 정의되지 않은 내용에 대한 처리는 다음과 같습니다.

  1. 숫자를 입력하지 않고 연산자를 입력했을 때의 동작
    1. - 의 경우 음수 입력 모드로 전환
    2. 그 외의 연산자를 입력하는 경우 초기 값 0 을 대상으로 연산 진행 모드 전환
  2. 결과 값을 대상으로 연속된 연산이 가능하지만 결과 값이 3자리를 넘어가는 경우 AC 버튼만 입력 가능
  3. 공식이 완성되지 않은 상태에서 = 버튼을 입력하는 경우 0을 출력

Copy link
Member

@hochan222 hochan222 left a comment

Choose a reason for hiding this comment

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

흐흫.. 타입스크립트 처음인데 고생하셨습니다. 훨씬 코드도 전보다 깔끔해서 좋았습니다. 에러 처리 경우에도 굉장히 세세하게 처리해서 좋았습니다.
commit 메세지는 조금 더 세분화 하면 좋을것 같아요. 예를 들어서, feat: 테스트케이스 추가는 테스트에 관한 부분이니 test: 테스트케이스 추가로, feat: gitignore 수정는 git 관련이니 chore: gitignore로 바꾸는게 좋아보입니다. 링크 저는 docs를 만들어 기능 구현 정의를 해놓고 그 분기대로 commit 을 해보니 깔끔해서 저는 좋았습니다.

{
	"parser": "typescript",
	"singleQuote": true,
	"trailingComma": "all"
  }

마지막으로, prettier 설정은 singleQuote로 되어있는데 코드에는 " 로 적용돼서 prettier 가 적용이 안된것같아요. 고생하셨습니다 :)

"cypress/no-async-tests": "error",
"import/extensions": "always"
}
}
Copy link
Member

Choose a reason for hiding this comment

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

EOL 관련 아티클 읽어보시면 좋을것 같아요 :)

@@ -1,3 +1,9 @@
package-lock.json
yarn.lock
Copy link
Member

Choose a reason for hiding this comment

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

transcendence42/javascript-archive#16 (comment)

현재는 복잡도가 낮은 프로그램이여서 문제가 안될것같지만 후에 복잡도가 높아지면 같이 저장하는게 좋다고 생각합니다.! 또 package-lock.jsonyarn.lock이 공존하는데 , npm 과 yarn중 하나로 통일해서 사용하는게 좋을것 같아요!

cy.get('#total').should('have.text', '0');
})

it('3. 연산자는 1개만 입력 가능', () => {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
it('3. 연산자는 1개만 입력 가능', () => {
it('3. 연산자 2개 이상 입력시, 먼저 입력한 연산자로 적용', () => {

작성한 it의 설명은 함수 단위 테스트 설명이라고 생각합니다. BDD인 만큼 조금 더 세부적으로 행동을 중심으로 작성하면 좋을것 같아요! 아래는 Given, When, Then 에 대한 간략 설명입니댜!

Given : 시나리오 진행에 필요한 값을 설정합니다.
When : 시나리오를 진행하는데 필요한 조건을 명시합니다
Then : 시나리오를 완료했을 때 보장해야 하는 결과를 명시합니다.

Copy link

Choose a reason for hiding this comment

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

@hochan222

그러면 여기서는

  • Given: 연산자 2개(+, -),
  • When: 입력(클릭),
  • Then: 먼저 입력한 연산자 적용

이 되는 건가요?

Copy link
Member

@hochan222 hochan222 Jun 4, 2021

Choose a reason for hiding this comment

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

맞아요! 제가 코멘트를 단 이유는 Given, When, Then의 느낌보다는 다음 it 설명이 유닛테스트 느낌이 더 강해서 였습니다. 조금 더 자세히 적어서 it 설명만 보아도 동작을 알 수 있어야 한다고 생각합니다.

연산자 2개 이상 입력시, 먼저 입력한 연산자로 적용

연산자 2개 이상(Given)
입력시,(When)
먼저 입력한 연산자로 적용(Then)

으로 나누어서 적었습니다!

})

it('5. 한 자릿수 사칙연산', () => {
calculate([4], '+', [2], '6');
Copy link
Member

Choose a reason for hiding this comment

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

LGTM 👍 👍

"cypress": "^7.4.0",
"typescript": "^4.3.2"
},
"description": "<br/> <br/> <p align=\"middle\" > <img width=\"100px;\" src=\"src/images/calculator_icon.png\"/> </p> <h2 align=\"middle\">level1 - 자바스크립트 계산기</h2> <p align=\"middle\">자바스크립트 계산기로 익혀보는 Cypress</p> <p align=\"middle\"> <img src=\"https://img.shields.io/badge/version-1.0.0-blue?style=flat-square\" alt=\"template version\"/> <img src=\"https://img.shields.io/badge/language-html-red.svg?style=flat-square\"/> <img src=\"https://img.shields.io/badge/language-css-blue.svg?style=flat-square\"/> <img src=\"https://img.shields.io/badge/language-js-yellow.svg?style=flat-square\"/> <img src=\"https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square\"/> </p>",
Copy link
Member

Choose a reason for hiding this comment

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

description에 HTML Tag를 포함하신 이유가 따로 있으신가요?

Copy link
Member Author

Choose a reason for hiding this comment

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

이건.. 제가 한 기억이 없습니다. 초기 설정에 애를 먹어서 중복 작업이 많았었는데 어느 툴에선가 자동으로 들어갔나봅니다.

totalValue: string,
clickValue: string
): boolean => {
const tmpValue = totalValue + clickValue;
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
const tmpValue = totalValue + clickValue;
const tmpValue: string = totalValue + clickValue;

@@ -0,0 +1,32 @@
import { OPERATOR, REGEXP } from "../constants/index.js";

export const calculator = (splitedTotalValue: Array<string>): number => {
Copy link
Member

Choose a reason for hiding this comment

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

저는 string[] 을 사용했는데 둘의 차이점이 궁금해서 찾아보았습니다.

What is the difference Array and string[]?

모두 다 동일한 문법인데, 차이점이 하나있다면

class MyArray extends Array<string> { ... }

확장 문법 사용할 때는 Array<string>만 써야한다고 합니다.

import { calculator, makeFormula } from "../model/calculator.js";

export const clearTotalValue = (): void => {
document.getElementById("total")!.innerText = "0";
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
document.getElementById("total")!.innerText = "0";
$("total")!.innerText = "0";

다른 코드 보니까 이렇게 간단하게 표현한 코드가 있어서 공유합니댜.

const $ = (id) => document.getElementById(id) 로 정의하면 위와같이 표현할 수 있습니다.

Copy link
Member Author

Choose a reason for hiding this comment

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

넘 좋네요. 담 프로젝트에 한번 사용해보겠습니다.

@@ -0,0 +1,78 @@
{
"include": [
"src/**/*.ts"
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
"src/**/*.ts"
"./src/**/*.ts"

이렇게 하면 빨간줄 사라지더라구요!

cy.get('#total').should('have.text', '1');
cy.get('.digit').contains('2').click();
cy.get('#total').should('have.text', '12');
cy.get('.digit').contains('3').click();
Copy link
Member

Choose a reason for hiding this comment

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

위에 calculate 만들어 준 것처럼 따로 함수로 빼주면 더 깔끔해 보일 것 같아요!

Copy link
Member Author

Choose a reason for hiding this comment

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

맞아요. 다음 과제에선 cypress 코드 작성에 조금 더 신경을 써봐야겠어요~!

Comment on lines +1 to +12
const calculate = (number1, operator, number2, expectedResult) => {
cy.get('.modifiers').click();
for (let index = 0 ; index < number1.length ; index += 1) {
cy.get('.digit').contains(number1[index]).click();
}
cy.get('.operations').contains(operator).click();
for (let index = 0 ; index < number2.length ; index += 1) {
cy.get('.digit').contains(number2[index]).click();
}
cy.get('.operations').contains('=').click();
cy.get('#total').should('have.text', expectedResult)
}
Copy link
Member

Choose a reason for hiding this comment

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

깔끔하고 좋네요.

@@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Calculator</title>
<link rel="stylesheet" type="text/css" href="src/css/index.css" />
<link rel="stylesheet" type="text/css" href="dist/css/index.css" />
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
<link rel="stylesheet" type="text/css" href="dist/css/index.css" />
<link rel="stylesheet" type="text/css" href="src/css/index.css" />

이렇게 해야 제대로 불러오네요

Copy link
Member Author

Choose a reason for hiding this comment

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

수정하고 데모페이지 생성했습니다 :)

import { setEventListener } from "./event.js";

export const controller = (): void => {
setEventListener();
Copy link
Member

Choose a reason for hiding this comment

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

여기 바로 setEventListener의 코드 자체를 넣지 않고 함수를 넣은 이유가 있나요?

Copy link
Member Author

Choose a reason for hiding this comment

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

이전 코드의 패턴을 그대로 복사해와서 사용해서 남은 흔적이에요. 없애고 메인에서 바로 해당 파트를 불러오는게 더 좋을 것 같습니다.

Comment on lines +19 to +27
const tmpValueArray: RegExpMatchArray | null = tmpValue.match(
"(" +
REGEXP.NUMBERS +
")?(" +
REGEXP.OPERATORS +
")?(" +
REGEXP.NUMBERS +
")?"
);
Copy link
Member

Choose a reason for hiding this comment

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

match 라는 좋은 함수 배워갑니다.

str.match(regexp)
match() 메서드는 문자열이 정규식과 매치되는 부분을 검색합니다.
regexp
정규식 개체입니다. RegExp가 아닌 객체 obj가 전달되면, new RegExp(obj)를 사용하여 암묵적으로 RegExp로 변환됩니다. 매개변수를 전달하지 않고 match()를 사용하면, 빈 문자열:[""]이 있는 Array가 반환됩니다.

Comment on lines +11 to +15
export const REGEXP = {
// SIGN: '\\-{1}',
NUMBERS: '\\-?\\d{1,3}',
OPERATORS: 'X|\\-|\\+|\\/{1}'
}
Copy link
Member

Choose a reason for hiding this comment

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

정규표현식도 잘 배워갑니다.
match말고 여러 함수에서 사용해볼 수 있네요.

정규 표현식은 문자열에 나타는 특정 문자 조합과 대응시키기 위해 사용되는 패턴입니다. 자바스크립트에서, 정규 표현식 또한 객체입니다. 이 패턴들은 RegExp의 exec 메소드와 test 메소드 ,그리고 String의 match메소드 , replace메소드 , search메소드 , split 메소드와 함께 쓰입니다 .

Comment on lines +29 to +48
if (!tmpValueArray[2] && tmpValueArray[3]) {
return true;
}
if (
tmpValueArray[2] &&
!isNumber(totalValue.charAt(totalValue.length - 1)) &&
!isNumber(clickValue)
) {
return true;
}
if (tmpValueArray[3] && !isNumber(clickValue) && clickValue !== EQUAL) {
return true;
}
if (
tmpValueArray[3] &&
tmpValue.length !== tmpValueArray[0].length &&
clickValue !== EQUAL
) {
return true;
}
Copy link
Member

Choose a reason for hiding this comment

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

모든 if 문이 안에서 return 하고 있으니까 이렇게 바꾸면 좋겠네요 !

Suggested change
if (!tmpValueArray[2] && tmpValueArray[3]) {
return true;
}
if (
tmpValueArray[2] &&
!isNumber(totalValue.charAt(totalValue.length - 1)) &&
!isNumber(clickValue)
) {
return true;
}
if (tmpValueArray[3] && !isNumber(clickValue) && clickValue !== EQUAL) {
return true;
}
if (
tmpValueArray[3] &&
tmpValue.length !== tmpValueArray[0].length &&
clickValue !== EQUAL
) {
return true;
}
if (!tmpValueArray[2] && tmpValueArray[3]) {
return true;
}
else if (
tmpValueArray[2] &&
!isNumber(totalValue.charAt(totalValue.length - 1)) &&
!isNumber(clickValue)
) {
return true;
}
else if (tmpValueArray[3] && !isNumber(clickValue) && clickValue !== EQUAL) {
return true;
}
else if (
tmpValueArray[3] &&
tmpValue.length !== tmpValueArray[0].length &&
clickValue !== EQUAL
) {
return true;
}

Copy link
Member Author

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 +13
export const parseTotalValue = (totalValue) => {
let isFirstNumberMinus = false;
if (totalValue[0] === "-") {
totalValue = totalValue.slice(1, totalValue.length);
isFirstNumberMinus = true;
}
const operator = totalValue.replace(/\d/g, "");
const numbers = totalValue.split(operator);
if (isFirstNumberMinus) {
return ["-" + numbers[0], operator, numbers[1]];
}
return [numbers[0], operator, numbers[1]];
};
Copy link
Member Author

Choose a reason for hiding this comment

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

로직 변경 후 사용하지 않는 파일입니다. git rm을 누락했네요.

Copy link

@yhshin0 yhshin0 left a comment

Choose a reason for hiding this comment

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

잘 봤습니다. 정규표현식 사용이랑 테스트에서 함수로 따로 빼낸 부분이 좋네요.

수고하셨습니다!

@@ -0,0 +1,23 @@
{
Copy link

Choose a reason for hiding this comment

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

파일명이 .exlintrc.json으로 되어 있는데 상관 없는건가요?

Copy link
Member Author

Choose a reason for hiding this comment

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

아뇨.. 어쩐지 적용이 안되더라구요. 하하하하..

yarn.lock
cypress/fixtures/
cypress/intergration/examples/
index.html
Copy link

Choose a reason for hiding this comment

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

gitignore에 index.html 파일이 있는 이유가 궁금합니다

Copy link
Member Author

Choose a reason for hiding this comment

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


이건 마지막에 git add이후엔 gitignore 적용이 안되는 부분 테스트하면서 넣어놨었는데 제거하는걸 깜빡했네요.

cy.get('#total').should('have.text', '0');
})

it('3. 연산자는 1개만 입력 가능', () => {
Copy link

Choose a reason for hiding this comment

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

@hochan222

그러면 여기서는

  • Given: 연산자 2개(+, -),
  • When: 입력(클릭),
  • Then: 먼저 입력한 연산자 적용

이 되는 건가요?

import { REGEXP, EQUAL } from "../constants/index.js";

export const isNumber = (str: string): boolean => {
return /^[\d.]+(?:e-?\d+)?$/.test(str);
Copy link

Choose a reason for hiding this comment

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

이건 숫자만 판별하는 건가요?
(?:e-?\d+)? 부분 의미가 궁금하네요

Copy link
Member Author

Choose a reason for hiding this comment

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

소수점, 부동소수점까지 체크하는 부분인데 이 프로그램에서는 필요가 없습니다. 예리하십니다~!

REGEXP.NUMBERS +
")?"
);
if (tmpValueArray) {
Copy link

Choose a reason for hiding this comment

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

요기는 if (!tmpValueArray) return false; 로 빼낼 수 있을 것 같아요

Suggested change
if (tmpValueArray) {
if (!tmpValueArray) return false;
if (!tmpValueArray[2] && tmpValueArray[3]) {
return true;
}
if (
tmpValueArray[2] &&
!isNumber(totalValue.charAt(totalValue.length - 1)) &&
!isNumber(clickValue)
) {
return true;
}
if (tmpValueArray[3] && !isNumber(clickValue) && clickValue !== EQUAL) {
return true;
}
if (
tmpValueArray[3] &&
tmpValue.length !== tmpValueArray[0].length &&
clickValue !== EQUAL
) {
return true;
}
return false;

Copy link
Member Author

Choose a reason for hiding this comment

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

맞네요. 선 에러처리가 좋겠군요. 시간도 단축되겠습니다.

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

Successfully merging this pull request may close these issues.

4 participants