Javascript

우아한 테크코스 6기: 프리코스 2주차 Front-End 피드백 정리

Jinmidnight 2023. 11. 8. 01:03

우아한 테크코스 6기 프리코스 2주차 과제에 대한 공통 피드백이 주어졌다.

프리코스 3주차 과제를 하기에 앞서, 더 나은 과제 수행을 위해 해당 피드백을 꼼꼼히 살펴보며 정리하고자 한다.


2주차 공통 피드백

README.md를 상세히 작성한다

미션 저장소의 README.md는 소스코드에 앞서 해당 프로젝트가 어떠한 프로젝트인지 마크다운으로 작성하여 소개하는 문서이다. 해당 프로젝트가 어떠한 프로젝트이며, 어떤 기능을 담고 있는지 기술하기 위해서 마크다운문법을 검색해서 학습해보고 적용해 본다.

-> 마크다운 guide 사이트(https://www.markdownguide.org/cheat-sheet/)를 참고하자

 

기능 목록을 재검토한다

기능 목록을 클래스 설계와 구현, 함수(메서드) 설계와 구현과 같이 너무 상세하게 작성하지 않는다. 클래스 이름, 함수(메서드) 시그니처와 반환값은 언제든지 변경될 수 있기 때문이다. 너무 세세한 부분까지 정리하기보다 구현해야 할 기능 목록을 정리하는 데 집중한다. 정상적인 경우도 중요하지만, 예외적인 상황도 기능 목록에 정리한다. 특히 예외 상황은 시작 단계에서 모두 찾기 힘들기 때문에 기능을 구현하면서 계속해서 추가해 나간다.

-> 2주차 과제에서 클래스 이름 등 기능 목록을 다소 상세하게 작성한 것 같다. 전체적인 기능 목록 정리에 집중하자. 그리고 꼭 다양한 경우의 예외 사항도 정리하자.

 

기능 목록을 업데이트한다

README.md 파일에 작성하는 기능 목록은 기능 구현을 하면서 변경될 수 있다. 시작할 때 모든 기능 목록을 완벽하게 정리해야 한다는 부담을 가지기보다 기능을 구현하면서 문서를 계속 업데이트한다. 죽은 문서가 아니라 살아있는 문서를 만들기 위해 노력한다.

-> 전체적인 기능 목록과 예외 사항만 README.md 파일에 작성한 후, 바로 구현 작업에 들어가자. 대신 해당 파일을 상황에 맞게 수시로 업데이트하자.

 

값을 하드 코딩하지 않는다

문자열, 숫자 등의 값을 하드 코딩하지 마라. 상수를 만들고 이름을 부여해 이 변수의 역할이 무엇인지 의도를 드러낸다.

-> 기존에 하던 대로 문자열, 숫자 등의 값을 MESSAGE.js 파일에 객체 속 상수로 구현하자. 과제 중간중간에 실수로 하드 코딩한 값이 없는지 검토하자.

 

구현 순서도 코딩 컨벤션이다

클래스는 필드, 생성자, 메서드 순으로 작성한다.

class A {
    필드

    생성자

    메서드
}

-> 기존에 하던 대로 필드는 private으로, 생성자는 constructor로, 메서드는 최대한 세분화해서 구현하자

 

한 함수가 한 가지 기능만 담당하게 한다

함수 길이가 길어진다면 한 함수에서 여러 일을 하려고 하는 경우일 가능성이 높다. 아래와 같이 한 함수에서 안내 문구 출력, 사용자 입력, 유효값 검증 등 여러 일을 하고 있다면 이를 적절하게 분리한다.

const userInput = () => {
  MissionUtils.Console.readLine("경주할 자동차 이름을 입력하세요(이름은 쉼표(,)를 기준으로 구분): ", (input) => {
    const carNames = input.split(",");
    for (int index = 0; index < carNames.length; index++) {
      if (carNames[index].length < 1 || carNames[index].length > 5) {
        throw new Error("[ERROR] 자동차 이름은 1자 이상 5자 이하만 가능합니다.");
      }
    }
    return carNames;
  });
};

-> 함수 내에 두 가지 이상의 기능이 수행될 경우, 여러 함수로 분리하여 구현하자

 

함수가 한 가지 기능을 하는지 확인하는 기준을 세운다

만약 여러 함수에서 중복되어 사용되는 코드가 있다면 함수 분리를 고민해 본다. 또한, 함수의 길이를 15라인을 넘어가지 않도록 구현하며 함수를 분리하는 의식적인 연습을 할 수 있다.

-> 재사용성이 높은 함수미리 빼서 구현하자. 또한, 함수는 15줄 이하로 구현하면서 여러 기능을 수행하지 않게 방지하자

 

JavaScript에서 객체를 만드는 다양한 방법을 이해하고 사용한다.

JavaScript에서는 클래스 말고도 객체를 만드는 방법은 여러 가지가 있다. 객체를 생성하는 방법에 대해서는 MDN 문서의 JavaScript 객체 기본 Classes을 참고한다.

-> 체 리터럴, 괄호 표현식 등 다양한 방법으로 객체를 다룰 수 있도록 연습하자 

 

테스트를 작성하는 이유에 대해 본인의 경험을 토대로 정리해본다

단지 기능을 점검하기 위한 목적으로 테스트를 작성하는 것은 아니다. 테스트를 작성하는 과정을 통해서 나의 코드에 대해 빠르게 피드백을 받을 수 있을 뿐만 아니라 학습 도구(학습테스트를 통해 JUnit 학습하기.pdf)로도 활용할 수 있다. 이런 경험을 통해 테스트에 대해 어떤 유용함을 느꼈는지 알아본다.

-> jest에 대한 학습을 틈틈이 하자(https://jestjs.io/). 테스트를 작성하는 이유는 테스트를 작성함으로써 구현 도중 예기치 못한 오류를 찾아낼 수 있으며, console에 출력해보는 등 기능 점검의 불필요한 과정을 생략할 수 있기 때문이다

 

처음부터 큰 단위의 테스트를 만들지 않는다

테스트의 중요한 목적 중 하나는 내가 작성하는 코드에 대해 빠르게 피드백을 받는 것이다. 시작부터 큰 단위의 테스트를 만들게 된다면 작성한 코드에 대한 피드백을 받기까지 많은 시간이 걸린다. 그래서 문제를 작게 나누고, 그 중 핵심 기능에 가까운 부분부터 작게 테스트를 만들어 나간다.

 

큰 단위의 테스트

  • 자동차경주를 시작해서 사용자가 이름, 진행 횟수를 입력하면, 게임을 진행한 후 그 결과를 알려준다.

작은 단위의 테스트

  • 무작위 값이 4 이상이면 자동차가 전진한다.
  • 무작위 값이 3 이하이면 자동차가 전진하지 않는다.

-> 기능 구현을 완료하는 동시에 테스트를 작성함으로써, 작은 단위에서 큰 단위로 테스트 코드를 확장하자

 


추가 학습 자료

상수

* 함수형 언어

- ex. Scala, Erlang- 변수값 변경 금지

 

* 변수명

// 문자와 숫자, 그리고 기호 $와 _만 들어갈 수 있음
// 주로 카멜 표기법으로 작성
let userName;
let test123;
let $ = 1;
let _ = 2;

// 아래는 잘못된 변수명의 예시
let 1a; // 변수명은 숫자로 시작해서는 안 됨
let my-name; // 하이픈 '-'은 변수명에 올 수 없음

// 예약어는 변수명으로 사용불가
let return = 5; // ERROR
let let = 5; // ERROR

// use strict
num1 = 5;
alert(num1); // 5
"use strict";
num2 = 5; // error: num is not defined

 

* 상수명

- 간결하고 명확하게

- 자신만의 규칙이나 소속된 팀의 규칙을 따르자. 만약 사이트 방문객을 'user'라고 부르기로 했다면, 이와 관련된 변수를 currentVisitor newManInTown이 아닌 currentUser newUser라는 이름으로 짓자

 

* 대문자 상수

- ‘하드 코딩한’ 값의 별칭을 만들 때 주로 사용

- 실행(런타임) 전에 이미 값을 알고 있고, 코드에서 직접 그 값을 쓰는 경우에 사용

 

JavaScript 객체 기본

* 객체 리터럴

- 객체를 생성할 때 컨텐츠를 그대로 대입

- 연속된 구조체나 연관된 데이터를 일정한 방법으로 변환하고자 할 때 많이 쓰이는 방법

const person = {
  // 프로퍼티(속성)
  name: ["Bob", "Smith"],
  age: 32,
  gender: "male",
  interests: ["music", "skiing"],
  // 메소드(함수)
  greeting: function () {
    alert("Hi! I'm " + this.name[0] + ".");
  },
};

 

* 점 표기법 / 괄호 표기법

- 표기 방식

ex. person.age <=> person["age"]

ex. person.name.first <=> person["name"]["first"]

- 괄호 표기법의 이점:  멤버의 값을 동적으로 변경할 수 있을 뿐아니라, 멤버 이름까지도 동적으로 사용할 수 있다는 것

var myDataName = "height";
var myDataValue = "1.75m";
person[myDataName] = myDataValue;

person.height; // 1.75m

 

Classes

* 함수 선언 & 클래스 선언 차이점

- 함수의 경우 정의하기 하기 전에 호출할 수 있

- 클래스의 경우 반드시 정의한 뒤에 사용할 수 있음

const p = new Rectangle(); // ReferenceError

class Rectangle {}

 

* Class 선언 vs Class 표현식

// Class 선언
class Rectangle {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
}

// Class 표현식
// unnamed
let Rectangle = class {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
};
console.log(Rectangle.name);
// 출력: "Rectangle"

// named
let Rectangle = class Rectangle2 {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
};
console.log(Rectangle.name);
// 출력: "Rectangle2"

 

* 정적(static) 메서드

- 클래스의 인스턴스화 없이 호출

- 어플리케이션(application)을 위한 유틸리티(utility) 함수를 생성하는 데 주로 사용

- 캐시, 고정 환경설정 또는 인스턴스 간에 복제할 필요가 없는 기타 데이터에 유용

// strict mode
class Animal {
  speak() {
    return this;
  }
  static eat() {
    return this;
  }
}

let obj = new Animal();
obj.speak(); // the Animal object
let speak = obj.speak;
speak(); // undefined

Animal.eat(); // class Animal
let eat = Animal.eat;
eat(); // undefined
// non-strict mode
function Animal() {}

Animal.prototype.speak = function () {
  return this;
};

Animal.eat = function () {
  return this;
};

let obj = new Animal();
let speak = obj.speak;
speak(); // global object (in non–strict mode)

let eat = Animal.eat;
eat(); // global object (in non-strict mode)

 

* 인스턴스 속성

- 인스턴스 속성은 반드시 클래스 메서드 내에 정의되어야 한다

- 정적 (클래스사이드) 속성과 프로토타입 데이터 속성은 반드시 클래스 선언부 바깥쪽에서 정의되어야 한다

// 인스턴스 속성
class Rectangle {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
}

// 정적 속성
Rectangle.staticWidth = 20;

// 프로토타입 데이터 속성
Rectangle.prototype.prototypeWidth = 25;

 

* Private 필드 선언

class Rectangle {
  #height = 0;
  #width;
  constructor(height, width) {
    this.#height = height;
    this.#width = width;
  }
}

 

* Species

class MyArray extends Array {
  // 부모 Array 생성자로 species 덮어쓰기
  static get [Symbol.species]() {
    return Array;
  }
}
var a = new MyArray(1, 2, 3);
var mapped = a.map((x) => x * x);

console.log(mapped instanceof MyArray); // false
console.log(mapped instanceof Array); // true

 

* Mix-ins

- 하나의 단일 슈퍼클래스만을 가질 수 있음

- 기능은 반드시 슈퍼클래스에서 제공

var calculatorMixin = (Base) =>
  class extends Base {
    calc() {}
  };

var randomizerMixin = (Base) =>
  class extends Base {
    randomize() {}
  };

// Mix-in
class Foo {}
class Bar extends calculatorMixin(randomizerMixin(Foo)) {}

 

학습테스트를 통해 JUnit 학습하기.pdf

* 테스트 클래스 이름

명명 규칙: "테스트 대상 클래스명 + Test"

 

* test.each

- Jest에 있는 기능

- AssertJ의 @CsvSource와 비슷한 기능을 함

const testCases = [
  { input: 1, expected: 2 },
  { input: 3, expected: 6 },
  { input: 5, expected: 10 },
];

test.each(testCases)("multiply %p by 2 to get %p", ({ input, expected }) => {
  expect(input * 2).toEqual(expected);
});