클린 코드와 코드 스멜
클린 코드와 코드 스멜¶
주제: Programming 레슨: 8 of 16 선수지식: 최소 하나 이상의 언어에서 프로그래밍 경험, 함수와 클래스 숙지 목표: 모범 사례를 따르고 코드 스멜을 인식하며 효과적인 리팩토링 기법을 적용하여 깨끗하고 유지보수 가능한 코드 작성 학습
소개¶
"바보도 컴퓨터가 이해할 수 있는 코드를 작성할 수 있습니다. 좋은 프로그래머는 사람이 이해할 수 있는 코드를 작성합니다." — Martin Fowler
클린 코드(Clean Code)는 읽기 쉽고, 이해하기 쉬우며, 수정하기 쉬운 코드입니다. 인지 부하를 최소화하고, 버그를 줄이며, 협업을 더 쉽게 만듭니다. 이 레슨에서는 Robert C. Martin의 Clean Code, Martin Fowler의 Refactoring, 그리고 기타 업계 모범 사례의 원칙들을 다룹니다.
나쁜 코드의 비용¶
기술 부채(Technical Debt)는 지름길을 택할 때 누적됩니다:
# Technical debt example
def process(data): # What does this process?
x = [] # What is x?
for d in data: # Single-letter variable
if d[0] > 100: # Magic number
x.append(d[1] * 1.2) # What is 1.2?
return x
# The "interest" we pay:
# - Hard to understand
# - Hard to modify
# - Hard to test
# - Bugs hide easily
클린 버전:
PRICE_MARKUP = 1.2
MINIMUM_QUANTITY = 100
def calculate_marked_up_prices(orders):
"""Extract prices from high-quantity orders and apply markup."""
marked_up_prices = []
for order in orders:
quantity = order[0]
price = order[1]
if quantity > MINIMUM_QUANTITY:
marked_up_price = price * PRICE_MARKUP
marked_up_prices.append(marked_up_price)
return marked_up_prices
의미 있는 이름¶
이름은 우리가 의도를 전달하는 주요 방법입니다. 좋은 이름은 코드를 자체 문서화하게 만듭니다.
의도를 드러내는 이름(Intention-Revealing Names)¶
나쁜 예:
int d; // elapsed time in days
좋은 예:
int elapsedTimeInDays;
int daysSinceCreation;
int daysSinceModification;
잘못된 정보 피하기(Avoid Disinformation)¶
나쁜 예:
const accountList = {}; // Not a list, it's an object!
function hp() { } // What does hp mean?
const theAccounts = []; // "the" adds no information
좋은 예:
const accountMap = {};
function calculateHorsePower() { }
// Or if context is clear:
function calculatePower() { }
const accounts = [];
발음 가능한 이름 사용(Use Pronounceable Names)¶
나쁜 예:
genymdhms # generation year, month, day, hour, minute, second
좋은 예:
generation_timestamp
검색 가능한 이름 사용(Use Searchable Names)¶
나쁜 예:
for (int i = 0; i < 7; i++) { // What is 7?
// ...
}
좋은 예:
const int DAYS_IN_WEEK = 7;
for (int day = 0; day < DAYS_IN_WEEK; day++) {
// ...
}
클래스 이름: 명사¶
클래스는 사물(명사)을 나타냅니다:
// Good
class Customer { }
class Account { }
class PaymentProcessor { }
// Bad
class Manager { } // Too vague
class Data { } // Meaningless
class Info { } // Meaningless
메서드 이름: 동사¶
메서드는 동작(동사)을 수행합니다:
# Good
def calculate_total():
pass
def send_email():
pass
def is_valid(): # Boolean: is_, has_, can_
pass
# Bad
def total(): # Noun (ambiguous)
pass
def email(): # Noun
pass
인코딩 피하기(Avoid Encodings)¶
나쁜 예 (헝가리안 표기법):
int iCount;
string strName;
bool bIsActive;
좋은 예:
int count;
string name;
bool isActive;
개념당 한 단어(One Word Per Concept)¶
하나의 용어를 선택하고 일관되게 사용하세요:
일관성 없음:
class UserFetcher { }
class AccountRetriever { }
class ProductGetter { }
일관성 있음:
class UserRepository { }
class AccountRepository { }
class ProductRepository { }
불필요한 단어 피하기(Avoid Noise Words)¶
나쁜 예:
const productData = {};
const productInfo = {};
const theProduct = {};
function getProductData() { }
function getProductInfo() { }
Data와 Info의 차이는? 없습니다!
좋은 예:
const product = {};
function getProduct() { }
함수¶
작은 함수(Small Functions)¶
함수는 한 가지만 해야 하고, 잘 해야 하며, 오직 그것만 해야 합니다.
나쁜 예 (너무 많은 일을 함):
def process_order(order):
# Validate
if not order.get('customer_id'):
raise ValueError("Missing customer ID")
if not order.get('items'):
raise ValueError("No items")
# Calculate total
total = 0
for item in order['items']:
total += item['price'] * item['quantity']
# Apply discount
if order.get('coupon'):
discount = get_discount(order['coupon'])
total -= total * discount
# Save to database
db.save('orders', order)
# Send confirmation email
send_email(order['customer_id'], f"Order confirmed: ${total}")
return total
좋은 예 (단일 책임):
def process_order(order):
validate_order(order)
total = calculate_order_total(order)
save_order(order)
send_confirmation_email(order, total)
return total
def validate_order(order):
if not order.get('customer_id'):
raise ValueError("Missing customer ID")
if not order.get('items'):
raise ValueError("No items")
def calculate_order_total(order):
subtotal = sum(item['price'] * item['quantity']
for item in order['items'])
discount = get_discount_amount(order, subtotal)
return subtotal - discount
def get_discount_amount(order, subtotal):
if coupon := order.get('coupon'):
discount_rate = get_discount(coupon)
return subtotal * discount_rate
return 0
def save_order(order):
db.save('orders', order)
def send_confirmation_email(order, total):
customer_id = order['customer_id']
message = f"Order confirmed: ${total}"
send_email(customer_id, message)
이상적인 인자 개수(Ideal Argument Count)¶
0개 인자 (무항, niladic): 최상 1개 인자 (단항, monadic): 좋음 2개 인자 (이항, dyadic): 괜찮음 3개 인자 (삼항, triadic): 의심스러움 4개 이상 인자 (다항, polyadic): 피하기
나쁜 예:
public void createUser(String firstName, String lastName, String email,
String phone, String address, String city,
String state, String zipCode) {
// Too many parameters!
}
좋은 예:
public class UserData {
private String firstName;
private String lastName;
private String email;
private String phone;
private Address address;
// Getters and setters...
}
public class Address {
private String street;
private String city;
private String state;
private String zipCode;
}
public void createUser(UserData userData) {
// Single parameter (object)
}
부수 효과 없음(No Side Effects)¶
함수는 이름이 말하는 것만 해야 하며, 그 이상은 하지 말아야 합니다.
나쁜 예 (숨겨진 부수 효과):
function checkPassword(username, password) {
const user = db.getUser(username);
if (user.password === hash(password)) {
Session.initialize(); // Side effect! Not mentioned in name
return true;
}
return false;
}
좋은 예:
function isPasswordCorrect(username, password) {
const user = db.getUser(username);
return user.password === hash(password);
}
// Caller controls side effects
if (isPasswordCorrect(username, password)) {
Session.initialize();
redirectToHomePage();
}
명령-쿼리 분리(Command-Query Separation)¶
함수는 무언가를 하거나 (명령, command) 무언가를 반환하거나 (쿼리, query) 해야 하며, 둘 다 하면 안 됩니다.
나쁜 예 (둘 다 함):
def set_and_check_attribute(name, value):
"""Sets attribute and returns True if it was changed."""
old_value = get_attribute(name)
set_attribute(name, value)
return old_value != value
# Confusing usage:
if set_and_check_attribute('status', 'active'):
# Did we set it? Or just check it?
pass
좋은 예 (명령과 쿼리 분리):
def set_attribute(name, value):
"""Sets attribute (command)."""
attributes[name] = value
def has_attribute_changed(name, new_value):
"""Checks if value would change (query)."""
return get_attribute(name) != new_value
# Clear usage:
if has_attribute_changed('status', 'active'):
set_attribute('status', 'active')
DRY: 반복하지 마세요(Don't Repeat Yourself)¶
나쁜 예:
public void printUserReport(User user) {
System.out.println("Name: " + user.getFirstName() + " " + user.getLastName());
System.out.println("Email: " + user.getEmail());
System.out.println("Phone: " + user.getPhone());
}
public void emailUserDetails(User user) {
String details = "Name: " + user.getFirstName() + " " + user.getLastName() + "\n";
details += "Email: " + user.getEmail() + "\n";
details += "Phone: " + user.getPhone();
sendEmail(user.getEmail(), details);
}
좋은 예:
public String formatUserDetails(User user) {
return String.format("Name: %s %s\nEmail: %s\nPhone: %s",
user.getFirstName(), user.getLastName(),
user.getEmail(), user.getPhone());
}
public void printUserReport(User user) {
System.out.println(formatUserDetails(user));
}
public void emailUserDetails(User user) {
sendEmail(user.getEmail(), formatUserDetails(user));
}
코드 스멜(Code Smells)¶
코드 스멜은 잠재적 문제의 지표입니다. 항상 나쁜 코드를 의미하는 것은 아니지만 주의가 필요합니다.
긴 메서드(Long Method)¶
스멜:
def process_payment(order):
# 200 lines of code...
pass
리팩토링: 메서드 추출(Extract Method)
def process_payment(order):
validate_payment_info(order)
charge_amount = calculate_charge(order)
transaction_id = charge_credit_card(charge_amount, order.card)
update_inventory(order)
send_confirmation(order, transaction_id)
return transaction_id
거대한 클래스(Large Class, God Class)¶
스멜:
public class User {
// Authentication
public boolean login(String password) { }
public void logout() { }
// Profile management
public void updateProfile() { }
public void uploadAvatar() { }
// Permissions
public boolean hasPermission(String permission) { }
public void grantPermission(String permission) { }
// Email
public void sendWelcomeEmail() { }
public void sendPasswordResetEmail() { }
// Analytics
public void trackLogin() { }
public void trackPageView() { }
// ... 50 more methods
}
리팩토링: 클래스 추출(Extract Class)
public class User {
private UserProfile profile;
private UserAuthentication auth;
private UserPermissions permissions;
// Minimal interface
}
public class UserAuthentication {
public boolean login(String password) { }
public void logout() { }
}
public class UserProfile {
public void update() { }
public void uploadAvatar() { }
}
public class UserPermissions {
public boolean has(String permission) { }
public void grant(String permission) { }
}
기능 선망(Feature Envy)¶
자신의 클래스보다 다른 클래스의 데이터/메서드를 더 많이 사용하는 메서드.
스멜:
class Order:
def __init__(self, customer):
self.customer = customer
def get_discount(self):
# Uses customer data extensively
if self.customer.is_premium():
if self.customer.years_active > 5:
return 0.20
else:
return 0.10
else:
return 0.05
리팩토링: 메서드 이동(Move Method)
class Customer:
def get_discount_rate(self):
if self.is_premium():
if self.years_active > 5:
return 0.20
else:
return 0.10
else:
return 0.05
class Order:
def __init__(self, customer):
self.customer = customer
def get_discount(self):
return self.customer.get_discount_rate()
데이터 덩어리(Data Clumps)¶
항상 함께 나타나는 데이터 그룹.
스멜:
function createUser(firstName, lastName, email, street, city, state, zip) {
// ...
}
function updateUser(userId, firstName, lastName, email, street, city, state, zip) {
// ...
}
리팩토링: 매개변수 객체 도입(Introduce Parameter Object)
class Address {
constructor(street, city, state, zip) {
this.street = street;
this.city = city;
this.state = state;
this.zip = zip;
}
}
class UserInfo {
constructor(firstName, lastName, email, address) {
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
this.address = address;
}
}
function createUser(userInfo) {
// ...
}
function updateUser(userId, userInfo) {
// ...
}
산탄총 수술(Shotgun Surgery)¶
하나의 변경이 많은 클래스의 수정을 요구.
스멜:
// Adding a new payment method requires changes in:
class Order { } // Add payment type check
class PaymentForm { } // Add UI for new payment
class PaymentValidator { } // Add validation
class Receipt { } // Add receipt format
class Analytics { } // Add tracking
리팩토링: 관련 변경을 한 곳으로 이동 (전략 패턴)
interface PaymentMethod {
void process(double amount);
String getReceiptFormat();
void validate();
void track();
}
class CreditCardPayment implements PaymentMethod { }
class PayPalPayment implements PaymentMethod { }
class CryptoPayment implements PaymentMethod { }
// Now adding a new payment method only requires one new class
발산적 변경(Divergent Change)¶
하나의 클래스가 다른 이유로 여러 방식으로 자주 변경됨.
스멜:
class Report:
def generate(self):
data = self.fetch_data() # Database logic (reason 1)
formatted = self.format(data) # Formatting logic (reason 2)
self.send_email(formatted) # Email logic (reason 3)
리팩토링: 클래스 추출 (단일 책임 원칙)
class ReportDataFetcher:
def fetch(self):
# Database logic only
pass
class ReportFormatter:
def format(self, data):
# Formatting logic only
pass
class ReportEmailer:
def send(self, report):
# Email logic only
pass
class ReportGenerator:
def __init__(self, fetcher, formatter, emailer):
self.fetcher = fetcher
self.formatter = formatter
self.emailer = emailer
def generate(self):
data = self.fetcher.fetch()
formatted = self.formatter.format(data)
self.emailer.send(formatted)
기본형 집착(Primitive Obsession)¶
단순한 작업에 작은 객체 대신 원시 타입 사용.
스멜:
void sendMessage(std::string phoneNumber) {
// Is phoneNumber valid? No type safety
if (phoneNumber.length() != 10) {
throw std::invalid_argument("Invalid phone");
}
// ...
}
sendMessage("invalid"); // Compiles, but wrong
리팩토링: 값 객체로 대체(Replace Data Value with Object)
class PhoneNumber {
private:
std::string number;
public:
PhoneNumber(const std::string& num) {
if (num.length() != 10 || !isDigits(num)) {
throw std::invalid_argument("Invalid phone number");
}
number = num;
}
std::string toString() const { return number; }
private:
bool isDigits(const std::string& str) const {
return std::all_of(str.begin(), str.end(), ::isdigit);
}
};
void sendMessage(const PhoneNumber& phoneNumber) {
// Type safety! phoneNumber is guaranteed valid
}
// sendMessage("invalid"); // Compiler error!
sendMessage(PhoneNumber("1234567890")); // OK
Switch 문(Switch Statements)¶
스멜:
public double calculatePay(Employee employee) {
switch (employee.type) {
case HOURLY:
return employee.hours * employee.hourlyRate;
case SALARIED:
return employee.monthlySalary;
case COMMISSIONED:
return employee.baseSalary + employee.commission;
default:
throw new IllegalArgumentException("Unknown employee type");
}
}
// Problem: This switch appears in multiple places!
// If we add a new employee type, we must update all switches.
리팩토링: 다형성으로 조건문 대체(Replace Conditional with Polymorphism)
abstract class Employee {
public abstract double calculatePay();
}
class HourlyEmployee extends Employee {
private double hours;
private double hourlyRate;
public double calculatePay() {
return hours * hourlyRate;
}
}
class SalariedEmployee extends Employee {
private double monthlySalary;
public double calculatePay() {
return monthlySalary;
}
}
class CommissionedEmployee extends Employee {
private double baseSalary;
private double commission;
public double calculatePay() {
return baseSalary + commission;
}
}
// Adding a new type only requires a new class
추측성 일반화(Speculative Generality)¶
스멜:
class AbstractFactoryProviderSingleton:
"""Might need this someday..."""
pass
def calculate_with_future_algorithm_support(data, algorithm='current'):
# Supports 5 algorithms we might need in the future
pass
리팩토링: YAGNI (필요하지 않을 것이다) — 삭제하세요!
def calculate(data):
# Implement what you need now
pass
# Add algorithms when actually needed
죽은 코드(Dead Code)¶
스멜:
function processOrder(order) {
// validateAddress(order.address); // Commented out
calculateTotal(order);
// if (false) { // Always false
// applyOldDiscountLogic(order);
// }
}
리팩토링: 삭제하세요! 버전 관리가 기억합니다.
function processOrder(order) {
calculateTotal(order);
}
리팩토링 기법¶
메서드 추출(Extract Method)¶
이전:
def print_invoice(invoice):
print("**** Invoice ****")
print(f"Customer: {invoice['customer']}")
print(f"Amount: ${invoice['amount']}")
# Calculate discount
discount = 0
if invoice['amount'] > 1000:
discount = invoice['amount'] * 0.1
print(f"Discount: ${discount}")
print(f"Total: ${invoice['amount'] - discount}")
이후:
def print_invoice(invoice):
print_header()
print_customer_info(invoice)
discount = calculate_discount(invoice)
print_amounts(invoice, discount)
def print_header():
print("**** Invoice ****")
def print_customer_info(invoice):
print(f"Customer: {invoice['customer']}")
print(f"Amount: ${invoice['amount']}")
def calculate_discount(invoice):
if invoice['amount'] > 1000:
return invoice['amount'] * 0.1
return 0
def print_amounts(invoice, discount):
print(f"Discount: ${discount}")
print(f"Total: ${invoice['amount'] - discount}")
변수/메서드 이름 변경(Rename Variable/Method)¶
이전:
public void calc(int d) {
int r = d * 0.1;
System.out.println(r);
}
이후:
public void calculateDiscount(int orderAmount) {
int discountAmount = orderAmount * 0.1;
System.out.println(discountAmount);
}
설명 변수 도입(Introduce Explaining Variable)¶
이전:
if ((platform.toUpperCase().includes('MAC') ||
platform.toUpperCase().includes('LINUX')) &&
browser.toUpperCase().includes('CHROME')) {
// ...
}
이후:
const isMacOrLinux = platform.toUpperCase().includes('MAC') ||
platform.toUpperCase().includes('LINUX');
const isChrome = browser.toUpperCase().includes('CHROME');
if (isMacOrLinux && isChrome) {
// ...
}
매직 넘버를 명명된 상수로 대체(Replace Magic Number with Named Constant)¶
이전:
double calculateMonthlyPayment(double principal, double rate, int years) {
return principal * rate / 12 / (1 - pow(1 + rate / 12, -years * 12));
}
이후:
const int MONTHS_PER_YEAR = 12;
double calculateMonthlyPayment(double principal, double annualRate, int years) {
double monthlyRate = annualRate / MONTHS_PER_YEAR;
int totalMonths = years * MONTHS_PER_YEAR;
return principal * monthlyRate / (1 - pow(1 + monthlyRate, -totalMonths));
}
메서드 이동(Move Method)¶
메서드를 속한 클래스로 이동.
이전:
class Customer:
def __init__(self, name):
self.name = name
class Order:
def __init__(self, customer, amount):
self.customer = customer
self.amount = amount
def get_discount(self):
# Uses customer data, not order data
if self.customer.name.startswith('VIP'):
return 0.2
return 0.1
이후:
class Customer:
def __init__(self, name):
self.name = name
def get_discount_rate(self):
if self.name.startswith('VIP'):
return 0.2
return 0.1
class Order:
def __init__(self, customer, amount):
self.customer = customer
self.amount = amount
def get_discount(self):
return self.amount * self.customer.get_discount_rate()
원칙¶
DRY: 반복하지 마세요(Don't Repeat Yourself)¶
모든 지식 조각은 단일하고 명확한 표현을 가져야 합니다.
KISS: 단순하게 유지하세요(Keep It Simple, Stupid)¶
단순성은 핵심 목표여야 합니다. 불필요한 복잡성을 피하세요.
복잡함:
class AbstractFactoryBuilderProvider:
@staticmethod
def create_instance_with_dependency_injection(config):
# 50 lines of complex logic
pass
단순함:
def create_user(name, email):
return User(name, email)
YAGNI: 필요하지 않을 것이다(You Aren't Gonna Need It)¶
실제로 필요할 때까지 기능을 구현하지 마세요.
YAGNI 위반:
class User {
// Just in case we need these someday...
private String backupEmail;
private String alternatePhone;
private String preferredLanguage; // We only support English
private boolean isVerified; // No verification process yet
}
최소 놀람 원칙(Principle of Least Surprise)¶
코드는 사용자가 예상하는 대로 동작해야 합니다.
놀라움:
function getUsers() {
const users = db.query('SELECT * FROM users');
users.forEach(u => u.lastAccessed = Date.now()); // Side effect!
db.update(users);
return users;
}
놀람 없음:
function getUsers() {
return db.query('SELECT * FROM users');
}
function updateLastAccessed(users) {
users.forEach(u => u.lastAccessed = Date.now());
db.update(users);
}
보이스카우트 규칙(Boy Scout Rule)¶
"발견한 코드보다 더 나은 상태로 남겨두세요."
작은 개선도 누적됩니다: - 혼란스러운 변수 이름 변경 - 긴 메서드 추출 - 누락된 주석 추가 - 죽은 코드 삭제
주석(Comments)¶
좋은 주석¶
법적 주석:
// Copyright (C) 2024 Company Inc. All rights reserved.
정보 제공 주석:
# Returns a list of tuples: (user_id, username, last_login)
def get_active_users():
pass
의도 설명:
// We use a hash instead of array because lookup needs to be O(1)
std::unordered_map<int, User> userCache;
명확화:
// Third-party API returns timestamps in milliseconds, not seconds
const timestampSeconds = apiResponse.timestamp / 1000;
TODO 주석:
# TODO: Add caching to improve performance (issue #1234)
def fetch_user_data(user_id):
pass
나쁜 주석¶
중복 주석:
// Get the name
public String getName() {
return name;
}
// Increment i
i++;
오해의 소지가 있는 주석:
# Returns the user's age
def get_user_info(user_id):
return db.get_user(user_id) # Returns entire user object, not just age!
잡음 주석:
/**
* Constructor.
*/
public User() {
}
주석 처리된 코드:
function processOrder(order) {
// validateAddress(order); // Don't comment out, delete it!
// applyDiscount(order);
calculateTotal(order);
}
일지 주석:
// 2024-01-15: Added validation - John
// 2024-01-20: Fixed bug - Sarah
// 2024-02-01: Refactored - Mike
public void processPayment() {
// Use version control for history!
}
자체 문서화 코드(Self-Documenting Code)¶
스스로 설명하는 코드를 선호하세요:
주석이 필요함:
# Check if user is eligible for discount
if u.t == 'premium' and u.yrs > 2:
pass
자체 문서화:
def is_eligible_for_loyalty_discount(user):
return user.type == 'premium' and user.years_active > 2
if is_eligible_for_loyalty_discount(user):
pass
형식(Formatting)¶
일관된 형식은 가독성을 향상시킵니다.
수직 형식(Vertical Formatting)¶
신문 은유: 가장 중요한 정보가 위에.
# Public interface first
class OrderService:
def process_order(self, order):
self._validate(order)
total = self._calculate_total(order)
self._save(order)
return total
# Private helpers below
def _validate(self, order):
pass
def _calculate_total(self, order):
pass
def _save(self, order):
pass
수평 형식(Horizontal Formatting)¶
줄을 짧게 유지 (80-120자).
나쁜 예:
public void createUser(String firstName, String lastName, String email, String phone, Address address, List<String> permissions) { }
좋은 예:
public void createUser(
String firstName,
String lastName,
String email,
String phone,
Address address,
List<String> permissions
) {
// ...
}
팀 규약(Team Conventions)¶
린터와 포매터 사용: - JavaScript: ESLint, Prettier - Python: pylint, black - Java: Checkstyle, Google Java Format - C++: clang-format
요약¶
| 원칙 | 지침 |
|---|---|
| 이름 | 의도 드러내기, 잘못된 정보 피하기, 발음 가능한 이름 사용 |
| 함수 | 작게, 한 가지만, 적은 인자, 부수 효과 없음 |
| 주석 | 왜인지 설명, 무엇인지는 설명 안 함; 자체 문서화 코드 선호 |
| DRY | 반복하지 마세요 |
| KISS | 단순하게 유지 |
| YAGNI | 필요하지 않을 것이다 |
| 보이스카우트 규칙 | 발견한 것보다 나은 상태로 남겨두기 |
주의할 코드 스멜: - 긴 메서드/거대한 클래스 - 기능 선망 - 데이터 덩어리 - 산탄총 수술 - 기본형 집착 - Switch 문 (다형성 고려) - 죽은 코드
연습 문제¶
연습 문제 1: 이 코드 리팩토링¶
def p(o):
t = 0
for i in o['items']:
t += i['p'] * i['q']
if o.get('c'):
d = 0.1 if o['c'] == 'SAVE10' else 0.2 if o['c'] == 'SAVE20' else 0
t = t - (t * d)
return t
클린 코드 원칙을 따르도록 리팩토링하세요.
연습 문제 2: 코드 스멜 식별¶
이 코드를 검토하고 최소 5개의 코드 스멜을 식별하세요:
public class UserManager {
public void process(String type, String id, String data) {
if (type.equals("create")) {
// 50 lines of user creation logic
} else if (type.equals("update")) {
// 50 lines of user update logic
} else if (type.equals("delete")) {
// 50 lines of user deletion logic
}
}
public String getUserFirstName(String id) {
String[] data = db.query("SELECT * FROM users WHERE id = " + id);
return data[0];
}
public String getUserLastName(String id) {
String[] data = db.query("SELECT * FROM users WHERE id = " + id);
return data[1];
}
public String getUserEmail(String id) {
String[] data = db.query("SELECT * FROM users WHERE id = " + id);
return data[2];
}
}
연습 문제 3: 클린 함수 작성¶
클린 코드 원칙을 따르도록 이 함수를 다시 작성하세요:
function validateAndProcessForm(formData) {
if (!formData.email || !formData.email.includes('@')) {
alert('Invalid email');
return false;
}
if (!formData.password || formData.password.length < 8) {
alert('Password too short');
return false;
}
if (formData.password !== formData.confirmPassword) {
alert('Passwords do not match');
return false;
}
const user = {
email: formData.email,
password: hash(formData.password),
created: Date.now()
};
db.save(user);
sendWelcomeEmail(user.email);
redirectToHomePage();
return true;
}
연습 문제 4: 보이스카우트 규칙 적용¶
코드베이스에서 파일을 찾아 약간 개선하세요: 1. 이름이 잘못 지어진 변수 하나 이름 변경 2. 긴 메서드 하나 추출 3. 죽은 코드 하나 제거 4. 설명 변수 하나 추가
변경 사항을 문서화하세요.
연습 문제 5: 코드 리뷰 체크리스트¶
이 레슨을 기반으로 코드 리뷰 체크리스트를 만드세요. 최소한 포함할 것: - 5개의 이름 지정 검사 - 5개의 함수 검사 - 5개의 코드 스멜 검사 - 3개의 주석 검사