내가 쓴 최악의 코드를 재구성하다

최근'개발자 자백'에 대한 채팅에서 나는 3년 전 첫 개발 작업을 시작했을 때 내가 무엇을 하고 있었는지 정말 몰랐다는 것을 인정했다.나의 경험이 부족하다는 것을 증명하기 위해서, 나는 내가 당시에 작성하고 있던 코드의 예를 공유했다.


아크 슈라그

솔직히: 나는 처음 일을 시작했을 때 완전히 소란을 피웠다.이것은 내가 가장 좋아하는 코드 괴물 중의 하나다.😂 나는 지금 순환을 어떻게 사용하는지 안다고 보증한다.
2019년 4월 3일 새벽 1:13
34
548
내가 얻은 응답의 절대 다수는 적극적이다.우리들 대부분은 엉망진창인 코드를 쓴 적이 있다. 우리는 자랑스럽지 않다. 그러나 낡은 코드를 돌이켜보면 더 좋을 수도 있고, 자신의 선택을 비웃을 수도 있다는 것을 깨달았을 때, 이것은 성장의 징조이다.계속 공부하는 정신에 따라 오늘 이 문제를 해결할 수 있는 방법을 공유하고 싶습니다.
* 이 코드는 어리석고 효율적으로 작성할 수 있지만 하드코딩은 필요한 작업을 완성했다.

배경과 목표


남겨진 코드를 재구성하기 전에 한 걸음 뒤로 물러나 코드가 작성된 상하문을 평가하는 것이 중요하다.개발자가 미친 듯이 선택하는 데는 상하 문장의 영향을 받아 중요한 원인이 있을 수 있으며, 코드라면 깨닫지 못할 수도 있다.나의 예에서, 나는 단지 경험이 부족할 뿐이기 때문에 이 코드를 안전하게 재구성할 수 있다.
코드는 두 가지 데이터 시각화에 사용된다. "Global Foreign Direct Investment Stocks"(입력/출력)과"China Bilateral Investment Outflows"(중국).그것들은 비슷한 데이터와 기능을 가지고 있으며, 주요 목표는 사용자가 유형, 연도, 지역별로 필터를 해서 데이터 집합을 조회할 수 있도록 하는 것이다.나는 전 세계 데이터에 중점을 두지만 중국 데이터 집합은 유사한 방식으로 재구성할 수 있다.

필터 중 하나를 변경하면 다음 값이 반환된다고 가정합니다.
    let currentType = 'in' // or 'out'
    let currentYear = 2017
    let currentRegions = ['Africa', 'Americas', 'Asia', 'Europe', 'Oceania']
주의: 지역 콤보 상자는 현재 이런 방식으로 작동하지 않기 때문에 코드 세그먼트에 '전부' 와 '부분' 이 있습니다. 그러나 이것은 반드시 이렇게 해야 합니다.
마지막으로 CSV에서 데이터를 로드한 후 데이터 자체를 단순화하는 예입니다.
    const data = [
      { country: "Name", type: "in", value: 100, region: "Asia", year: 2000 },
      { country: "Name", type: "out", value: 200, region: "Asia", year: 2000 },
      ...
    ]
    // Total Items in Array: ~2,400

옵션 1: 빈 객체 초기화


하드코딩을 제외하고, 나의 최초의 코드 세션은 '자신을 반복하지 마라' (DRY) 의 코드 작성 방법을 완전히 위반했다.물론 어떤 경우 자신을 반복하는 것은 의미가 있지만 이런 상황에서 같은 속성이 반복될 때 동적 창설 대상은 더욱 현명한 선택이다.이렇게 하면 데이터에 새해에 필요한 수동 작업량을 집중적으로 추가하고 입력 오류를 제한할 수 있다.
몇 가지 다른 방법이 그것을 더욱 건조하게 할 수 있다. for, .forEach, .reduce 등등. 나는 .reduce 수조 방법을 사용할 것이다. 왜냐하면 그것은 하나의 수조를 처리하고 다른 대상으로 바꾸기 때문이다. (우리의 예에서 하나의 대상이다.)우리는 .reduce 세 번, 매번 한 번 사용할 것이다.
범주를 상수로 선언하는 것부터 시작합니다.앞으로 우리는 years 어레이에 새해를 추가하기만 하면 됩니다.우리가 작성할 코드는 나머지 부분을 처리할 것이다.
    const types = ['in', 'out']
    const years = [2000, 2005, 2010, 2015, 2016, 2017]
    const regions = ['Africa', 'Americas', 'Asia', 'Europe', 'Oceania']
이걸 유형으로 보는 게 아니라.→ 년→ 구역, 우리는 그것을 역전시키고 싶다. 구역부터 시작하자.regions가 객체가 되면 해당 객체는 연도 속성에 지정된 값이 됩니다.여러 해 동안 유형적으로도 그랬다.더 적은 코드 줄로 이 글을 쓸 수 있지만, 나는 똑똑한 것이 아니라 명확한 것을 선택했다.
    const types = ['in', 'out']
    const years = [2000, 2005, 2010, 2015, 2016, 2017]
    const regions = ['Africa', 'Americas', 'Asia', 'Europe', 'Oceania']

    /*
      Convert regions to an object with each region as a property and 
      the region's value as an empty array.
    */
    const regionsObj = regions.reduce((acc, region) => {
      acc[region] = []
      return acc
    }, {}) // The initial value of the accumulator (`acc`) is set to `{}`. 

    console.log(regionsObj)
    // {Africa: [], Americas: [], Asia: [], Europe: [], Oceania: []}
이제 우리는 지역 대상이 생겨서 연도와 유형에 있어서 비슷한 일을 할 수 있다.그러나 영역처럼 값을 빈 그룹으로 설정하지 않고 이전 클래스의 대상으로 설정합니다.
편집: 원본 코드 세그먼트에 데이터를 불러오려고 할 때, 이것은 사실상 작동하지 않습니다. 왜냐하면 나는 기존의 대상을 인용했을 뿐이지, 새로운 대상을 실례화한 것이 아닙니다.다음 코드 세션이 업데이트되었습니다. 기존 대상의 깊이 있는 복사본을 만들어서 이 문제를 복구합니다.Lukas Gisder Dubé는 "How to differentiate between deep and shallow copies in JavaScript"의 이 글에서 해석을 내놓았다.
    function copyObj(obj) {
      return JSON.parse(JSON.stringify(obj))
    }

    /* 
      Do the same thing with the years, but set the value 
      for each year to the regions object.
    */
    const yearsObj = years.reduce((acc, year) => {
        acc[year] = copyObj(regionsObj)
      return acc
    }, {})

    // One more time for the type. This will return our final object.
    const dataset = types.reduce((acc, type) => {
      acc[type] = copyObj(yearsObj)
      return acc
    }, {})

    console.log(dataset)
    // {
    //  in: {2000: {Africa: [], Americas: [],...}, ...},
    //  out: {2000: {Africa: [], Americas: [], ...}, ...}
    // }
우리는 현재 나의 원시 코드 세션과 같은 결과를 얻었지만, 이미 기존의 코드 세션을 성공적으로 재구성하여, 그로 하여금 더욱 가독성과 유지보수성을 가지게 하였다.데이터 세트에 새해를 추가할 때 복사하고 붙이지 않습니다!
문제는 이런 방법은 여전히 연간 목록을 수동으로 업데이트하는 사람이 필요하다는 점이다.만약 우리가 데이터를 대상에 불러오려고 한다면, 단독으로 빈 대상을 초기화할 이유가 없다.다음 두 개의 재구성 옵션은 나의 원본 코드 세션을 완전히 삭제하고 데이터를 직접 사용하는 방법을 보여 줍니다.
방백: 솔직히 말하면 만약에 내가 3년 전에 이 코드를 작성하려고 시도한다면 나는 3개의 끼워넣기for 순환을 하고 결과에 만족할 것이다.그러나 플러그 순환은 성능에 현저한 부정적인 영향을 미칠 수 있다.이 방법은 분류의 각 층에 주목해 무관한 순환을 없애고 성능을 향상시켰다.편집: 이 방법의 예시와 성능에 대한 토론을 보십시오.

옵션 2: 직접 필터


너희들 중 일부는 우리가 왜 분류에 따라 데이터를 나누는 데 신경을 써야 하는지 알고 싶어 할지도 모른다.데이터 구조에 따라 .filter를 사용하여 다음과 같이 currentType, currentYearcurrentRegion 기반 데이터를 반환할 수 있습니다.
    /*
      `.filter` will create a new array with all elements that return true
      if they are of the `currentType` and `currentYear`

      `.includes` returns true or false based on if `currentRegions`
      includes the entry's region
    */
    let currentData = data.filter(d => d.type === currentType && 
    d.year === currentYear && currentRegion.includes(d.region))
비록 이 줄 프로그램은 매우 효과가 있지만, 나는 우리의 사례에서 그것을 사용하는 것을 건의하지 않는다. 이유는 두 가지가 있다.
  • 사용자가 선택할 때마다 이 방법이 실행됩니다.데이터 세트의 크기에 따라 (매년 증가하고 있음을 명심해라) 성능에 부정적인 영향을 미칠 수 있다.현대 브라우저는 효율이 높아 성능에 미치는 영향은 적을 수 있지만 사용자가 한 번에 한 가지 유형과 1년만 선택할 수 있다는 것을 알면 처음부터 데이터를 그룹으로 나누어 성능을 향상시킬 수 있다.
  • 이 옵션은 사용 가능한 유형, 연도 또는 지역 목록을 제공하지 않습니다.이러한 목록이 있으면 수동으로 UI를 만들거나 업데이트하는 대신 선택 UI를 동적으로 생성할 수 있습니다.

  • 네, 저도 선택기를 하드코딩했습니다.매번 우리가 새해를 추가할 때마다 나는 JS와 HTML을 업데이트하는 것을 기억해야 한다.

    옵션 3: 데이터 제어 객체


    우리는 첫 번째와 두 번째 옵션의 각 방면을 결합시켜 세 번째 방식으로 코드를 재구성할 수 있다.목표는 데이터 집합을 업데이트할 때 코드를 변경할 필요가 없고 데이터 자체에서 분류를 정하는 것이다.
    마찬가지로 여러 가지 기술 방법으로 이 점을 실현할 수 있지만, 우리는 데이터 그룹을 하나의 대상으로 전환할 것이기 때문에 .reduce을 계속 사용할 것이다.
        const dataset = data.reduce((acc, curr) => {
            /*
              If the current type exists as a property of our accumulator,
              set it equal to itself. Otherwise, set it equal to an empty object.
            */
            acc[curr.type] = acc[curr.type] || {}
            // Treat the year layer the same way
            acc[curr.type][curr.year] = acc[curr.type][curr.year] || []
            acc[curr.type][curr.year].push(curr)
            return acc
        }, {})
    
    데이터세트 대상에서 분류된 구역층을 삭제했습니다.유형과 연도와 달리 임의의 조합에서 여러 지역을 동시에 선택할 수 있기 때문이다.이것은 지역으로 미리 그룹을 나누는 것이 사실상 쓸모가 없다. 왜냐하면 우리는 어떻게든 그것들을 한데 합쳐야 하기 때문이다.
    이를 감안하여 업데이트된 원 라인으로 선택한 유형, 연도, 지역에 따라currentData 획득.현재 유형과 년도로 제한된 데이터를 찾을 것이기 때문에, 그룹의 최대 항목은 국가수 (200보다 작음) 라는 것을 알고 있기 때문에, 이 방법은 옵션 2 .filter 보다 실현 효율이 훨씬 높다.
        let currentData = dataset[currentType][currentYear].filter(d => currentRegions.includes(d.region))
    
    마지막 단계는 서로 다른 종류, 연도, 지역의 그룹을 가져오는 것입니다.이를 위해, 나는 .map와 텔레비전을 즐겨 사용한다.다음은 데이터의 모든 유일한 영역을 포함하는 그룹을 가져오는 예입니다.
        /*
          `.map` will extract the specified object property 
          value (eg. regions) into a new array
        */
        let regions = data.map(d => d.region)
    
        /*
            By definition, a value in a Set must be unique.
            Duplicate values are excluded. 
        */
        regions = new Set(regions)
    
        // Array.from creates a new array from the Set
        regions = Array.from(regions)
    
        // One-line version
        regions = Array.from(new Set(data.map(d => d.region)))
    
        // or using the spread operator
        regions = [...new Set(data.map(d => d.region))]
    
    type & year에 이 동작을 반복해서 이 진열을 만듭니다.그런 다음 배열 값에 따라 필터 UI를 동적으로 만들 수 있습니다.

    최종 재구성 코드


    이 모든 것을 함께 놓으면 우리가 최종적으로 얻은 코드는 데이터 집중의 변화에 따라 미래 검증을 할 수 있다.수동으로 업데이트할 필요가 없습니다!
        // Unique Types, Years, and Regions
        const types = Array.from(new Set(data.map(d => d.type)))
        const years = Array.from(new Set(data.map(d => d.year)))
        const regions = Array.from(new Set(data.map(d => d.region)))
    
        // Group data according to type and year
        const dataset = data.reduce((acc, curr) => {
            acc[curr.type] = acc[curr.type] || {}
            acc[curr.type][curr.year] = acc[curr.type][curr.year] || []
            acc[curr.type][curr.year].push(curr)
            return acc
        }, {})
    
        // Update current dataset based on selection
        let currentData = dataset[currentType][currentYear].filter(d => currentRegions.includes(d.region))
    

    마지막 생각


    정리 문법은 재구성의 일부분일 뿐이지만 통상적으로'재구성 코드'는 실제적으로 서로 다른 부분 간의 실현이나 관계를 새롭게 정의하는 것을 의미한다.재구성은 매우 어렵다. 왜냐하면 몇 가지 방법으로 문제를 해결할 수 있기 때문이다.일단 당신이 효과적인 해결 방안을 찾게 되면, 당신은 다른 해결 방안을 생각해 내기 매우 어렵다.어떤 해결 방안이 더 좋은지 확인하는 것은 항상 뻔한 것이 아니라 코드의 상하문과 개인의 선호에 따라 다를 수 있다.
    재구성을 어떻게 더 잘 할 것인가에 대한 조언은 간단하다. 더 많은 코드를 읽는 것이다.만약 당신이 팀원이라면 코드 심사에 적극적으로 참여하세요.만약 당신이 어떤 것들을 재구성할 것을 요구한다면, 왜 그런지 물어보고, 다른 사람들이 문제를 어떻게 처리하는지 이해해 보세요.만약 당신이 혼자서 일한다면 (내가 처음 일을 시작했을 때처럼) 같은 문제에 서로 다른 해결 방안이 있음을 주의하고, 가장 좋은 코드 실천 지침을 찾아보세요.저는 당신이 이 책을 읽는 것을 강력히 추천합니다BaseCode.그것은 더욱 간단하고 읽을 수 있는 코드를 작성하는 우수한 현장 안내서로, 많은 실제 세계의 예시를 포함하고 있다.
    가장 중요한 것은 이런 사실을 받아들여야 한다는 것이다. 때로는 나쁜 코드를 작성하고 재구성하는 과정을 겪으며, 그것을 더욱 좋아지게 하는 것은 성장의 징조이며, 경축할 만하다.

    좋은 웹페이지 즐겨찾기