안 좋은 코드란? - 1

이준호·2023년 12월 22일
0

안 좋은 코드란 ?



📌 기이한 이름 (Mysterious Name)

  • 코드를 명료하게 표현하는데 가장 크게 기여하는 것은 이름이다.

  • 함수, 변수, 클래스, 모듈 이름만 보고도 무슨 일을 하는지 알아야 한다.

  • 명확한 이름이 떠오르지 않는다면 설계가 잘못되었을 수 있다는 것을 명심해야 한다.

➔ 안 좋은 예시

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;
    }
}











📌 중복 코드 (Duplicated Code)

  • 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}");
    }
}











📌 긴 함수 (Long Function)

  • 짧은 함수는 재사용성도 좋고, 코드를 이해하고 공유하기가 쉽다.

  • 하나의 함수에서 길게 모든걸 처리하는 것보다는 짧은 함수 여러개를 호출하는 구조가 훨씬 좋다.

  • 짧은 함수와 좋은 이름의 조합이 최고.

  • 이런 짧은 함수 여러개를 쓰는 구조가 성능에 문제가 된다고 생각할 수 있지만, 요즘의 프로그래밍 언어에서는 전혀 상관이 없는 이야기이다.

➔ 안 좋은 예시

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}");
    }
}











📌 전역 변수의 남용 (Global Data)

  • 전역 데이터의 사용은 프로그램의 악취중 가장 독한 악취 중의 하나이다.

  • 전역 변수는 어디서나 변경이 가능해서, 변경 시점을 추적하기 힘들고, 디버깅을 어렵게 만들고, 버그의 원인이 된다.

  • 전역 데이터를 꼭 써야겠다면, 그나마 변경되지 않도록 만들어야 한다.

  • 클래스 전역변수도 왠만하면 변수를 캡슐화하는 것을 습관화하자.

  • 싱글톤 패턴을 싫어하는 프로그래머들은 이 싱글톤 객체가 사실상 전역변수이기 떄문이라고 주장하는 파들도 존재한다. 하지만 싱글톤 객체도 캡슐화를 잘 하면 이것 또한 문제는 없다.

➔ 안 좋은 예시

// 전역 데이터
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();
    }
}











📌 주석의 남용 (Comments)

  • 올바른 주석은 아주 좋다.

  • 그러나 코드만으로 명확하게 이해되는게 더 좋다. 주석은 사실 코드를 변명하기 위한 장치에 가깝다.

  • 즉, 주석이 필요한 상황일 경우, 주석이 필요없는 코드로 먼저 바꾸는게 우선이다.

➔ 안 좋은 예시

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)
    {
        // 소켓 연결이 설정된 시간 내에 이루어지는지 확인하는 복잡한 로직
        // ...
    }
}
profile
No Easy Day

0개의 댓글