미래 지향적인 웹 앱 구축: The Codest의 전문가 팀이 제공하는 인사이트
The Codest가 최첨단 기술로 확장 가능한 대화형 웹 애플리케이션을 제작하고 모든 플랫폼에서 원활한 사용자 경험을 제공하는 데 탁월한 성능을 발휘하는 방법을 알아보세요. Adobe의 전문성이 어떻게 디지털 혁신과 비즈니스를 촉진하는지 알아보세요...
경력을 시작하는 많은 프로그래머는 변수, 함수, 파일 및 기타 구성 요소의 이름을 지정하는 주제를 그다지 중요하지 않다고 생각합니다. 그 결과 알고리즘이 빠르게 실행되고 원하는 효과를 내면서도 가독성이 떨어지는 등 설계 로직이 올바른 경우가 많습니다. 이 글에서는 다양한 코드 요소의 이름을 지정할 때 참고해야 할 사항과 극단적인 이름을 지정하지 않는 방법에 대해 간략하게 설명하겠습니다.
귀하와 귀하의 팀 를 인수하고 있습니다. 코드 다른 프로그래머로부터. 다른 프로그래머의 프로젝트 는 애정을 쏟지 않고 개발되었지만, 모든 요소가 훨씬 더 나은 방식으로 작성될 수 있었을 것입니다.
아키텍처와 관련하여 코드 상속의 경우 거의 항상 코드를 받은 프로그래머의 증오와 분노를 유발합니다. 때로는 죽어가는(또는 사라진) 기술을 사용하거나, 때로는 개발 초기에 애플리케이션에 대한 잘못된 사고 방식, 때로는 단순히 담당 프로그래머의 지식 부족으로 인해 발생하기도 합니다.
어쨌든 프로젝트 시간이 지날수록 프로그래머가 아키텍처와 기술에 열광하는 지점에 도달할 수 있습니다. 결국 모든 애플리케이션은 시간이 지나면 일부 부분을 다시 작성하거나 특정 부분을 변경해야 하는 것은 당연한 일입니다. 그러나 프로그래머의 머리를 하얗게 만드는 문제는 상속받은 코드를 읽고 이해하는 데 어려움이 있다는 것입니다.
특히 변수가 의미 없는 단일 문자로 명명되고 함수가 애플리케이션의 나머지 부분과 전혀 일관성이 없이 갑자기 창의력을 발휘하는 극단적인 경우에는 프로그래머가 미쳐버릴 수 있습니다. 이러한 경우 올바른 이름 지정으로 빠르고 효율적으로 실행할 수 있는 코드 분석은 예를 들어 함수 결과를 생성하는 알고리즘에 대한 추가 분석이 필요합니다. 이러한 분석은 눈에 잘 띄지 않지만 엄청난 시간을 낭비하게 됩니다.
애플리케이션의 다른 부분에 새로운 기능을 구현하면 시간이 지나면 의도가 명확하지 않아 다시 코드로 돌아가 분석해야 하는 악몽을 겪게 되고, 그 동작을 이해하려고 했던 이전의 시간은 더 이상 목적이 무엇인지 기억나지 않아 낭비되는 경우가 많습니다.
따라서 우리는 애플리케이션을 지배하고 개발의 모든 참여자를 천천히 잡아먹는 무질서의 토네이도에 빨려 들어가게 됩니다. 프로그래머는 프로젝트를 싫어하고, 프로젝트 관리자는 개발 시간이 계속 늘어나는 이유를 설명하는 것을 싫어하며, 고객은 계획대로 진행되는 것이 없기 때문에 신뢰를 잃고 화를 냅니다.
현실을 직시하자 - 건너뛸 수 없는 것도 있습니다. 프로젝트 초기에 특정 기술을 선택했다면 시간이 지남에 따라 지원이 중단되거나 몇 년 전의 기술에 능통한 프로그래머가 점점 줄어들어 서서히 구식이 될 수 있다는 사실을 인지해야 합니다. 업데이트되는 일부 라이브러리의 경우 코드에 다소 많은 변경이 필요하기 때문에 종속성의 소용돌이에 휘말려 더 큰 어려움을 겪을 수 있습니다.
물론 기술은 점점 더 오래되고 있지만 이와 관련된 프로젝트의 개발 시간을 확실히 늦추는 요소는 대부분 추악한 코드입니다. 물론 여기에서 Robert C. Martin의 책을 언급해야합니다. 이 책은 프로그래머를위한 성경으로, 저자는 완벽을 추구하는 코드를 만들기 위해 따라야 할 많은 모범 사례와 원칙을 제시합니다.
위의 일부 문장을 가장 정확하게 표현하는 방법은 실제로 어떻게 작동하는지 보여주는 것이라고 생각합니다. 이 단락에서는 어느 정도 좋은 코드 관행으로 바꿀 수 있는 몇 가지 나쁜 코드 관행을 간략하게 설명하려고 합니다. 어떤 순간에 코드의 가독성을 방해하는 요소와 이를 방지하는 방법을 지적하겠습니다.
안타깝게도 대학에서도 흔히 볼 수 있는 끔찍한 관행은 한 글자로 변수의 이름을 짓는 것입니다. 변수의 목적을 파악하기 위해 불필요한 고민을 하지 않고 여러 글자를 사용하는 대신 한 글자(예: i, j, k)만 사용하는 것이 때로는 매우 편리한 해결책이라는 데 동의하지 않기는 어렵습니다.
역설적이게도 이러한 변수에 대한 일부 정의에는 훨씬 더 긴 설명이 붙어 있어 작성자가 염두에 둔 것이 무엇인지 알 수 있습니다.
여기서 좋은 예는 열과 행의 교차점에 해당 값이 포함된 2차원 배열에 대한 반복을 표현하는 것입니다.
const array = [[0, 1, 2], [3, 4, 5], [6, 7, 8]];
// 꽤 나쁘다
for (let i = 0; i < array[i]; i++) {
for (let j = 0; j < array[i][j]; j++) { {
// 여기에 내용이 있지만, i와 j가 사용될 때마다 다시 돌아가서 그들이 무엇에 사용되는지 분석해야 합니다.
}
}
// 여전히 나쁘지만 재밌습니다.
let i; // 행
let j; // 열
for (i = 0; i < array[i]; i++) {
for (j = 0; j < array[i][j]; j++) { {
// 여기에 내용이 있지만 i와 j가 사용될 때마다 돌아가서 그들이 무엇에 사용되는지 주석을 확인해야합니다.
}
}
// 훨씬 낫습니다.
const rowCount = array.length;
for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) {
const row = array[rowIndex];
const columnCount = row.length;
for (let columnIndex = 0; columnIndex < columnCount; columnIndex++) {
const column = row[columnIndex];
// 뭐가 뭔지 의심스러운 사람 있나요?
}
}
어느 화창한 날, 저는 어떤 사람이 작성한 매우 정교한 코드를 발견했습니다. 소프트웨어 엔지니어. 이 엔지니어는 사용자 권한을 특정 작업을 지정하는 문자열로 전송하면 몇 가지 비트 수준 트릭을 사용하여 크게 최적화할 수 있다는 사실을 알아냈습니다.
아마도 이러한 솔루션은 대상이 Commodore 64라면 괜찮을 것이지만 이 코드의 목적은 JS로 작성된 간단한 웹 애플리케이션이었습니다. 이 단점을 극복할 때가 왔습니다:
사용자가 전체 시스템에서 콘텐츠를 수정할 수 있는 옵션이 작성, 읽기, 업데이트, 삭제의 네 가지뿐이라고 가정해 보겠습니다. 이러한 권한을 상태 또는 배열이 있는 객체의 키로 JSON 형식으로 전송하는 것은 매우 자연스러운 일입니다.
하지만 영리한 엔지니어는 숫자 4가 이진 표현에서 마법 같은 값이라는 것을 알아채고 다음과 같이 알아냈습니다:
전체 기능 표에는 16개의 행이 있지만, 이러한 권한을 만드는 아이디어를 전달하기 위해 4개만 나열했습니다. 권한을 읽는 방법은 다음과 같습니다:
const user = { 권한: 11 };
const canCreate = Boolean(user.permissions & 8); // 참
const canRead = Boolean(user.permissions & 4); // false
const canUpdate = Boolean(user.permissions & 2); // 참
const canDelete = Boolean(user.permissions & 1); // 참
위에 표시된 내용은 웹 어셈블리 코드. 이러한 최적화는 특정 작업에 시간이나 메모리(또는 둘 다)가 거의 필요하지 않은 시스템에서 흔히 볼 수 있는 일입니다. 반면에 웹 애플리케이션은 이러한 과도한 최적화가 전혀 의미가 없는 곳이 아닙니다. 일반화하고 싶지는 않지만 프런트엔드 개발자의 작업에서 비트 추상화 수준에 이르는 복잡한 작업은 거의 수행되지 않습니다.
단순히 읽을 수 없으며 이러한 코드를 분석 할 수있는 프로그래머는이 솔루션이 어떤 보이지 않는 이점이 있는지, 그리고 손상 될 수있는 것이 무엇인지 궁금해 할 것입니다. 개발 팀 보다 합리적인 솔루션으로 다시 작성하려고 합니다.
또한 권한을 일반 개체로 보내면 프로그래머가 1-2 초 안에 의도를 읽을 수 있지만 처음부터이 모든 것을 분석하는 데 최소 몇 분이 걸릴 것이라고 생각합니다. 프로젝트에는 여러 프로그래머가있을 것이며, 각 프로그래머는이 코드를 접해야 할 것입니다. 시간이 지나면 어떤 마법이 일어나고 있는지 잊어 버릴 것이기 때문에 여러 번 분석해야 할 것입니다. 그 몇 바이트를 절약할 가치가 있을까요? 제 생각에는 그렇지 않습니다.
웹 개발 가 빠르게 성장하고 있으며 이와 관련하여 곧 변화가 있을 것이라는 징후는 없습니다. 최근 프론트엔드 개발자의 책임이 크게 증가하여 사용자 인터페이스에서 데이터 표시를 담당하는 로직의 일부를 맡게 되었음을 인정하지 않을 수 없습니다.
때로는 이 로직이 간단하고 API에서 제공하는 객체의 구조가 간단하고 읽기 쉬운 경우도 있습니다. 그러나 때로는 페이지의 다른 위치에 맞게 조정하기 위해 다양한 유형의 매핑, 정렬 및 기타 작업이 필요합니다. 바로 이 지점이 우리가 쉽게 늪에 빠질 수 있는 곳입니다.
저는 수행하던 연산의 데이터를 거의 읽을 수 없게 만든 적이 여러 번 있었습니다. 배열 메서드와 적절한 변수 이름을 올바르게 사용했음에도 불구하고, 어떤 시점에서는 일련의 연산으로 인해 제가 달성하고자 하는 목적의 맥락을 거의 잃어버릴 뻔했습니다. 또한 이러한 연산 중 일부는 다른 곳에서 사용해야 하는 경우도 있었고, 때로는 쓰기 테스트가 필요할 정도로 전역적이거나 정교한 경우도 있었습니다.
const devices = [
{ id: '001', 버전: 1, 소유자: { 위치: '텍사스', 나이: 21 } }. },
{ id: '002', 버전: 2, 소유자: { 위치: '텍사스', 나이: 27 } }, { id: '002', 버전: 2, 소유자: { 위치: '텍사스', 나이: 27 } }. },
{ id: '003', 버전: 3, 소유자: { 위치: '애리조나', 나이: 27 } }. },
{ id: '004', 버전: 2, 소유자: { 위치: '시카고', 나이: 24 } }. },
{ id: '005', 버전: 2, 소유자: { 위치: '애리조나', 나이: 19 } }. },
{ id: '006', 버전: 3, 소유자: { 위치: '텍사스', 나이: 42 } }. },
];
// 꽤 복잡
(() => {
const locationAgeMap = devices
.map(device => device.owner)
.reduce((map, item) => {
if (!map[item.location]) { {
map[item.location] = item.age;
} else {
map[item.location] += item.age;
}
return map;
}, {});
const locationLegalAgeOwnersMap = devices
.map(device => device.owner)
.filter(owner => owner.age >= 21)
.reduce((map, item) => {
if (!map[item.location]) { {
map[item.location] = [item];
} else {
map[item.location].push(item);
}
반환 지도;
}, {});
console.log({ locationAgeMap, locationLegalAgeOwnersMap });
})();
// 여전히 복잡하지만 더 간결해졌습니다.
(() => {
const locationOwnersMap = devices
.map(device => device.owner)
.reduce((map, item) => {
if (!map[item.location]) { {
map[item.location] = [item];
} else {
map[item.location].push(item);
}
반환 지도;
}, {});
const locationAgeMap = Object.fromEntries(
Object.entries(locationOwnersMap).map(([location, owners]) => {
const ownersAge = owners.map(소유자 => 소유자.나이).reduce((a, b) => a + b);
반환 [위치, 소유자 연령];
})
);
const locationLegalAgeOwnersMap = Object.fromEntries(
Object.entries(locationOwnersMap).map(([location, owners]) => {
const filteredOwners = owners.filter(owner => owner.age >= 21);
반환 [위치, 필터링된 소유자];
}).filter(([_location, owners]) => {
반환 소유자.길이 > 0;
})
);
console.log({ locationAgeMap, locationLegalAgeOwnersMap });
})();
알아요, 알아요 - 제가 전달하고자 하는 바를 쉽게 설명하는 사소한 코드가 아닙니다. 또한 두 예제의 계산 복잡성이 약간 다르다는 것도 알고 있지만, 99%의 경우에는 걱정할 필요가 없습니다. 두 알고리즘의 차이점은 둘 다 위치와 디바이스 소유자의 지도를 준비한다는 점에서 간단합니다.
첫 번째 알고리즘은 이 지도를 두 번 준비하는 반면, 두 번째 알고리즘은 한 번만 준비합니다. 두 번째 알고리즘이 더 이식성이 뛰어나다는 것을 보여주는 가장 간단한 예는 첫 번째 알고리즘에서 특정 위치나 기타 비즈니스 로직이라고 하는 이상한 것들을 제외하는 등 이 맵을 만드는 로직을 변경해야 한다는 사실에 있습니다. 두 번째 알고리즘의 경우 지도를 가져오는 방법만 수정하고 이후 줄에서 발생하는 나머지 데이터 수정은 모두 변경하지 않습니다. 첫 번째 알고리즘의 경우 지도를 준비할 때마다 수정해야 합니다.
실제로는 전체 애플리케이션에서 특정 데이터 모델을 변환하거나 리팩터링해야 하는 경우가 많이 있습니다.
다양한 비즈니스 변화를 따라잡지 않는 가장 좋은 방법은 상당히 일반적인 방식으로 관심 있는 정보를 추출할 수 있는 글로벌 도구를 준비하는 것입니다. 최적화 해제에 따른 2~3밀리초의 손실을 감수하더라도 말이죠.
프로그래머는 다른 직업과 마찬가지로 매일 새로운 것을 배우고 종종 많은 실수를 저지르는 직업입니다. 가장 중요한 것은 이러한 실수로부터 배우고 더 나은 사람이 되어 앞으로는 이러한 실수를 반복하지 않는 것입니다. 우리가 하는 일이 항상 완벽할 것이라는 신화를 믿어서는 안 됩니다. 그러나 다른 사람들의 경험을 바탕으로 그에 따라 결함을 줄일 수는 있습니다.
이 글을 읽으면서 최소한 다음과 같은 문제를 피하는 데 도움이 되길 바랍니다. 잘못된 코딩 관행 제가 업무에서 경험한 사례를 소개합니다. 모범 코드 관행에 관한 질문이 있는 경우 다음 연락처로 문의하세요. The Codest 승무원 를 통해 궁금한 점을 상담하세요.
자세히 읽어보세요:
*타이틀 그래픽은 speednet.pl에서 가져온 것입니다.