그럼에도 불구하고

👨‍💻

[CleanCode] 객체 다루기 본문

JavaScript/Clean code

[CleanCode] 객체 다루기

zenghyun 2023. 6. 2. 18:09

오늘은 객체를 다루는 여러 가지 방법에 대해 알아보겠습니다.

 

목차

     

    [ Shortan Properties Name ]

     

     

    객체를 사용하여 key : value를 지정할 때 변경 전과 같이 지정하는 경우를 종종 볼 수 있는데요. ( 저만 그런가요..? ㅎㅎ )

    변경 후와 같이 더 깔끔하게 지정해 줄 수 있습니다. 

     

    그리고 ES2015 이후부터는 key와  value의 이름이 같다면 한 번만 써도 된다는 거 잊지 말아 주세요!!

     

    📌 예시

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // 변경 전 
     
    const person = {
    name : name
    age : age, 
    getInfo : getInfo, 
     
    }; 
     
    // 변경 후
     
    const person = {
    name,
    age,
    getInfo
     
    };
    cs

     

    [ Computed Property Name ]

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    /**
     * Computed Property Name
     */
     
    import React, { useState, Fragment } from 'react';
     
    const [state, setState] = useState({
        id: '',
        password: '',
    });
     
    const handleChange = e => {
        setState({
            [e.target.name] : e.target.value, // [e.target.name]과 같이 property를 동적으로 다룰 수 있음 
        });
    };
     
    return (
        <React.Fragment>
            <input value={state.id} onChange={handleChange} name="password"/>
            <input value={state.password} onChange={handleChange} name="name"/>
        </React.Fragment>
    )
    cs

     

    react에서 자주 사용하는 구문의 예시를 가져와봤습니다. 

     

    handleChange 메서드의 setState안에 지정하는 객체의 값을 보면 key를 [e.target.name]과 같이 property를 동적으로 다루는 것을 볼 수 있습니다. 

     

    이렇게 하면 input태그에 각각 onChange 메서드를 만들어 사용할 필요가 없이 하나의 메서드로 둘 다 이용할 수 있게 됩니다. 

     

    이 방법은 react의  reducer에서도 마찬가지입니다.

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    /**
     * Computed Property Name
     */
     
    const noop = createAction('INCREMENT');
     
    const reducer = handleActions(
        {
            [noop]: (state, action) => ({
                counter : state.counter + action.payload,
            }),
        },
        { counter : 0},
    );
    cs

     

     

     

    [ Lookup Table (순람표) ]

     

    혹시 엑셀의 Lookup Table을 본 적 있으신가요? 

     

    https://www.educba.com/lookup-table-in-excel/

    엑셀을 사용해보지 않으셨을 분들을 위해 간단하게 말씀드리자면 Lookup Table은 key와 그에 따른 value가 정해진 테이블을 말합니다.

     

    즉. 데이터의 구조가 key와 value로 나열된 것을 말합니다. 

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    /**
     * Object Lookup Table
     */
     
    // 변경 전
    function getUserType1(type) {
        if (type === "ADMIN") {
          return "관리자";
        } else if (type === "INSTRUCTOR") {
          return "강사";
        } else if (type === "STUDENT") {
          return "수강생";
        } else {
          return "해당 없음";
        }
      }
      
      // 개선 1  (보다 깔끔하지만 case가 추가될 때마다 나열되는건 다름없다.)
      
      function getUserType2(type) {
        switch (key) {
          case "ADMIN":
            return "관리자";
          case "INSTRUCTOR":
            return "강사";
          case "STUDENT":
            return "수강생";
          case "STUDENT2":
            return "수강생";
          case "STUDENT3":
            return "수강생";
          default:
            return "해당 없음";
        }
      }
      
      // 개선 2 (개인적으로 이게 가장 좋다.)
      
      function getUserType3(type) {
        const USER_TYPE = {
          ADMIN: "관리자",
          INSTRUCTOR: "수강생",
          STUDENT: "학생",
          UNDEFINED: "해당 없음",
        };
      
         return USER_TYPE[type] ?? USER_TYPE.UNDEFINED; // ver 1
     
        //  return USER_TYPE[type] ?? USER_TYPE["UNDEFINED"];  ver 2
      }
      
      console.log(getUserType3("ADMIN")); // 관리자 
      console.log(getUserType3("qesf")); // 해당 없음 
      
      // 개선 3
      
      function getUserType4(type) {
        return {
          ADMIN: "관리자",
          INSTRUCTOR: "수강생",
          STUDENT: "학생",
        }[type] ?? "해당 없음";
      }
      
      console.log(getUserType4("ADMIN")); // 관리자
      console.log(getUserType4("qesf")); // 해당 없음 
      
    cs

     

    위와 같이 type에 따른 출력값을 반환해주어야 할 때, 처음 코딩을 배워보는 개발자나 학생들의 경우 보통 else if 구문을 많이 이용합니다.

     

    하지만 type이 추가됨에 따라 계속되는 else if의 추가는, 명시적으로 봤을 때 바람직하지는 않습니다.  그렇기 때문에 보완하고자 switch문을 사용하면 보다 깔끔하지만, 이 역시 case가 추가될 때마다 나열되는 건 다름없습니다. 

     

    그럴 때는 객체를 이용하면 보다 깔끔하게 작성할 수 있습니다. 

     

     

    개선 2의 경우 객체로 key : value 값을 지정해 준 후 네이밍 체이닝 연산자를 통해 결과 값을 반환하고 있습니다. 

     

    이렇게 작성하면 새로운 타입이 추가될 때마다 key: value 값만 지정해 주면 됩니다. 

     

    또한 이 객체를 외부 파일로 빼놓은 후 모듈화 해서 따로 관리하기도 용이합니다. :) 

     

    [ Object Destructuring ]

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    /**
     * Object Destructuring
     */
     
    // 변경 전
     
    function Person1(name, age, location) {
      this.name = name;
      this.age = age;
      this.location = location;
    }
     
    const zenghyun1 = new Person1("zenghyun"27"korea");
     
    // 개선 1 
     
    function Person2({ name, age, location }) {
      this.name = name;
      this.age = age ?? 27;  // ?? 를 사용하여 null이나 undefined일 경우 27 반환
      this.location = location ?? 'korea'// ?? 를 사용하여 null이나 undefined일 경우 korea를 반환
    }
     
    const zenghyun2 = new Person2({
      age: 27,
      name"zenghyun",
      location"korea",
    }); // 이렇게 바꾸면 매개변수의 순서를 지키지 않아도 되고, age와 location의 경우 값을 넣지 않아도 된다.
     
     
    // 개선 2
     
    function Person3( name, { age, location }) { // name은 필수
        this.name = name;
        this.age = age;
        this.location = location;
      }
      
      const zenghyunOptions = ({
        age: 27,
        location"korea",
      });
     
      const zenghyun3 = new Person3('zenghyun', zenghyunOptions);
     
     
    cs

     

    변경 전의 함수 구성은 흔히들 가장 쉽게 작성하는 로직입니다만, 매개변수를 순서대로 입력을 해주어야 한다는 단점과, 기본 값을 지정해주지 않았기 때문에 값을 입력하지 않으면 오류가 발생할 수 있습니다.

     

    이럴 때 아래의 개선과 같은 방식을 유지보수에 있어 보다 깔끔하게 이용할 수 있습니다.

     

    매개변수의 순서를 지키지 않아도 되고, 특정 인자의 값을 넣지 않아도 상관없게 되죠 

     

    다른 예시를 살펴보겠습니다. 

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    /**
     * Object Destructuring
     */
     
    // 변경 전
     
    const orders = ["First""Second""Third"];
     
    const [first, , third] = orders;
     
    console.log(first); // First
     
    console.log(third); // Third
     
    // 변경 후 (이렇게도 가능하다.)
     
    const { 0: st, 2: rd } = orders;
     
    console.log(st); // First
     
    console.log(rd); // Third
    cs

     

    배열 안에서 원하는 값을 따로 지정하고 싶을 때 아래와 같은 방법을 사용하고는 합니다.

     

    const orders = ["First", "Second", "Third"];
    
    const [first, , third] = orders;

     

    이 방법도 문제는 없지만 객체 분해할당을 이용해 보는 건 어떨까요? 

     

    객체 분해할당을 이용하면 기존의 방식에서 내가 필요하지 않은 값을 ,를 통해 공백으로 나열해야 했던 작업을 하지 않아도 됩니다.

     

    [ Object.freeze ]

     

    객체 안의 값을 유지하기 위해서 사용하는 Object.freeze의 경우 shallow copy만 가능하기 때문에 deep copy와 같은 경우 개발자가 원하는 대로 동작하지 않는 결과를 나타내고는 합니다.

     

    이럴 때는 deep copy에 대한 대안책을 만들어서 사용할 수 있고, 혹은 TypeScript의 readonly를 통해 간단하게 해결할 수 있습니다.

     

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    /**
     * Object.freeze
     * 
     * 1. 대중적인 유틸 라이브러리 사용 (lodash)
     * 2. 직접 유틸 함수 생성 
     * 3. TypeScript => readonly
     */
     
    const STATUS = Object.freeze({ // shallow copy
        PENDING: 'PENDING',
        SUCCESS: 'SUCCESS',
        FAIL: 'FAIL',
        OPTIONS: {
            GREEN : 'GREEN',
            RED: 'RED',
        },
    });
     
    STATUS.PENDING = 'pending';
    STATUS.PLUS = 'PLUS';
     
    console.log( STATUS );
    console.log( Object.isFrozen(STATUS) );
     
    // shallow copy vs deep copy 
     
    // Object.freeze는 shallow copy를 한다.
     
    STATUS.OPTIONS.GREEN = 'green';
    STATUS.OPTIONS.YELLOW = 'YELLOW';
     
    console.log( Object.isFrozen(STATUS.OPTIONS) );
    console.log( STATUS.OPTIONS); // 수정됨
     
     
    // deep freeze 직접 구현 
     
    function deepFreeze(targetObj) {
        /**
         * 1. 객체를 순회
         * 2. 값이 객체인지 확인
         * 3. 객체이면 재귀
         * 4. 그렇지 않으면 Object.freeze
         */
     
        // 예시
        Object.keys(kargetObj).forEach(key => {
            // if(/* 객체가 맞다면 */) {
            //     deepFreeze(targetObj[key])
            // }
        })
        return Object.freeze(targetObj);
    }
     
    cs

     

    Object.freeze에 대해 보다 더 자세하게 알고 싶으면 아래의 게시물을 참고해 주세요! 

     

    https://despiteallthat.tistory.com/59

     

    [JavaScript] Immutability와 mutable

    오늘은 Immutability와 mutable에 대해 알아보자 [ Immutability(변경 불가성)] Immutability(변경 불가성)는 객체가 생성된 이후 그 상태를 변경할 수 없는 디자인 패턴을 의미한다. Immutability은 함수형 프로그

    despiteallthat.tistory.com

     

    [ Prototype 조작 지양하기 ]

     

    예전에는 prototype을 조작하여 내가 원하는 메서드를 직접 만들고는 했지만 요즘 같이 JS가 많이 발전된 환경에서는 위험한 결과를 초래할 수 있습니다. 

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    /**
     * Prototype 조작 지양하기
     * 
     * 1. 이미 JS는 많이 발전했다.
     *  1-1. 직접 만들어서 모듈화
     *  1-2. 직접 만들어서 모듈화 => 배포 => NPM 
     * 2. JS 빌트인 객체를 건들지말자
     */
     
    // 클래스가 생기기 이전
     
    function Car(name, brand) {
      this.name = name;
      this.brand = brand;
    }
    Car.prototype.sayName = function () {
      return this.brand + "-" + this.name;
    };
     
    // 클래스가 생긴 이후
     
    class Car{
      constructor(name, brand) {
        this.name = name;
        this.brand = brand;
      }
     
      sayName() {
        return this.brand + "-" + this.name;
      }
    };
     
    const casper = new Car("캐스퍼""현대");
     
    console.log(casper);
    cs

     

    [  hasOwnProperty ]

     

    자바스크립트는 프로퍼티 명칭으로서 hasOwnProperty를 보호하지 않습니다. 그러므로 이 명칭을 사용하는 프로퍼티를 가지는 개체가 존재하려면, 올바른 결과들을 얻기 위해서는 외부 hasOwnProperty를 사용해야 합니다.

     

    hasOwnProperty를 사용하려고 호출했을 때 다른 hasOwnProperty가 호출될 수 있어 위험합니다.

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    /**
     * hasOwnProperty
     */
     
     const person = {
        name : 'zenghyun',
     };
     
     console.log( person.hasOwnProperty('name') );  // true
     
     console.log( person.hasOwnProperty('age') ); // false 
     
     const foo = {
        hasOwnProperty: function() {
            return 'hasOwnProperty';
        },
        bar: 'string',
     };
     
     // hasOwnProperty은 보호받지 못한다. 
     console.log( foo.hasOwnProperty('bar') ); //' hasOwnProperty'
     
     console.log( Object.prototype.hasOwnProperty.call(foo, 'hasOwnProperty')); // true
     
    cs

     

     

    [ 직접 접근 지양하기 ] 

     

    객체를 직접 건드는 영역을 분리하지 않고 호출하는 것과, 객체를 직접 건드는 영역을 분리한 후에 호출하는 방법에는 차이가 있습니다. 

     

    객체를 직접 건드는 영역을 분리하여 호출했을 경우 모델에 접근하는 권한을 확실히 설정할 수 있고, 어디서나 모델에 접근할 수 있던 방법을 지양하고 함수에 위임하여 추상화할 수 있습니다. 

     

    이런 방법을 이용하면 모델이 어디서 어떻게 변화하는지도 추적 가능하고 모델을 보다 안전하게 사용할 수 있습니다. 

     

    아래의 예시를 살펴보겠습니다.

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    /**
     * 직접 접근 지양하기
     * flux => action => reducer => state 변화 
     * 예측 가능한 코드를 작성해서 동작이 예측 가능한 앱을 만들자
     */
     
    const model = {
        isLogin: false,
        isValidToken: false,
    };
     
    // model 직접 접근 불가 
    function login() {
       model.isLogin = true;
       model.isValidToken = true;    
     
    // model 직접 접근 불가 
    function logout() {
        model.isLogin = false;   
        model.isValidToken = false;    
    }
     
    someElement.addEventListener('click', login);
    cs

     

    위의 코드는 login 메서드와 logout 메서드를 통해 모델에 직접 접근하고 있습니다. 이를 구조화해서 살펴보면, 

     

    login & logout 

     

    1. model에 직접 접근한다.  

     

    2. model 객체의 isLogin과 isValidToken의 boolean 값을 변경한다. 

     

    겉보기에는 문제가 없어 보일 수도 있지만, 하나의 함수는 최소한의 기능을 수행한다는 개념에서 살펴보면 지금 함수는 두 개의 기능을 하고 있습니다.

     

    model에 직접 접근하는 함수를 따로 분리하고, login과 logout에서는 그 함수를 호출하는 방식으로 바꾸면 어떨까요? 

     

    또한 이 방법을 이용하여 아래와 같이 setLogin과 setValidToken 메서드를 따로 만들어 놓으면 이 메서드를 실행할 때마다 serverAPI의 log를 출력하는 작업도 할 수 있습니다. ( 할 수 있다의 가능성을 염두에 두기보다는 메서드의 이름에 맞는 보다 직관적인 역할을 수행할 수 있죠 ) 

     

    serverAPI의 log를 만약 기존의 방식에서 login과 logout에서 이용한다면, 혹은 다른 기능을 추가한다면 

     

    하나의 함수는 최소한의 기능을 수행한다는 개념에 더욱 거리가 멀어지게 되니까요.

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    /**
     * 직접 접근 지양하기
     * flux => action => reducer => state 변화 
     * 예측 가능한 코드를 작성해서 동작이 예측 가능한 앱을 만들자
     */
     
     
    //  이 model에 너무 쉽게 접근하고 있다. 모델에 직접 접근 지양
    const model = {
        isLogin: false,
        isValidToken: false,
    };
     
    // model 대신 접근
    function setLogin(bool) {
        model.isLogin = bool; 
        serverAPI.log(model.isLogin);
    }
     
    // model 대신 접근
    function setValidToken(bool) {
        model.isValidToken = bool;
        serverAPI.log(model.isValidToken);
    }
     
     
    // model 직접 접근 불가 
    function login() {
        setLogin(true);
        setValidToken(true);
    }
     
    // model 직접 접근 불가 
    function logout() {
        setLogin(false);
        setValidToken(false);
    }
     
    someElement.addEventListener('click', login);
    cs

     

    [ Optional (선택적) Chaning (?.) ]

    앞선 cleancode 게시물에서 언급했던 Optional Chaning 연산자와 Nullish Coalescing 연산자는 아래의 게시물에서 보다 자세히 확인할 수 있습니다. 

     

    https://despiteallthat.tistory.com/54

     

    [JavaScript] 단축 평가

    오늘은 단축 평가에 대해 알아보자 [ 논리 연산자를 사용한 단축 평가 ] 논리 합 (| |) 또는 논리 곱 (&&) 연산자 표현식은 언제나 2개의 피연산자 중 어느 한쪽으로 평가된다. 다음 예제를 살펴보자

    despiteallthat.tistory.com

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    /**
     * Optional Chaining (?.)
     */
     
    const obj = {
        name'value'
    };
     
    console.log( obj?.name );
     
     
    const response = {
        data : {
            userList: [
                {
                    name'Lee',
                    info: {
                        tel: '010',
                        email: 'zenghyun@naver.com'
                    }
                }
            ]
        }
    };
     
    console.log( response.data.userList[0].info.email );
     
    // 만약에서 서버에서 데이터를 가져올 때 특정 부분이 유실된 상태라면? 
     
    // 특정 부분 유실에 대한 if문을 이용하면 아래와 작성할 수 있다. 이는 꽤나 번거롭고, 복잡하다. 
     
    function getUserEmailByIndex(userIndex) {
        if(response.data) {
            if(response.data.userList) {
                if(response.data.userList[userIndex]) {
                   // some code 
            }
        }
    }
     
    // 옵셔널 체이닝을 이용하면 오류에 보다 깔끔하게 대응한다. 
     
    function getUserEmailByIndex(userIndex) {
        if(response?.data?.userList?.[userIndex]?.info?.email) {
            return  response.data.userList[userIndex].info.email;
        }
        return '알 수 없는 에러가 발생했습니다.';
    }
     
    console.log( getUserEmailByIndex(0) ); // zenghyun@naver.com
     
    console.log( getUserEmailByIndex(1) ); // 알 수 없는 에러가 발생했습니다.
     
    cs

     

    'JavaScript > Clean code' 카테고리의 다른 글

    [CleanCode] 추상화하기  (0) 2023.06.05
    [CleanCode] 함수 다루기  (1) 2023.06.04
    [CleanCode] 배열 다루기  (0) 2023.06.01
    [CleanCode] 분기다루기  (0) 2023.05.30
    [CleanCode] 경계 다루기  (0) 2023.05.25
    Comments