함수를 작게 만들어야한다는 것은 아는데, 실무에서 적용하려고 할때는 버튼 클릭하나에 많은 로직이 필요해서 어떻게 함수를 작게 쪼개야 하는 것인지 감이 안왔었다.
이 책에 나오는 나쁜 함수 예시를 보고 불과 몇달 전에 내가 싸놓은 똥같은 코드들이 주마등처럼 스쳐지나갔다.
1. 추상화
주석으로 함수를 주절주절 설명하는게 아니라 함수에 의미를 담고 표현해야 한다.
함수를 읽기 쉽게 만들기 위해서는 함수를 작게 만들어야한다.
함수는 한 가지를 해야한다.
여기서 '한 가지'의 기준은 추상화 수준이 하나인 단계만 수행하는 것이 한 가지다.
좋은 함수를 만들기 위해서는 하나의 추상화 수준을 일관되게 유지해야한다.
추상화 수준이란 함수가 얼마나 구체적인 단계까지 신경 쓰느냐를 의미한다.
함수가 무엇(What)을 하는지에 집중하면 추상화 수준이 높은 것이고, 어떻게(How)하는지를 구체적으로 다루면 추상화 수준이 낮은 것이다.
❌ 나쁜 함수!
function 스파게티_만들기() {
1. 재료를 준비한다. (높은 수준)
2. 칼을 서랍에서 꺼내고, 양파 껍질을 벗긴 뒤, 0.5cm 간격으로 다진다. (낮은 수준)
3. 파스타를 삶는다. (높은 수준)
4. 소스를 만든다. (높은 수준)
5. 가스레인지 불을 중간 세기로 켜고, 팬에 올리브 오일을 두른다. (낮은 수준)
6. 완성된 요리를 접시에 담는다. (높은 수준)
}
추상화 수준이 섞여있으면 함수에 세부사항을 점점 더 추가하게 된다.
또한, 코드의 가독성이 떨어진다.
✅ 좋은 함수!
// [최상위 레벨] 전체적인 흐름만 보여준다. (높은 추상화 수준)
function 스파게티_만들기() {
1. 재료_준비하기();
2. 파스타_삶기();
3. 소스_만들기();
4. 재료_합치고_마무리하기();
}
// [중간 레벨] '재료 준비'의 구체적인 절차를 보여준다.
function 재료_준비하기() {
1. 채소_손질하기();
2. 고기_밑간하기();
3. 향신료_계량하기();
}
// [가장 낮은 레벨] '채소 손질'의 가장 구체적인 방법을 보여준다.
function 채소_손질하기() {
1. 양파_다지기('0.5cm');
2. 마늘_편썰기();
}
이렇게 최상위 함수에서는 전체적인 흐름만 보여준다.
무엇을 하는지 위에서 아래로 이야기처럼 읽힌다.
그리고 구체적인 방법은 하위 함수에게 위임한다.
2. 다형성
함수를 한가지 작업만 하도록 하라했는데 switch 문은 N가지를 처리하는 조건문이다.
switch문이 프로그램 곳곳에 있으면 유지보수 지옥이 된다.
코드를 변경할 일이 잦게 생길 수 있으며, 실수가 발생하기 쉽다.
switch문은 다형성을 이용하여 좋은 함수로 만들 수 있다.
직원의 유형(type)에 따라 급여를 계산해 반환하는 함수로 나쁜 예시, 좋은 예시를 비교해보았다.
❌ 나쁜 함수!
// 직원 정보를 담는 단순한 클래스
class Employee {
public String type;
public double hoursWorked;
public double monthlySalary;
}
// 급여를 계산하는 로직
class PayCalculator {
public double calculatePay(Employee e) {
switch (e.type) {
case "FULL_TIME":
return e.monthlySalary;
case "PART_TIME":
return e.hoursWorked * 10000; // 시급 1만원
case "CONTRACTOR":
return e.monthlySalary * 0.967; // 세금 3.3% 공제
// 만약 '인턴' 직군이 추가되면? 이 코드를 또 수정해야 한다!
}
return 0.0;
}
}
위 함수를 보면 가독성이 그리 떨어져보이진 않는다.
하지만, switch문으로 모든 로직을 처리하기보다 다형성을 활용할 수 있다는 사실을 기억해두면 좋겠다.
모든 직원은 급여를 계산할 수 있다는 의미를 가진 '추상 클래스'를 만든다.
그리고 직원 유형별로 급여계산 로직을 구현해준다.
✅ 좋은 함수!
// 1. Employee 추상 클래스 선언
abstract class Employee {
abstract double calculatePay();
}
// 2. 규칙을 실제로 구현하는 클래스들
class FullTimeEmployee extends Employee {
private double monthlySalary;
@Override
double calculatePay() {
return this.monthlySalary;
}
}
class PartTimeEmployee extends Employee {
private double hoursWorked;
@Override
double calculatePay() {
return this.hoursWorked * 10000;
}
}
class ContractorEmployee extends Employee {
private double monthlySalary;
@Override
double calculatePay() {
return this.monthlySalary * 0.967;
}
}
각 Employee 객체는 스스로 급여를 계산할 수 있다.
이제 직원 타입에 따라 객체를 생성해주는 클래스를 만들 수 있다.
나쁜 함수 예시와 크게 다를바 없어보일 수 있지만, 이 switch문은 비즈니스 로직에서는 쓰이지 않을 것이다.
// 오직 '객체 생성'으로 한정되어있는 역할!
// Employee 객체 생성 책임을 지는 공장 클래스
class EmployeeFactory {
public static Employee makeEmployee(String employeeType) {
switch (employeeType) {
case "FULL_TIME":
return new FullTimeEmployee();
case "PART_TIME":
return new PartTimeEmployee();
case "CONTRACTOR"
return new ContractorEmployee();
default:
throw new IllegalArgumentException("Unknown employee type");
}
}
}
public static void main(String[] args) {
Employee employee = EmployeeFactory.makeEmployee("PART_TIME");
// 어떤 타입의 객체인지 묻지도 따지지도 않고 그냥 "급여 계산해!" 라고 메시지를 보낸다.
// 그러면 객체가 알아서 자신의 방식(다형성)으로 급여를 계산한다.
double pay = employee.calculatePay();
System.out.println("지급할 급여: " + pay);
}
직원 유형에 따라 급여를 계산하는 메인 로직은 EmployeeFactory안에 숨어 있기때문에 switch문없이 만들 수 있다.
실무에서는 서비스단에서 비즈니스 로직을 처리할때 이런 방식으로 다형성을 활용할 수 있다.
'프로그래밍 언어 > Java' 카테고리의 다른 글
| Java 8 이후부터 지원되는 Stream API 알아보기 (0) | 2025.05.04 |
|---|---|
| [책 리뷰] 객체지향의 사실과 오해 - 객체지향의 본질 (0) | 2024.12.31 |
| [IntelliJ] Live Templates 사용법 | 반복되는 코드 편하게 작성하기 (0) | 2024.08.08 |
| [JSP] 게시판 만들기 | 5편.게시판 기능 구현하기 (1) | 2024.02.07 |
| [JSP] 게시판 만들기 | 4편.회원가입 기능 구현하기 / 로그아웃 (1) | 2024.02.07 |