코드 구성 및 관심사 분리를 위한 예시

이상문
9 min readMay 13, 2023

소프트웨어 개발에서 코드 정리는 유지 관리가 가능하고 확장 가능하며 효율적인 코드를 작성하는 데 있어 매우 중요한 요소이다. 적절하게 코드를 정리하면 더 쉽게 이해하고, 탐색하고, 유지 관리할 수 있게 해준다. 이 과정의 핵심은 관심사 분리에 있다.

추상화 수준 이해하기

코드를 효과적으로 정리하려면 관련된 추상화 수준을 이해하는 것이 필수적이다. 이러한 수준은 낮은 수준의 작업부터 높은 수준의 비즈니스 로직까지 다양할 수 있으며, 그에 따라 코드를 그룹화하는 것이 중요하다. 코드 정리는 다양한 추상화 수준에서 코드를 쉽게 탐색하고 이해할 수 있도록 해야 한다.

// Low level of abstraction - specific implementation details
function calculateTaxes(price, taxRate) {
const taxAmount = price * taxRate;
return price + taxAmount;
}

// Medium level of abstraction - reusable function
function calculateTotalPrice(price, taxRate) {
return calculateTaxes(price, taxRate);
}

// High level of abstraction - business logic
function calculateInvoiceTotal(items) {
const totalPrice = items.reduce((acc, item) => acc + item.price, 0);
const taxRate = 0.1;
return calculateTotalPrice(totalPrice, taxRate);
}

위의 예제에서 calculateTaxes() 함수는 주어진 가격 및 세율을 기준으로 세금을 계산하기 위한 구체적인 구현 세부 사항을 포함하므로 추상화 수준이 낮다. calculateTotalPrice() 함수는 calculateTaxes() 함수에 의존하여 세금이 포함된 총 가격을 계산하는 재사용 가능한 함수이므로 추상화 수준이 중간 정도이다.

마지막으로, calculateInvoiceTotal() 함수는 품목의 총 가격을 가져와 세금을 더하고 최종 가격을 반환하는 인보이스의 총 가격을 계산하는 비즈니스 로직을 나타내므로 추상화 수준이 높습니다.

코드를 개별 단위로 나누기

개별성(discreteness)은 코드 조각이 얼마나 독립적이고 고유한지를 나타낸다. 개별 코드는 다른 컨텍스트를 변경하거나 참조할 필요가 없다. 코드를 개별 단위로 나누면 이해, 테스트 및 유지 관리가 더 쉬워진다.

function calculateSquareArea(sideLength) {
return sideLength * sideLength;
}

function calculateRectangleArea(length, width) {
return length * width;
}

const squareArea = calculateSquareArea(5);
const rectangleArea = calculateRectangleArea(3, 4);

console.log(`The area of the square is ${squareArea}.`);
console.log(`The area of the rectangle is ${rectangleArea}.`);

위의 예제에는 각각 해당 도형의 면적 계산을 처리하는 두 개의 개별 함수 calculateSquareArea()와 calculateRectangleArea()가 있다. 이러한 함수는 서로 독립적으로 사용할 수 있는 개별 코드 단위이다. 이 함수를 사용하여 정사각형과 직사각형의 면적을 계산하고 결과를 콘솔에 출력할 수 있다.

이렇게 코드를 개별 단위로 나누면 각 함수가 특정 목적을 수행하고 개별적으로 테스트 및 디버깅할 수 있으므로 코드를 더 쉽게 관리하고 이해할 수 있다. 또한 코드 전체에서 동일한 함수를 사용하여 동일한 계산을 수행할 수 있으므로 코드의 재사용성을 높일 수 있다.

그룹 크기(Group sizing) 조정하기

그룹 크기 조정은 그룹 내에서 수행해야 할 작업 범위를 정의하는 과정이다. 코드를 그룹화할 때는 관련된 작업 수준을 고려해야 한다. 적절하게 그룹 크기를 조정하면 코드를 관리하기 쉬운 단위로 구성하는 데 도움이 된다.

// Original code with one large function
function calculateSalary(employee) {
let salary = employee.baseSalary;
if (employee.isManager) {
salary += 10000;
if (employee.teamSize > 10) {
salary += 5000;
}
}
if (employee.yearsOfExperience > 5) {
salary += 5000;
}
return salary;
}

// Refactored code with smaller functions
function calculateSalary(employee) {
let salary = employee.baseSalary;
salary += calculateManagerBonus(employee);
salary += calculateExperienceBonus(employee);
return salary;
}

function calculateManagerBonus(employee) {
let bonus = 0;
if (employee.isManager) {
bonus += 10000;
bonus += calculateTeamSizeBonus(employee);
}
return bonus;
}

function calculateTeamSizeBonus(employee) {
if (employee.teamSize > 10) {
return 5000;
}
return 0;
}

function calculateExperienceBonus(employee) {
if (employee.yearsOfExperience > 5) {
return 5000;
}
return 0;
}

위의 예제에서는 원래 calculateSalary()를 특정 작업을 처리하는 더 작은 함수로 리팩터링했다. calculateManagerBonus(), calculateTeamSizeBonus(), calculateExperienceBonus() 함수는 그룹의 크기를 조정하고 코드를 보다 모듈화하여 이해하기 쉽게 만드는 데 사용된다. 이를 통해 특정 작업에 필요한 추상화 수준에 따라 그룹 크기를 조정할 수 있다.

나누기(breaks)를 활용하여 관심사 분리 달성

코드를 여러 단위로 나누는 것은 관심사를 분리하는 훌륭한 방법이다. 코드 단락, 함수/클래스/구조체, 파일, 폴더, 외부 라이브러리 등 다양한 유형의 나누기를 사용할 수 있다. 이러한 구분은 코드에 논리적 구분을 만들고 이해하고 탐색하기 쉽게 만드는 데 도움이 된다.

// Main program code
function main() {
let input = getInput(); // Gets user input

if (isValid(input)) {
let processedData = processData(input); // Processes the input data
displayResult(processedData); // Displays the result to the user
} else {
displayError(); // Displays an error message if input is invalid
}
}

// Input validation code
function isValid(input) {
// Checks if the input is valid
// ...
}

// Data processing code
function processData(input) {
// Processes the input data
// ...
}

// User interface code
function getInput() {
// Gets user input
// ...
}

function displayResult(data) {
// Displays the result to the user
// ...
}

function displayError() {
// Displays an error message
// ...
}

위의 예제에서는 메인 프로그램 코드를 입력 유효성 검사, 데이터 처리 및 사용자 인터페이스를 위한 작은 함수로 분리한다. 이렇게 하면 코드를 특정 작업을 처리하는 개별 단위로 분리하여 관심사를 분리할 수 있습니다.

isValid() 함수는 사용자 입력이 유효한지 확인하고, processData() 함수는 입력 데이터를 처리하며, displayResult() 및 displayError() 함수는 사용자에게 결과를 표시하는 작업을 처리한다. 이렇게 코드를 분할하면 코드를 읽고 유지 관리하기가 쉬워지며 각 함수를 개별적으로 테스트하고 디버깅할 수 있다.

물리적인 나누기(hard breaks) 및 규칙(convertion) 사용

물리적인 나누기란 코드를 별도의 파일로 옮기는 것을 말하며, 규칙은 특정 유형의 파일 위치를 표시하는 데 사용되는 일반적인 폴더 구조를 말한다. 이러한 기법은 관련 코드를 잃어버릴 위험을 줄이고 필요할 때 코드를 더 쉽게 찾을 수 있도록 도와준다.

코드 리팩터링

리팩터링은 코드의 외부 동작을 변경하지 않고 코드 품질을 개선하는 작업이다. 리팩터링은 이러한 목표를 더 잘 충족하도록 코드를 최적화하여 코드 구성 및 관심사 분리를 개선하는 데 사용할 수 있다.

마무리

코드 구성과 관심사 분리는 소프트웨어 개발의 중요한 부분이다. 관련된 추상화 수준을 이해하고, 코드를 개별 단위로 나누고, 그룹 크기 조정 및 나누기를 활용하고, 하드 나누기 및 규칙을 사용하고 코드를 리팩토링함으로써 개발자는 이해하기 쉽고, 탐색하고, 유지 관리하기 쉬운 유지 관리 가능하고 확장 가능하며 효율적인 코드를 작성할 수 있다.

--

--

이상문

software developer working mainly in field of streaming, using C++, javascript