본문 바로가기
Frontend/엘리스 SW 엔지니어 트랙

[ 엘리스 SW 엔지니어 트랙 ] 21일차(5주차: 클린코드와 타입스크립트 - 정적타입, 인터페이스, Generic, Decorator)

by YWTechIT 2021. 11. 23.
728x90

📍 21일 차 11.23. 화.(실시간 강의) (클린 코드와 타입 스크립트)

이번 주는 클린코드typescript타입, 클래스, 인터페이스, Generic, Decorator에 대해서 배웠다. 엘리스 엔지니어 SW 트랙을 신청할 당시 제일 기대가 되었던 커리큘럼이 이번 주차에 배우는 내용이었는데, 벌써 그 내용을 듣게 된다니.. 이전보다 더욱 열심히 들어야겠다. 여느 때와 마찬가지로 이론수업과 실습수업으로 나뉘었는데, 실습수업은 김병철 코치님이 가르쳐주셨다. 처음에는 누군지 잘 몰랐는데 알고리즘계에서 꽤 유명한 분이셨다. 개발자 오픈 채팅방에 코딩 테스트 대비 순서에 대해 물어보면 항상 tony9402 - 코딩테스트 대비 문제집부터 풀라는 얘기가 많았는데 그 레포지터리 컨트리뷰터의 2번째(VSfe)분이셨다. 😲😲 저번 이 고잉님이 가르쳐주실 때도 신기했는데 이번에도 엄청 신기했다.. 공부 의지가 한껏 솟아오르는 계기가 되었다.


❏ 클린 코드란

  1. 비즈니스 전문가 → 분석가(제품 관리자 / 서비스 기획자 / 프로그래머) → 프로그래머 → 컴퓨터
  2. 좌측 방향은 목적 / 추상적, 우측 방향은 수단 / 구체적
  3. 시간이 갈수록 코드를 쓰는 것보다 읽는 비중이 늘어난다.(코드 가독성의 중요성이 늘어남)
  4. 코드 재사용(반복되는 문제의 풀이는 재사용 가능, SW 개발 비용 절감)

💡 테스트 기법

  1. 수동 테스트
    • 품질 담당자가 UI를 사용해 기능 검증
    • 사람의 손으로 일일이 테스트한다.(인건비 증가)
    • 소프트웨어 회귀(시간에 따라 기능이 많아지면, 추가한 기능과 기존의 기능과의 충돌로 인해 기존의 있던 코드조차 실행이 안 되는 현상이 발생한다.)
  2. 인수 테스트
    • 배치된 시스템을 대상으로 검증
    • 전체 시스템 이상 여부 신뢰도가 높음
    • 높은 비용(작성 / 관리 / 실행 비용)
    • 피드백 품질이 낮음(현상을 드러나지만 원인은 숨겨짐)
    • 테스터가 품질 외부를 살펴본다.
  3. 자동 테스트
    • 기능을 검증하는 코드를 작성
    • 테스트 코드 작성 비용이 소비되지만 실행 비용이 낮고 결과의 신뢰도가 높음
    • 테스트 코드 작성과 관리가 프로그래머 역량에 크게 영향을 받는다.
  4. 단위 테스트
    • 시스템 일부(하위 시스템)를 대상으로 검증
    • 낮은 비용
    • 높은 피드백 품질
    • 전체 시스템 이상 여부 신뢰도가 낮음
    • 단위끼리 오류가 나는 경우가 있다.
    • 테스터가 프로그램의 기능을 살펴본다.

❏ 타입 스크립트의 역사

  1. ES2015부터 모듈 스펙을 제공하기 시작: 프로젝트의 규모 증가, 개발환경이 복잡해짐, npm 의 등장, 번들러 등장(Webpack 등등) 주석제거 / 파일압축 / 어글리파이, 트랜스파일러 등장 (Babel, Typescript 등등)
  2. TSJS 의 모든 기능을 기본으로 제공한다. (슈퍼셋), 명시적인 데이터에 대한 유형 설명
  3. 자바스크립트보다 타입에 대한 추가적인 정보를 얻을 수 있다.
  4. 안정성 있는 코드를 작성하기 위하여 별도의 타입 체크가 필요함
  5. 예상치 못한 오류가 발생할 수 있음
  6. 타입 체크를 위해 코드가 길어지는 문제 발생
  7. 실제 코드를 실행을 해보기 전까지 오류 체크 불가
  8. 실제 서비스 단계가 아닌 컴파일러 과정에서 오류를 확인할 수 있다.(디버깅, 새로고침 과정까지 가지 않아도 확인 가능함) 보다 안전한 프로그래밍 환경을 제공받을 수 있다.
JS
 - 인터프리터 언어(한줄 한줄 실행)
 - 스크립팅 언어
 - 컴파일러 필요 없음
 - 객체 지향적이지 않음, 프로토타입 기반

TS
 - 컴파일 언어
 - 객체 지향 프로그래밍 언어
 - 컴파일러 필요
 - 클래스 기반, 상속, 인터페이스, 수정자 사용 가능

 

728x90

❏ typescript의 type 알아보기

  1. 인터페이스의 경우, 컴파일하는 과정에서 인터페이스끼리 전부 합쳐지고 해당 인터페이스에 대응하는 객체를 생성해주는 과정을 거칩니다. (JS의 호이 스팅을 떠올릴 수 있지만, 실제 동작 과정은 약간 다릅니다. 일반적으로 인터페이스, 클래스 등의 정의는 최상단에 고정하고 하단에 추가하거나 수정하는 것은 지양하는 편입니다.)
// 타입 유틸리티
// 1. Tuple: 튜플, 보다 엄격하게 타입을 정의하고 싶을 때 사용
let x : [number, string];
x = [27, "AYW"];

// enum
enum Color {
    Red, Green, Blue
}

console.log(Color)
👉🏽 { 0: 'Red', 1: 'Green', 2: 'Blue', Red: 0, Green: 1, Blue: 2 }

 - 값을 할당해주지 않으면 0, 1, 2가 들어간다.

enum Color {
    Red = 2, Green, Blue
  }

console.log(Color[2])
👉🏽 Red

// any
let num: any = "123";

// void: 함수가 리턴하는 값이 없을 때 사용하는 타입
function warnUser(): void{
  console.log("This is my warning message")
}

function temp(age: number): number{
  return age;
}

// null
let none: null = null;

// undefined
let done: undefined = undefined;

// never
function error(message: string): never{
  throw new Error(message);
}

function infiniteLoop(): never {
  while(true){
    /* 기능 */  
  }
}

// 타입 별칭
let x: number = 10;
let xPositina: number = 10;

type YesOrNo = string;
type YesOrNoDetail = "Y" | "N";

let sayMe: YesOrNo = "HI";
let sayYou: YesOrNoDetail = "Y"

// 인자가 없고 리턴 값이 string 타입인 type 지정
type FooFunction = () => string;
let temp: FooFunction = () => {
  return "temp"
}

// interface

type Name = "AYW";

interface IUser{
  id: number;
  name: Name;
  email: string;
  age: number;
}

const myInfo: IUser = {
    id: 1,
    name: "AYW",
    email: "any",
    age: 27
}

// interface는 속성 값끼리 합칠 수 있다. 
interface IUser{
  id: number;
  name: Name;
  email: string;
  age: number;
}

interface IUser {address: string}

// type 별칭은 불가능하다.
type IUser{
  id: number;
  name: Name;
  email: string;
  age: number;
}

type IUser {address: string}
👉🏽 Error. Duplicate identifier 'IUser'.

// keyof: 타입에 있는 속성값들을 하나의 타입으로 묶는다.
interface User {
    id: number;
    name: string;
    age: number;
    gender: "male" | "female";
}

type UserKey = keyof User;
// 'id' | 'name' | 'age' | 'gender'

const uk: UserKey = "name"
console.log(uk)

❏ type utility 알아보기

  1. typescript는 일반적인 타입 변환을 쉽게 하기 위해서 몇 가지 유틸리티 타입을 제공합니다. 이러한 유틸리티는 전역으로 사용 가능합니다.
// Partial<T>: type 집합의 모든 프로퍼티를 선택적으로 타입을 생성한다.
// Partial 대신 ?를 써도 된다.
interface User {
    id: number;
    name: string;
    age: number;
    gender: 'male' | 'female'
}

let admin: Partial<User> = {
    id: 1,
    name: "AYW",
}

console.log(admin);

// Readonly<T>: 처음 설정한 값외에 속성을 수정 할 수 없다. 재할당 금지
interface User {
    id: number;
    name: string;
    age: number;
    gender: 'male' | 'female'
}

let admin: Readonly<User> = {
    id: 1,
    name: "AYW",
    age: 27,
    gender: "male"
}

admin.id = 3;
console.log(admin)

👉🏽 Cannot assign to 'id' because it is a read-only property.

// Readonly
interface User {
    id: number;
    name: string;
    age: number;
    readonly gender: 'male' | 'female'
}

let admin: User = {
    id: 1,
    name: "AYW",
    age: 27,
    gender: "male"
}

admin.id = 3;
admin.gender = "female";  // error
console.log(admin)
👉🏽 Cannot assign to 'gender' because it is a read-only property.

// Record<K, T>: 개체의 속성과 타입을 정의할 때 사용하는 메소드
type Grade = "1" | "2" | "3" | "4";
type Score = "A" | "B" | "C" | "D";

const score: Record<Grade, Score> = {
    1: "A",
    2: "B",
    3: "C",
    4: "D",
}

console.log(score)

// Record<K, T>: 타입의 프로퍼티 키의 집합으로 타입을 생성한다. 개체의 속성과 타입을 정의할 때 사용하는 메소드
interface User {
    id: number;
    name: string;
    age: number;
}

function isValid(user: User){
    const result: Record<keyof User, boolean> = {
        id: user.id > 0,
        name: user.name !== '',
        age: user.age > 0,
    }
    return result;
}

// Pick<T, K>: Type에서의 Key값을 선택하여 새로 type으로 정하는 기능
interface User {
    id: number;
    name: string;
    age: number;
    gender: "male" | "female";
}

const admin: Pick<User, "id"|"name"> = {
    id : 0,
    name: "TED"
}

console.log(admin)

// Omit<T, K>: Type에서의 Key값을 선택하여 사용하지 못하게 한다.
interface User {
    id: number;
    name: string;
    age: number;
    gender: "male" | "female";
}

const admin: Omit<User, "id"|"name"> = {
    age: 27,
    gender: "male"
}

console.log(admin)

// Exclude<T1, U>: 속성 대신 Type의 key 값을 제외한다.
type T1 = string | number | boolean;
type T2 = Exclude<T1, number | string>;

let isNum: T2 = 3;
👉🏽 Type 'number' is not assignable to type 'T2'.

let isNum: T2 = true;

// NonNullable<T>: undefined와 null 타입을 제거한다.
type T1 = string | null | undefined | void;
type T2 = NonNullable<T1>;

let empty: T2 = null;
👉🏽 Type 'null' is not assignable to type 'T2'.
let empty: T2 = "Hi";

// Parameters<T>: 함수 타입을 인자로 받아서 튜플타입으로 리턴해주는 문법
type T0 = Parameters<() => string>;  // []
type T1 = Parameters<(s: string) => void>;  // [string]
type T2 = Parameters<(s: string, i: number) => number>

let T1Arr: T1 = ["123"];

// ReturnType<T>: return type값을 정의하는 문법
type T0 = ReturnType<() => string>;

function returnString(): T0{
    return "123"
}

type T7 = ReturnType<any> // any
type T8 = ReturnType<never> // any

// Required<T>: partial과 반대되는 기능으로 type집합의 모든 프로퍼티를 필수로 설정한다.
interface User {
    id: number;
    name?: string
}

let admin: Required<User> = {
    id: 1,
}

👉🏽 Property 'name' is missing in type '{ id: number; }' but required in type 'Required<User>'.

let admin: Required<User> = {
    id: 1,
        name: "AYW"
}

❏ 타입 스크립트의 함수

  1. 일급 객체(first-class object): 함수를 변수로 선언하고, 함수에 인자를 함수로 넘길 수 있고, 함수의 리턴 값을 함수로 설정할 수 있는 것을 일급 객체라 부릅니다. JS와 TS는 모두 일급 객체입니다.
  2. 일급 객체의 특징 때문에 고차 함수, 콜백 함수를 만들 수 있다.
// 함수 자체를 값으로 저장한다.
let sum = function(a, b){
  return a + b;
}

function ul(child){
  return `<ul>${child}</ul>`
}

// 함수를 리턴할 수 있다.
function makeLi(container, contents){
  const liList = [];

  for (const content of contents){
    liList.push(`<li>${content}</li>`)
  }

  return container(liList.join(""))
}

// 함수를 함수의 인자로 넣어줄 수 있다.
const htmlUl = makeLi(ul, ['월', '화', '수', '목', '금', '토', '일'])
  1. 함수의 선언문, 선언식
    // 함수 선언문
    function sum(a, b){
     return a+b;
    }
    
    // 함수 표현식
    const myFunc = function(a, b){
    return a+b;
    }

4. 함수의 가변인자

function sum(a, b){
    return a+b;
}

const abcSum = sum(10, 20, 30);

// 가변 인자식
function sum(){
  let s = 0;
    for (let i=0; i< arguments.length; i++){
        s+=arguments[i];
    }
    return s;
}

// arguments
function sum(...args){
  let s = 0;
    for (let i=0; i< args.length; i++){
        s+=args[i];
    }
    return s;
}
  1. 함수 호출
    // call과 apply는 인자를 하나씩 넘겨주냐 배열로 넘겨주냐의 차이
    sum(10, 20, 30, 40);
    // 컨텍스트 null
    sum.call(null, 10, 20, 30, 40);
    
    arr = [10, 20, 30, 40];
    sum.apply(null, arr);
// 즉시 실행 함수
(function(){
  console.log(123);
})

// 일반함수와 화살표함수의 차이:  컨텍스트의 차이점
const sumV2 = (a, b, ...args) => {

}

const ten = () => 100;
const hundred = x => 100 + x;

// 생성기 함수(generate function): 함수를 한번에 실행시키는 것이 아니라 텀을 두고 실행할 때
function* gen(){
    yield 10;
    yield 20;
    return 30;
}

const g = gen();

console.log(g.next());  // 10
console.log(g.next());  // 20
console.log(g.next());  // 30
  1. 함수 작성 시 반환 타입 명시 권장(필수는 아님, TS 컴파일러는 방정식의 한쪽에만 타입이 있더라도 타입을 추론할 수 있다.)
  2. 매개변수와 인수의 타입이 호환 가능하게 작성, 인수 타입의 전달이 잘못된 경우 오류 발생
// 함수를 안전하게 만들 수 있다.
interface MathFn {
    (a: number, b: number): number;
    operator: string;
};

const sum: MathFn = (a, b) => a + b;
sum.operator = "+";

1. void: 반환값이 없는 함수입니다. (그냥 출력하거나, 상태를 바꿀 때 자주 씁니다.)
2. never: 함수가 종료되지 않습니다. (무한루프를 돌거나, Error를 띄울 때 사용합니다.)
  1. 함수의 매개변수: 함수에 주어진 인자의 개수는 함수가 기대하는 매개변수의 수와 일치해야 함
function sum(a: number, b:number, type?: string): number{
    return a+b;
}

let sumAge = sum(2, 3, "ted");

function buildName(firstName: string, lastName="Smith") {
    return firstName + ' ' + lastName;
}

let result1 = buildName("Bob");
let result2 = buildName("Bob", undefined);
let result3 = buildName("Bob", 'Ted');
console.log(result1)
👉🏽 "Bob Smith" 


[LOG]: "Bob Smith" 
[LOG]: "Bob Ted"

console.log(result2)
console.log(result3)

undefined가 넘어가면 lastName이 없다고 판단되지만, null이 들어가면 lastName이 있다고 판단을 합니다. 
(undefined는 값 자체가 없다고 판단하나, null은 값은 있는데 의미없는 특별한 값이 들어있다고 보시면 좋을 것 같아요.)
+ undefined는 미리 선언된 global variable이나, null은 키워드입니다.

❏ 객체 지향 프로그래밍(OOP)

  1. OOP 는 컴퓨터 프로그램을 객체의 모임으로 파악하려는 프로그래밍 패러다임
  2. 프로그램을 유연하고, 변경이 용이하고, 개발과 보수를 간편하게 만든다, 직관적인 코드 분석이 가능해진다.
  3. 클래스 요소: 필드(field), 생성자(constructor, 객체가 처음 생성될 때 호출, 멤버 변수 초기화), 메서드(method)
  4. 인스턴스: new 연산자에 의해서 생성된 객체
class Person {
    name: string;
    constructor(name: string){
        this.name = name
    }
    say(){
        return `Hello, ${this.name}!`
    }
}

let person = new Person("AYW");
console.log(person.say())

❏ 클래스 만들기

  1. 생성자의 매개변수에 public 과 같은 접근 제한자를 붙이면 해당 매개변수의 이름을 가진 속성이 클래스에 선언된 것처럼 동작합니다.
// public 접근 제한자
class Information {
  constructor(public name: string, public age: number){}
}

const myInfo = new Information("AYW", 27);
console.log(myInfo)
👉🏽 { name: 'AYW', age: 27 }

// this
class Information {
    name: string;
    age: number
  constructor(name: string, age: number){
    this.name = name;
    this.age = age;
  }
}

const myInfo = new Information("AYW", 27);
console.log(myInfo)
👉🏽 { name: 'AYW', age: 27 }

❏ 클래스 상속받기

  1. 부모 클래스를 상속받는 키워드: extends
class AYW extends Information{
  constructor(public name: string, public age: number, gender: "male" | "female"){
    super(name, age)
  }

  sayHello(): void {
    console.log(`제 이름은 ${this.name}입니다.`)
  }
}

const myInfo: AYW = new AYW("AYW", 27, "male")
console.log(myInfo);
👉🏽 AYW { name: 'AYW', age: 27 }

myInfo.sayHello();
👉🏽 제 이름은 AYW입니다.

❏ 추상 클래스

  1. 추상 클래스는 자신의 속성이나 메서드 앞에 abstract를 붙여 나를 상속하는 다른 클래스에서 이 속성이나 메서드를 구현하게 합니다.
abstract class Information {
    name: string;
    age: number;
    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }

    abstract sayHello(): void;
}

class AYW extends Information {
    constructor(
        public name: string,
        public age: number,
        gender: "male" | "female"
    ) {
        super(name, age);
    }

    sayHello(): void {
        console.log(`제 이름은 ${this.name}입니다.`);
    }
}

const myInfo: AYW = new AYW("AYW", 27, "male");
console.log(myInfo);
👉🏽 AYW { name: 'AYW', age: 27 }

myInfo.sayHello();
👉🏽 제 이름은 AYW입니다.
반응형

댓글