코드 가독성 또는 성능: 어느 것이 더 중요할까요? | Bikash Paneru

코드 가독성 또는 성능: 어느 것이 더 중요할까요? | Bikash Paneru

양쪽을 모두 분석하여 그에 따라 우선순위를 정하세요.

·

8 min read

Bikash Paneru님의 Code Readability or Performance: Which Is More Important?글을 번역하였습니다.


a perplexed-looking woman and the words “performance?” and “readability?”

성능 대비 코드 가독성의 중요성에 대한 논의는 오랫동안 인터넷에서 떠돌아다녔습니다. 하지만 객관적인 답을 찾기는 어렵습니다.

따라서 객관적인 해답을 찾기보다는 다양한 맥락에서 가독성과 성능을 분석해 보겠습니다.

질문부터 시작하겠습니다: 이 코드는 읽을 수 있는 코드인가요?

- .... .. ... / .. ... / .-. . .- -.. .- -... .-.. .

이것은 사실 "읽을 수 있습니다!"로 번역되는 모스 부호입니다. 모스 부호를 안다면 이 코드를 읽을 수 있다고 말할 수 있습니다. 그렇다면 모스 부호를 모른다면 이 코드를 읽을 수 없다고 말하는 것이 합리적일까요? 이 코드는 읽을 수 있는 모스 부호이기 때문에 그렇지 않습니다.

이제 이를 소프트웨어에 적용해 보겠습니다. 다음 코드를 한 눈에 보고 어떤 기능을 하는지 파악할 수 있나요?

void mysteryFunction (int[] items) {
  for (int i = 0; i < items.length; i++) {
    for (int j = 0; j < items.length - i - 1; j++) {
      int num1 = items[j]
      int num2 = items[j + 1]
      if (num1 > num2) {
        items[j] = num2;
        items[j + 1] = num1;
      }
    }
  }
}

이것은 실제로 버블 정렬을 구현한 것입니다. 버블 정렬이 어떻게 작동하는지 알고 있고 이와 같은 알고리즘을 작성하는 데 익숙하다면 단번에 알아낼 수 있을 것입니다. 그러나 버블 정렬 알고리즘을 한 번도 살펴본 적이 없다면 이해하는 데 시간이 걸릴 수 있습니다.

이 주제에 익숙하지 않은 사람들이 코드를 이해하는 데 시간이 조금 더 걸릴 수 있다는 이유만으로 이 코드를 읽을 수 없다고 할 수 있을까요? 이 질문은 첫 번째 요점으로 돌아옵니다.

위의 알고리즘이 흥미를 끌었다면 Vaidehi Joshi의 버블 정렬에 대한 자세한 설명과 분석을 살펴보세요. 얼마 전에 읽었는데 흥미로운 글이라고 생각합니다.

가독성은 개념과 관련이 없습니다.

정렬 알고리즘의 구현은 언어의 표준 라이브러리나 알고리즘을 제공하는 라이브러리에서 찾을 수 있을 것입니다. 이러한 프로젝트에서 작업했다면 이와 같은 코드 작업에 익숙하고 기본 개념을 알고 있을 것입니다. 따라서 이 코드는 읽을 수 있을 것입니다. 기본 개념을 이해하지 못한다고 해서 일부 코드를 읽을 수 없다고 할 수는 없습니다.

예, 특정 영역의 문제를 해결하여 다양한 애플리케이션에서 사용되는 많은 알고리즘의 코드는 이해하기 어렵습니다. 그러나 이는 이러한 알고리즘이 일반적으로 비즈니스 로직과 비교할 때 더 많은 수학 및/또는 복잡한 논리적 분기를 포함하기 때문일 수 있습니다.

알고리즘은 항상 가독성을 높이기 위해 적절한 이름 지정 및 기타 코딩 관행을 사용할 수 있습니다. 또한 알고리즘을 먼저 공부하지 않고는 이러한 알고리즘을 구현할 수 없습니다. 또한 기본 개념을 먼저 이해하지 않고 복잡한 알고리즘의 코드를 읽으려고 시도해서는 안 됩니다.

위의 함수에 알고리즘이 무엇인지 설명하는 주석이 있거나 함수의 이름이 적절하게 지정되어 있다면 훨씬 더 이해하기 쉬울 것입니다. 구글에서 검색하여 기본 개념을 이해한 다음 코드로 돌아올 수 있습니다. 이러한 종류의 가독성은 코드가 무엇을 다루든 항상 달성할 수 있습니다.

가독성은 개인 취향에 따라 영향을 받습니다.

일부 개발자는 for 루프의 가독성이 적당한 수준이라고 생각합니다. 반면에 filter, map, reduce와 같은 메서드를 훨씬 더 읽기 편하다고 생각하는 개발자도 있습니다. 가독성의 정도는 또한 for 루프 안에 얼마나 많은 로직이 들어 있는지, 그리고 부작용을 유발하는지 여부에 따라 달라집니다. 이 같은 논증은 if - elseif 분기 및 가드 절과 같은 다른 개념에도 적용할 수 있습니다.

가드 조항에 대해 잘 모르거나 더 자세히 알고 싶다면 다음의 Carlos Caballero의 가드 조항에 대한 글을 참조하세요.

이러한 가독성의 정도는 제품을 개발하는 개발자에 따라 달라집니다. 그러나 모든 팀이 표준을 이해하고 준수하는 것은 매우 유익합니다. 따라서 가능하면 팀원들이 함께 모여 가독성 측면에서 팀에 중요한 것이 무엇인지에 대한 간단한 가이드라인을 만들어야 합니다. 코드 리뷰는 팀에 적합한 가독성 수준을 파악하고 이를 시행할 수 있는 훌륭한 플랫폼입니다.

성능은 상황에 따라 다릅니다.

웹 애플리케이션을 개발할 때 대부분의 시간을 비즈니스 로직을 구현하는 데 할애합니다. 대부분의 비즈니스 로직은 API를 호출하고 다른 라이브러리에서 제공하는 클래스를 사용하여 구현됩니다. 이러한 라이브러리는 HTTP 서버부터 데이터베이스 드라이버, 심지어 알고리즘에 이르기까지 다양합니다. 이러한 기본 라이브러리는 일반적으로 성능을 위해 많은 작업을 수행합니다.

function handleProfileRequest (Request request, Response response) {
  // Relies on Web Application Library
  string profileId = request.query.get('profileId');
  // Relies on Database
  Profile userProfile = ProfileStore.findById(profileId);
  // Relies on Web Application Library
  response.send(userProfile.toJSON());
}

반대로 그래프 이동과 같이 여러 애플리케이션에 유용한 잘 알려진 문제를 해결하기 위한 알고리즘을 만들거나 구현하는 경우 일반적으로 빠른 알고리즘을 찾으려고 노력합니다. 애플리케이션 개발자도 항상 즉석에서 알고리즘을 만듭니다. 그러나 이러한 알고리즘은 해당 애플리케이션에 특화되어 있으며 일반적으로 한 번에 많은 데이터를 처리하지 않습니다. 따라서 적절한 코딩 관행을 따르는 한 대부분 성능에 큰 영향을 미치지 않습니다.

그럼에도 불구하고 모든 유형의 알고리즘, 특히 즉석에서 생성된 알고리즘의 경우 사용 사례에 따라 항상 허용 가능한 수준의 성능을 정의할 수 있습니다.

성능 코드가 항상 읽을 수 없는 것은 아닙니다.

알고리즘의 성능은 가독성과 직접적인 관련이 없습니다. 알고리즘의 속도는 알고리즘이 사용하는 로직과 데이터 구조에 따라 결정되며, 가독성 여부와는 무관합니다.

마찬가지로 애플리케이션이 느리다면 당면한 문제에 대해 비효율적인 알고리즘이나 데이터 구조를 직간접적으로 사용하고 있을 가능성이 높습니다. 이러한 경우 가독성을 희생한다고 해서 문제가 해결되지는 않습니다.

예를 들어 느린 데이터베이스 쿼리로 인해 애플리케이션이 느려지는 경우, 인덱스를 현명하게 배치하거나 데이터 간의 관계를 재고하여 데이터베이스가 보다 효율적인 알고리즘과 데이터 구조를 사용하도록 하는 것이 해결책이 될 수 있습니다. 이 경우 가독성은 고려되지 않습니다.

가독성 높은 코드라고 해서 성능이 저하될 필요는 없습니다.

사용자가 다른 사용자를 추천하여 가입할 수 있는 애플리케이션을 생각해 봅시다. 데이터베이스에서 모든 사용자를 추천인의 이름과 함께 가져오는 비즈니스 로직을 작성해야 한다면 여러 가지 방법으로 할 수 있습니다. 다음은 두 가지 방법입니다:

User[] users = User.executeQuery(
  'SELECT * FROM users'
);
UserWithReferrer[] usersWithReferrer = users.map((User user) => {
 if (user.referrer_id) {
   // Manually find the referrer from the list of users
   User referrer = users.find(
     searchUser => searchUser.id == user.referrerId
   );
   return new UserWithReferrer(user, referrer);
 } else {
   return new UserWithReferrer(user);
 }
})
response.send(users);
UserWithReferrer[] usersWithReferrer = UserWithReferrer. executeQuery('
  SELECT users.*, referrers.name as referrer_name
    FROM users LEFT JOIN users referrers ON
      users.referrer_id = referrers.id
')
response.send(usersWithReferrer)

대부분의 사람들은 이 두 가지 대안을 모두 가독성이 좋다고 말합니다. 그러나 두 번째 방법이 더 읽기 쉽고 특히 데이터베이스에 올바른 인덱스가 정의되어 있는 경우 첫 번째 방법보다 더 나은 성능을 제공합니다. SQL 조인에 대해 모르는 사람에게는 가독성이 떨어질 수 있지만, 유용한 언어 기능을 사용한다고 해서 가독성이 떨어진다고 분류해서는 안 됩니다.

이 예는 사용하는 기술 스택을 제대로 숙지하면 코드의 가독성과 성능을 향상시킬 수 있다는 것을 보여줍니다. 문제가 발생할 때마다 즉시 자체 솔루션을 배포하거나 다른 타사 라이브러리를 사용하는 대신 프로젝트에서 사용 중인 라이브러리와 도구가 이미 문제를 해결할 수 있는 기능을 제공하는지 조사하는 것이 더 나을 수 있습니다.

정확성이 가장 중요합니다.

제품이나 기능이 예상대로 작동하면 올바른 것입니다. 코드가 전혀 올바르지 않다면 가독성이나 성능은 중요하지 않습니다.

전체 시스템 또는 일부가 성능을 중심으로 돌아가는 제품도 있습니다. 이러한 경우 예상 성능과 일치하지 않으면 제품이 올바를 수 없습니다. 따라서 성능이 정확성과 연결될 때는 성능에 대해 먼저 생각해야 합니다.

예를 들어, 펜티엄 4에서 초당 60프레임으로 실행되는 게임을 빌드하는 경우, 이를 달성하는 데 초점을 맞춰야 합니다. 이를 위해 가장 빠른 알고리즘과 데이터 구조를 사용하거나, 새로운 알고리즘을 만들거나, 기존 코드를 최적화하거나, 일부 기능을 희생할 수도 있습니다.

최적화해야 할 시기와 대상 파악

병목 지점이 어디인지 먼저 파악하지 않고 코드를 최적화하는 것은 불가능합니다. 물론 일반적인 성능 문제가 발생할 가능성이 높은 위치를 파악하고 있다면 처음부터 성능 좋은 코드를 작성할 수 있습니다.

이에 대한 좋은 예는 앞서 설명한 SQL 예제입니다. SQL을 사용해 본 사람이라면 이러한 상황에서 조인을 사용해야 한다는 것을 분명히 알고 있을 것입니다. 대부분의 경우 올바른 코딩 관행을 따른다면 성능 문제가 발생하지 않습니다.

최적화가 필요한지, 얼마나 필요한지 알 수 있는 가장 좋은 방법은 기능을 계획할 때 예상 성능을 정의하는 것입니다. 기대 성능이 정의되면 먼저 최대한 가독성 있는 코드를 작성한 다음 성능 요구 사항을 충족하지 않는 경우에만 리팩터링할 수 있습니다.

제품 관점에서 성능이 정말 최고 수준이어야 하는 경우, 구현이 성능 요구 사항을 충족하는지 확인하기 위해 스트레스 테스트를 수행할 수 있습니다.

예를 들어, 시스템의 모든 사용자 목록을 반환하는 API는 현재 프로덕션에 있는 데이터의 양에 대해 1초 이내에 응답해야 한다고 가정할 수 있습니다. 또는 현재 프로덕션 데이터의 두 배에 해당하는 양의 데이터에 대해 1초 이내에 응답을 반환해야 한다고 말할 수도 있습니다.

또한 클라우드 제공업체에서 제공하는 다양한 도구를 통해 성능을 측정할 수 있습니다. 이러한 도구는 실제로 성능 병목 현상이 발생하여 최적화가 필요한 시점을 감지할 수 있는 훌륭한 도구입니다. 이러한 도구를 사용하고 싶지 않다면 성능을 기록하는 간단한 솔루션을 쉽게 만들어 로그에 기록할 수 있습니다. 그 후에는 성능을 모니터링하고 언제 성능이 저하되기 시작하는지 조사하기만 하면 됩니다.

스트레스 테스트를 수행하더라도 이러한 도구를 반드시 사용해야 합니다.

애플리케이션에 따라 성능이 달라질 수 있습니다.

최신 애플리케이션은 복잡하고 서로 통신하고 작동하는 많은 구성 요소가 있습니다. 하지만 이러한 모든 구성 요소에 동일한 수준의 성능이 필요한 것은 아닙니다.

쇼핑 애플리케이션을 예로 들어 보겠습니다. 이 애플리케이션에는 제품 카탈로그 작성, 주문 처리, 결제 처리, 배송 처리, 데이터 분석, 추천 생성 등의 기능이 있습니다. 이러한 다양한 구성 요소는 모두 동일한 수준의 성능을 필요로 하지 않습니다.

데이터 분석 및 추천 부분은 대량의 데이터로 작업할 수 있으며 시스템에서 이러한 작업을 수행하는 빈도에 따라 성능이 필요할 수 있습니다. 대부분의 애플리케이션은 이러한 구성 요소가 빠르더라도 많은 양의 데이터를 처리해야 하므로 백그라운드 작업으로 백그라운드에서 실행합니다.

마찬가지로 제품 카탈로그는 대부분의 시간을 데이터베이스에서 제품을 대기하고 HTML 파일을 제공하는 데 소비할 가능성이 높습니다. 따라서 올바른 코딩 관행을 사용하는 한 최적화에 대해 크게 걱정할 필요가 없습니다. 기본 웹 애플리케이션과 데이터베이스 라이브러리가 제공하는 성능이면 충분합니다.

컴파일러가 사용자를 위해 최적화합니다.

좋은 컴파일러는 항상 코드에 최적화를 수행합니다. 너무 많은 변수와 함수를 생성하거나, 다른 연산자 간의 성능을 비교하는 등 사소한 것에 대해 너무 걱정할 필요는 없습니다.

가독성 높은 코드는 항상 최적화하기 쉽습니다.

읽기 쉬운 코드는 이해하기 쉬우므로 필요한 경우 최적화하기가 더 쉽습니다. 시스템의 요구 사항을 충족하면서 가독성 있는 코드를 작성하기 위해 최선을 다하는 것에는 큰 불이익이 없습니다.

적절한 테스트를 작성했다면 테스트를 실행하여 동작이 변경되지 않았는지 확인할 수 있으므로 리팩터링이 항상 더 쉬워집니다. 또한 최신 소프트웨어는 플로피디스크나 CD롬이 아닌 인터넷을 통해 제공됩니다. 따라서 성능이 저하되기 시작하면 업데이트를 푸시하여 버그를 수정하는 것이 더 쉽습니다.

결론

코드 가독성이 성능보다 더 중요한지에 대한 객관적인 답을 제시하기는 어렵습니다. 가독성과 성능의 정도는 애플리케이션의 특성과 요구 사항에 따라 달라집니다. 또한 이는 애플리케이션의 여러 부분에 따라 달라질 수 있습니다. 따라서 충분한 정보를 바탕으로 적절하고 올바른 결정을 내릴 수 있는 사람은 애플리케이션을 개발하는 팀뿐입니다.


코드 가독성에 대해 자세히 알아보고 싶다면 다음의 좋은 글을 참고하세요. 개발자 RayRay의 가독성 유지 가이드라인을 참조하세요. 이 글에서 언급된 사항은 모든 프로그래밍 언어에 적용됩니다.


https://betterprogramming.pub/code-readability-vs-performance-25a9ca970aca