# 17장. 냄새와 휴리스틱

## Overview

* 휴리스틱
  * Heutiskein → '찾아내다', '발견하다’

🌱 발견법 - 간편 추론, 경험을 기반으로 \*\*그때그때 상황과 직관에 따라 행동\*\*하여 결론을 도출하는 것

* 휴리스틱 알고리즘
  * 최적 해를 보장하지 않는다.
  * Greedy
  * Travelling Salesman Problem

***

## 저자의 휴리스틱

### 주석

🌱 주석은 코드만으로 다하지 못하는 설명을 부언한다.

1. 부적절한 정보

   * 코드만으로 충분한데 구구절절 설명하는 주석은 없어야 한다.

   ```kotlin
   i++ // i 증가
   ```

   * function **signature**만 달랑 기술하는 Javadoc은 사용하지 않아야한다.

   ```kotlin
   /** 
    * @param sellRequest
    * @return
    * @throws ManagedComponentException
    */
   public SellResponse beginSellItem(SellRequest sellRequest)
   throws ManagedComponentException
   ```
2. 주석 처리된 코드
   * 주석 처리된 코드를 발견하면 즉각 지워버려야 한다.

### 함수

1. 너무 많은 parameter

   * parameter 개수는 작을수록 좋다. 아예 없는게 가장 좋다!
   * 넷 이상은 최대한 피해야한다.

   ```kotlin
   fun testEffect(key1: Any?, key2: Any?, key3: Any?, key4: Any?) { }

   fun testEffect(
       key1: Any?, 
       key2: Any?, 
       key3: Any?, 
       key4: Any?
   ) { }
   ```
2. flag parameter ✨
   * boolean, int, enum 등 플래그 파라미터는 함수가 내부적으로 여러 기능을 수행한다는 뜻
   * **플래그를 넘겨 제어하는 대신, 새로운 함수를 만들자.**
3. 죽은 함수
   * 아무도 호출하지 않는 함수는 삭제해야 한다.

***

### 일반

1. 한 소스 파일에 여러 언어를 사용하는 것
   * 이상적으로는 소스 파일 하나 당, 하나의 언어만 사용해야 한다.
   * 불가피한 경우에 최대한 언어 범위를 줄여야 한다.
2. 당연한 기능을 구현해라

   * 함수나 클래스는 프로그래머가 당연히 받아들일 수 있는 기능을 제공해야 한다.

   ```kotlin
   Day day = DayDate.StringToDay(String dayName);

   // Monday -> Day.MONDAY
   ```
3. 중복✨
   * DRY (Don’t Repeat Yourself)
   * 코드에서 중복을 발견하면, 추상화의 기회이다!
   * 중복 코드 → 하위 루틴이나 클래스로 분리 가능
4. 올바른 추상화 수준을 설정하기 ✨
   * high level - 집의 형태, 외관, 공간, 방의 배치 (**정책**)
   * low level - 콘센트, 전등의 위치, 지붕의 크기 등의 기초공사 수준 (**세부사항**)
   * **의존성 역전 법칙 (DIP)** → 집의 형태가 전등의 위치나 색깔에 의해 바뀌어선 안된다. low level이 high level에 의존
   * 기초 클래스 (interface, abstract) 에서 파생 클래스에 의존해서는 안된다.
5. 기능 욕심

   * 클래스 메서드는 자기 클래스의 상태에 관심을 가져야지, 다른 객체를 조작해서는 안된다.

   ```kotlin
   public class HourlyPayCalculator {
       public Money calculateWeeklyPay(**HourlyEmployee e**) {
           int tenthRate = e.getTenthRate().getPennies();
           int tenthsWorked = e.getTenthsWorked();
           int straightTime = Math.min(400, tenthWorked);
           int overTime = Math.max(0, tenthsWorked - straightTime);
           int straightPay = straightTime * tenthRate;
           int overtimePay = (int)Math.round(overTime * tenthRate * 1.5);
           return new Money(straightPay + overtimePay);
       }
   }
   ```
6. 부적절한 static 함수

   * Math.max(double a, double b) 는 좋은 static 함수. 모든 정보를 parameter를 통해 가져온다.
   * **재정의 할 가능성이 있는 함수를 static으로 만들어선 안된다. ✨**
   * 시급을 계산하는 방법은 언제든 바뀔 수 있다.

   ```kotlin
   HourlyPayCalculator.calculatePay(employee, overtimeRate);
   ```
7. 서술적 변수 (Descriptive Variable)

   * 서술적 변수 이름을 사용해서 가독성을 높이자.

   ```kotlin
   Matcher match = headerPattern.matcher(line);
   if(match.find())
   {
     String **key** = match.group(1);
     String **value** = match.group(2);
     headers.put(key.toLowerCase(), value);
   }
   ```

   * 변수 이름을 지어보자
     * The total cost of a purchase (**totalCost**)
     * The total cost of a purchase, including tax (**totalCostWithTax**)
     * The number of columns in a grid (**colNum**)
     * The number of rows in a grid (**rowNum**)
     * The width of a rectangle (**rectWidth**)
   * 플래그 변수
     * **isRaining** , **isTooHot** , **hasPassedExam → is** , **has** 또는 **contains** 와 같은 동사로 시작
     * 변수 이름을 positive 하게 → `isNotFull` 대신 `isEmpty` (부정 조건을 피하자)
8. 명명된 상수로 교체해라! ✨

   * 코드에서 숫자를 사용하지 마라 (숫자는 명명된 상수 뒤로 숨기자)
   * ex) 책의 한 페이지당, 55줄을 인쇄한다 → `LINES_PER_PAGE`

   ```kotlin
   double circleArea = radius * radius * **Math.PI**
   ```
9. 조건을 캡슐화 하라

```kotlin
if (timer.hasExpired() && !timer.isRecurrent())

보다는

if (shouldBeDeleted(timer))

가 더 좋다.
```

10. 함수는 한 가지 일만 한다.

```kotlin
public void pay(){
	for (Employee e : employees) { // 직원 목록을 돌며
    	if (e.isPaypay()) { // 월급일이라면
        	Money pay = e.calculatePay(); // 급여를 계산해서
          e.deliverPay(pay); // 급여 지급
        }
    }
```

* 세개로 나눌 수 있다.

```kotlin
public void pay(){
	for (Employee e : employees) 
    	payIfNecessary(e);
}

private void payIfNecessary(Employee e) {
	if(e.isPayday()){
    	calculateAndDeliverPay(e)
    }
}

private void calculateAndDeliverPay(Employee e) {
	Money pay = e.calculatePay();
    e.deliverPay(pay);
}
```

1. 시간적인 결합을 드러내라

   * 함수 파라미터를 통해 함수가 호출되는 순서를 드러낼 수 있다.

   ```kotlin
   public class MoogDiver {
       Gradient gradient;
       List<Spline> splines;

       public void dive(String reason) {
         saturateGradient(); ...1
         reticulateSplines(); ...2
         diveForMoog(reason); ...3
       }
       ...
     }

   // 프로그래머가 reticulateSplines 를 먼저 호출한다면?
   ```

   * 파라미터를 사용!

   ```kotlin
   public class MoogDiver {
       Gradient gradient;
       List<Spline> splines;

       public void dive(String reason) {
         Gradient gradient = saturateGradient();
         List<Spline> splines = reticulateSplines(**gradient**);
         diveForMoog(**splines, reason**);
       }
       ...
     }
   ```
2. 경계 조건은 캡슐화 하라 ✨

   * 경계 조건은 한 곳에서 설정해두고 사용한다.

   ```kotlin
   if (**level + 1** < tags.length) {
       parts = new Parse(body, tags, **level + 1**, offset + endTag;
       body = null;
   }
   ```

   * 변수로 캡슐화

   ```kotlin
   int nextLevel = level + 1;
   if (nextLevel < tags.length)
   {
       parts = new Parse(body, tags, nextLevel, offset + endTag;
       body = null;
   }
   ```

***

### Java

1. 긴 import 목록을 피해라

   * 패키지에서 클래스 둘 이상을 사용한다면 **와일드 카드**를 사용한다.

   ```kotlin
   import package.*;
   ```
2. 상수는 상속하지 않는다.

   * TENTHS\_PER\_WEEK, OVERTIME\_RATE 상수의 출처는?

   ```kotlin
   public class HourlyEmployee extends Employee {
       private int tenthsWorked;
       private double hourlyRate;

       public Money calculatePay() {
         int straightTime = Math.min(tenthsWorked, **TENTHS_PER_WEEK**);
         int overTime = tenthsWorked - straightTime;
         return new Money(
           hourlyRate * (tenthsWorked + **OVERTIME_RATE** * overTime)
         );
       }
       ...
     }
   ```

   * 부모 클래스?

   ```kotlin
   public abstract class Employee implements PayrollConstants {
       public abstract boolean isPayday();
       public abstract Money calculatePay();
       public abstract void deliverPay(Money pay);
   }
   ```

   * 인터페이스?
     * 계층의 가장 상위에 선언되어 있다.

   ```kotlin
   public interface PayrollConstants {
      public static final int **TENTHS_PER_WEEK = 400;** 
      public static final double **OVERTIME_RATE = 1.5;** 
   }
   ```

   * static import 사용 ✨

   ```java
   import static PayrollConstants.*;

   public class HourlyEmployee extends Employee {
       private int tenthsWorked;
       private double hourlyRate;

       public Money calculatePay() {
         int straightTime = Math.min(tenthsWorked, TENTHS_PER_WEEK);
         int overTime = tenthsWorked - straightTime;
         return new Money(
           hourlyRate * (tenthsWorked + OVERTIME_RATE * overTime)
         );
       }
       ...
   }
   ```
3. 상수 vs Enum

   * 주석으로 상수의 의미를 전달,,?

   ```java
   public class EnumExample {
   		public static void main(String[] args) {
   			/*
   			* 월요일 == 1
   			* 화요일 == 2
   			* 수요일 == 3
   			* 목요일 == 4
   			* 금요일 == 5
   			* 토요일 == 6
   			* 일요일 == 7
   			*/
   			int day = 1;
   			
   			switch (day) {
   					**case 1:
   						System.out.println("월요일");**
   						break;
   					case 2:
   						// ...
   			}
   		}
   }ㅇ
   ```

* 상수들이 많아진다면? 집합을 만들고 싶다.

```java
public class EnumExample {
		private val static int MONDAY = 1;
		private val static int TUESDAY = 2;
		private val static int WENDESDAY = 3;
		private val static int THURSDAY = 4;
		private val static int FRIDAY = 5;
		private val static int SATURDAY = 6;
		private val static int SUNDAY = 7;

		public static void main(String[] args) {

			int day = MONDAY;
			
			switch (day) {
					**case MONDAY:
						System.out.println("월요일");**
						break;
					case TUESDAY:
						// ...
			}
		}
}
```

* Enum을 활용, 서로 연관된 상수들의 집합

```java
**enum Day {
		MONDAY,
		TUESDAY,
		WENDESDAY,
		THURSDAY,
		FRIDAY,
		SATURDAY,
		SUNDAY
}**

public class EnumExample {
		public static void main(String[] args) {

			int day = Day.MONDAY;
			
			switch (day) {
					**case MONDAY:
						System.out.println("월요일");**
						break;
					case TUESDAY:
						// ...
			}
		}
}
```

* 추가 속성 부여 가능

```java
**enum Day(String korDay) {
		MONDAY("월요일"),
		TUESDAY("화요일"),
		WENDESDAY("수요일"),
		THURSDAY("목요일"),
		FRIDAY("금요일"),
		SATURDAY("토요일"),
		SUNDAY("일요일")
}**

public class EnumExample {
		public static void main(String[] args) {

			int day = Day.MONDAY;
			
			**System.out.println(day.korDay);**
			}
		}
}
```

***

### 이름

1. 서술적인 이름을 사용하라

   * 이렇게는 쓰지마라,,

   ```java
     public int x() {
       int q = 0;
       int z = 0;
       for (int kk = 0; kk < 10; kk++) {
         if (l[z] == 10)
         {
           q += 10 + (l[z + 1] + l[z + 2]);
           z += 1;
         }
         else if (l[z] + l[z + 1] == 10)
         {
           q += 10 + l[z + 2];
           z += 2;
         } else {
           q += l[z] + l[z + 1];
           z +=2;
         }
       }
       return q;
   }
   ```

   * 명명법에 신경쓰기

   ```java
   public int score() {
       int score = 0;
       int frame = 0;
       for (int frameNumber = 0; frameNumber < 10; frameNumber++) {
         if (isStrike(frame)) {
           score += 10 + nextTwoBallsForStrike(frame);
           frame += 1;
         }
         else if (isSpare(frame)) {
           score += 10 + nextBallForSpare(frame);
           frame += 2;
         } else {
           score += twoBallsInFrame(frame);
           frame += 2;
         }
       }
       return score;
   }
   ```
2. 적절한 추상화 수준에서 이름을 선택하라

   * detail한 구현을 드러내는 이름을 피하라

   ```java
   public interface Modem {
       boolean **dial(String phoneNumber**);
       boolean disconnect();
       boolean send(char c);
       char recv();
       String **getConnectedPhoneNumber();**
     }
     
   // Refactor
   public interface Modem {
       boolean **connect(String connectionLocator);**
       boolean disconnect();
       boolean send(char c);
       char recv();
       String **getConnectedLocator();**
   }
   ```
3. 이름과 범위는 비례한다

   * 짧은 범위에는 짧은 이름을 사용해도 괜찮다.

   ```kotlin
   private void rollMany(int n, int pins) {
       for (**int i = 0**; i < n; i++)
         g.roll(pins);
   }
   ```
4. 이름에 부수효과를 설명하라

   * 함수, 변수, 클래스가 하는 모든 일을 기술해야 한다.

   ```kotlin
   public ObjectOutputStream **getOos()** throws IOException {
       if (m_oos == null) {
         m_oos = new ObjectOutputStream(m_socket.getOutputStream());
       }
       return m_oos;
   }

   -> **createOrReturnOos()**
   ```

***

### 테스트

1. 잠재적으로 불안정한 모든 부분을 테스트 해야한다.
2. 사소한 테스트를 건너뛰지 마라
3. `@Ignore` 를 붙여 불분명한 요구사항을 표시하라
4. 경계 조건을 테스트 하라
5. 버그 주변은 철저히 테스트하라

***

* 주소 값 비교

  * Int

  ```java
  val a: Int = 1
  val b: Int = 1

  println(a === b) // true
  ```

  * Int?

  ```java
  val a: Int? = 1
  val b: Int? = 1

  println(a === b) // true
  ```

  * \~128 - 127 사이만 캐싱

  ```java
  val a: Int? = 128
  val b: Int? = 128

  println(a === b) // false
  ```

  * String

  ```java
  val a: String = "1"
  val b: String = "1"

  println(a === b) // true
  ```
* Marker Interface

  ```java
  data class User(val name: String): Serializable
  ```

  * Serializable

  ![Untitled](/files/6Xic5lXsa2XCtX3u2CNV)

  ```java
  import java.io.*;

  public class SerializableTester {

      public void serializableTest() throws IOException, ClassNotFoundException {
          File f = new File("user.txt");
          ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(f));
          objectOutputStream.writeObject(new User("sujin", "sujin@gmail.com"));
      }

      class User {
          private String name;
          private String email;

          public User(String name, String email) {
              this.name = name;
              this.email = email;
          }

  				// ...
      }

      public static void main(String[] args) {
          serializableTest serializableTester = new SerializableTester();
          try {
              **serializableTester.serializableTest();**
          } catch (IOException e) {
              e.printStackTrace();
          } catch (ClassNotFoundException e) {
              e.printStackTrace();
          }

      }
  }
  ```

  * 익셉션!

  ![Untitled](/files/y0olOz6hgsQDbDYqMEoY)

  * Serializable 인터페이스를 구현함으로써 해결

  ```java
     class User implements Serializable {
          private String name;
          private String email;

          public User(String name, String email) {
              this.name = name;
              this.email = email;
          }
     // ...
  ```

  * writeObject()를 살펴보자

  ```java
  private void writeObject0(Object obj, boolean unshared)
          throws IOException
      {
  			... 

              if (obj instanceof String) {
                  writeString((String) obj, unshared);
              } else if (cl.isArray()) {
                  writeArray(obj, desc, unshared);
              } else if (obj instanceof Enum) {
                  writeEnum((Enum<?>) obj, desc, unshared);
              } **else if (obj instanceof Serializable) {**
                  writeOrdinaryObject(obj, desc, unshared);
              } else {
                  if (extendedDebugInfo) {
                      throw new NotSerializableException(
                          cl.getName() + "\n" + debugInfoStack.toString());
                  } else {
                      throw new NotSerializableException(cl.getName());
                  }
              }
          } finally {
              depth--;
              bout.setBlockDataMode(oldMode);
          }
      }
  ```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://kukingclass.gitbook.io/cleancode/clean-code/17.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
