코드를 명료하게 표현하는데 가장 크게 기여하는 것은 이름이다.
함수, 변수, 클래스, 모듈 이름만 보고도 무슨 일을 하는지 알아야 한다.
명확한 이름이 떠오르지 않는다면 설계가 잘못되었을 수 있다는 것을 명심해야 한다.
class Data
{
public DateTime dob { get; set; }
public string x { get; set; }
public string y { get; set; }
public int Calc()
{
DateTime n DateTime.Now;
int a = n.Year - dob.Year;
if (n < dob.AddYears(a))
{
a--;
}
return a;
}
}
class Customer
{
public DateTime DateOfBirth { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int CalculateAge()
{
DateTime now = DateTime.Now;
int age = new.Year - DateOfBirth.Year;
if (now < DateOfBirth.AddYear(age))
{
age--;
}
return age;
}
}
Don't Repeat Yourself (DRY)의 원칙
똑같은 구조의 반복은 최악
하나의 클래스 안에 비슷한 함수가 있다면 중복되는 부분을 함수로 추출하자.
혹은 비슷한 함수가 있다면, 공통된 부분을 부모의 함수로 만들고 자식 함수로 각자 호출하는 방법도 있다.
중복된 코드를 하나로 통합하는 것은 리팩토링에서 가장 기본.
class ReportGenerator
{
public string GenerateSalesReport()
{
// 데이터 로드
var date = LoadSaleData();
var report = "Sales Report\n";
// 데이터 처리
foreach (var item in data)
{
report += $"Date : {item.Date}, Sales : {item.Amount}\n";
}
// 보고서 반환
return report;
}
public string GenerateEmplyeeReport()
{
// 데이터 로드
var data = LoadEmployeeData();
var report = "Employee Report\n";
// 데이터 처리
foreach (var item in data)
{
report += $"Name : {item.Name}, Role : {item.Role}\n";
}
// 보고서 반환
return report;
}
}
class ReportGenerator
{
public string GenerateReport<T>(string title, IEnumerable<T> data, Func<T, string> formatFunc)
{
var report = ${title}\n";
{
report += formatFunc(item);
}
return report;
}
public string GenerateSalesReport()
{
var data = LoadSalesData();
return GenerateReport("Sales report", data, item => $Name : {item.Name}, Role : {item.Role}");
}
public string GenerateSalesReport()
{
var data = LoadEmployeeData();
return GenerateReport("Employee Report", data, item => $Name : {item.Name}, Role : {item.Role}");
}
}
짧은 함수는 재사용성도 좋고, 코드를 이해하고 공유하기가 쉽다.
하나의 함수에서 길게 모든걸 처리하는 것보다는 짧은 함수 여러개를 호출하는 구조가 훨씬 좋다.
짧은 함수와 좋은 이름의 조합이 최고.
이런 짧은 함수 여러개를 쓰는 구조가 성능에 문제가 된다고 생각할 수 있지만, 요즘의 프로그래밍 언어에서는 전혀 상관이 없는 이야기이다.
class ReportProcessor
{
public void ProcessReport(string filePath)
{
// 파일 읽기
var lines = File.ReadAllLines(filePath);
// 데이터 처리
var reportData = new List<ReportData>();
foreach (var line in lines)
{
var elements = line.Split(',');
if (elements.Length == 3)
{
var reportItem = new ReportData
{
Date = DateTime.Parse(elements[0]),
Category = elements[1],
Amount = decimal.Parse(elements[2])
};
reportData.Add(reportItem);
}
}
// 데이터 분석
var totalAmount = reportData.Sum(item => item.Amount);
// 결과 출력
Console.WriteLine($"Total Amount: {totalAmount}");
}
}
class ReportProcessor
{
public void ProcessReport(string filePath)
{
var reportData = ReadAndParseFile(filePath);
var totalAmount = CalculateTotalAmount(reportData);
PrintResult(totalAmount);
}
private List<ReportData> ReadAndParseFile(string filePath)
{
var lines = File.ReadAllLines(filePath);
var reportData = new List<ReportData>();
foreach (var line in lines)
{
var reportItem = ParseLine(line);
if (reportItem != null)
{
reportData.Add(reportItem);
}
}
return reportData;
}
private ReportData ParseLine(string line)
{
var elements = line.Split(',');
if (elements.Length == 3)
{
return new ReportData
{
Date = DateTime.Parse(elements[0]),
Category = elements[1],
Amount = decimal.Parse(elements[2])
};
}
return null;
}
private decimal CalculateTotalAmount(List<ReportData> reportData)
{
return reportData.Sum(item => item.Amount);
}
private void PrintResult(decimal totalAmount)
{
Console.WriteLine($"Total Amount: {totalAmount}");
}
}
전역 데이터의 사용은 프로그램의 악취중 가장 독한 악취 중의 하나이다.
전역 변수는 어디서나 변경이 가능해서, 변경 시점을 추적하기 힘들고, 디버깅을 어렵게 만들고, 버그의 원인이 된다.
전역 데이터를 꼭 써야겠다면, 그나마 변경되지 않도록 만들어야 한다.
클래스 전역변수도 왠만하면 변수를 캡슐화하는 것을 습관화하자.
싱글톤 패턴을 싫어하는 프로그래머들은 이 싱글톤 객체가 사실상 전역변수이기 떄문이라고 주장하는 파들도 존재한다. 하지만 싱글톤 객체도 캡슐화를 잘 하면 이것 또한 문제는 없다.
// 전역 데이터
static class GlobalData
{
public static int GlobalCounter;
}
class Processor
{
public void Process()
{
GlobalData.GlobalCounter++; // 전역 데이터 변경
// 다른 처리 로직
}
}
class AnotherProcessor
{
public void AnotherProcess()
{
GlobalData.GlobalCounter++; // 동일한 전역 데이터 변경
// 다른 처리 로직
}
}
// 좋은 예시
// 변경과 접근은 이 클래스를 통해서만 이루어짐
class GlobalCounter
{
private int _counter = 0;
public void Increment()
{
_counter++;
}
public int GetCounter()
{
return _counter;
}
}
class Processor
{
private readonly GlobalCounter _globalCounter;
public Processor(GlobalCounter globalCounter)
{
_globalCounter = globalCounter;
}
public void Process()
{
_globalCounter.Increment();
}
}
class AnotherProcessor
{
private readonly GlobalCounter _globalCounter;
public AnotherProcessor(GlobalCounter globalCounter)
{
_globalCounter = globalCounter;
}
public void AnotherProcess()
{
_globalCounter.Increment();
}
}
올바른 주석은 아주 좋다.
그러나 코드만으로 명확하게 이해되는게 더 좋다. 주석은 사실 코드를 변명하기 위한 장치에 가깝다.
즉, 주석이 필요한 상황일 경우, 주석이 필요없는 코드로 먼저 바꾸는게 우선이다.
public class Calculator
{
// 이 메소드는 두 수를 더합니다.
// a는 첫 번째 숫자입니다.
// b는 두 번째 숫자입니다.
// 결과는 두 숫자의 합입니다.
public int Add(int a, int b)
{
// a와 b를 더한 값을 반환합니다.
return a + b;
}
}
public class NetworkUtils
{
// TCP 소켓 연결에서 타임아웃을 확인하는 메서드
// timeoutInMilliseconds는 타임아웃 시간을 밀리초 단위로 설정합니다.
// 소켓 연결이 타임아웃 시간 내에 이루어지지 않으면 예외를 발생시킵니다.
public void CheckConnectionTimeout(Socket socket, int timeoutInMilliseconds)
{
// 소켓 연결이 설정된 시간 내에 이루어지는지 확인하는 복잡한 로직
// ...
}
}