์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ

์˜ค๋ฅ˜ ์ฝ”๋“œ๋ณด๋‹ค ์˜ˆ์™ธ๋ฅผ ์‚ฌ์šฉํ•˜๋ผ

// Bad
public class DeviceController {
...
public void sendShutDown() {
  DeviceHandle handle = getHandle(DEV1);
  // Check the state of the device
  if (handle != DeviceHandle.INVALID) {
    // Save the device status to the record field
    retrieveDeviceRecord(handle);
    // If not suspended, shut down
    if (record.getStatus() != DEVICE_SUSPENDED) {
      pauseDevice(handle);
      clearDeviceWorkQueue(handle);
      closeDevice(handle);
    } else {
      logger.log("Device suspended. Unable to shut down");
    }
  } else {
    logger.log("Invalid handle for: " + DEV1.toString());
  }
}
...
}
  • ์œ„์˜ ์ฝ”๋“œ๋Š” ํ˜ธ์ถœ์ž์˜ ์ฝ”๋“œ๊ฐ€ ๋ณต์žกํ•ด์ง„๋‹ค. ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•œ ์ฆ‰์‹œ ์˜ค๋ฅ˜๋ฅผ ํ™•์ธํ•ด์•ผ ํ•œ๋‹ค.
  • ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์˜ˆ์™ธ๋ฅผ ๋˜์ง€๋Š” ํ‡์ด ๋‚ซ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ํ˜ธ์ถœ์ž ์ฝ”๋“œ๊ฐ€ ๋” ๊น”๋”ํ•ด์ง„๋‹ค.
// Good
public class DeviceController {
...
public void sendShutDown() {
  try {
    tryToShutDown();
  } catch (DeviceShutDownError e) {
    logger.log(e);
  }
}
 
private void tryToShutDown() throws DeviceShutDownError {
  DeviceHandle handle = getHandle(DEV1);
  DeviceRecord record = retrieveDeviceRecord(handle);
  pauseDevice(handle);
  clearDeviceWorkQueue(handle);
  closeDevice(handle);
}
 
private DeviceHandle getHandle(DeviceID id) {
  ...
  throw new DeviceShutDownError("Invalid handle for: " + id.toString());
  ...
}
...
}

Try-Catch-Finally ๋ฌธ๋ถ€ํ„ฐ ์ž‘์„ฑํ•˜๋ผ

  • ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•  ์ฝ”๋“œ๋ฅผ ์งค ๋•Œ๋Š” try-catch-finally ๋ฌธ์œผ๋กœ ์‹œ์ž‘ํ•˜๋Š” ํŽธ์ด ๋‚ซ๋‹ค.
  • try๋ฌธ์€ transaction์ฒ˜๋Ÿผ ๋™์ž‘ํ•˜๋Š” ์‹คํ–‰์ฝ”๋“œ๋กœ, catch๋ฌธ์€ try๋ฌธ์— ๊ด€๊ณ„์—†์ด ํ”„๋กœ๊ทธ๋žจ์„ ์ผ๊ด€์ ์ธ ์ƒํƒœ๋กœ ์œ ์ง€ํ•˜๋„๋ก ํ•œ๋‹ค.

Step 1

// Step 1: StorageException์„ ๋˜์ง€์ง€ ์•Š์œผ๋ฏ€๋กœ ์ด ํ…Œ์ŠคํŠธ๋Š” ์‹คํŒจํ•œ๋‹ค.
 
@Test(expected = StorageException.class)
public void retrieveSectionShouldThrowOnInvalidFileName() {
  sectionStore.retrieveSection("invalid - file");
}
 
public List<RecordedGrip> retrieveSection(String sectionName) {
  // dummy return until we have a real implementation
  return new ArrayList<RecordedGrip>();
}

Step 2

// Step 2: ์ด์ œ ํ…Œ์ŠคํŠธ๋Š” ํ†ต๊ณผํ•œ๋‹ค.
public List<RecordedGrip> retrieveSection(String sectionName){
  try{
    FileInputStream stream = new FileInputStream(sectionName);
  } catch (Exception e) {
    throw new StorageException("retrieval error", e);
  }
  return new ArrayList<RecordedGrip>();
}

Step 3

// Step 3: Exception์˜ ๋ฒ”์œ„๋ฅผ FileNotFoundException์œผ๋กœ ์ค„์—ฌ ์ •ํ™•ํžˆ ์–ด๋–ค Exception์ด ๋ฐœ์ƒํ•œ์ง€ ์ฒดํฌํ•˜์ž.
public List<RecordedGrip> retrieveSection(String sectionName) {
  try {
    FileInputStream stream = new FileInputStream(sectionName);
    stream.close();
  } catch (FileNotFoundException e) {
    throw new StorageException("retrieval error", e);
  }
  return new ArrayList<RecordedGrip>();
}

๋ฏธํ™•์ธ(unchecked) ์˜ˆ์™ธ๋ฅผ ์‚ฌ์šฉํ•˜๋ผ

  • ์˜ˆ์ „์—๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ์„ ์–ธํ•  ๋•Œ๋Š” ๋ฉ”์„œ๋“œ๊ฐ€ ๋ฐ˜ํ™˜ํ•  ์˜ˆ์™ธ๋ฅผ ๋ชจ๋‘ ์—ด๊ฑฐํ–ˆ๋‹ค.
  • ์˜ˆ์™ธ์ฒ˜๋ฆฌ์— ๋“œ๋Š” ๋น„์šฉ ๋Œ€๋น„ ์ด๋“์„ ์ƒ๊ฐํ•ด๋ด์•ผ ํ•œ๋‹ค.
  • ํ™•์ธ๋œ ์˜ˆ์™ธ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด OCP๋ฅผ ์œ„๋ฐ˜ํ•œ๋‹ค.
    • ํ™•์ธ๋œ ์˜ˆ์™ธ๋ฅผ ๋˜์กŒ๋Š”๋ฐ catch ๋ธ”๋ก์ด ์„ธ ๋‹จ๊ณ„ ์œ„์— ์žˆ๋‹ค๋ฉด ๊ทธ ์‚ฌ์ด ๋ฉ”์„œ๋“œ ๋ชจ๋‘๊ฐ€ ์„ ์–ธ๋ถ€์— ํ•ด๋‹น ์˜ˆ์™ธ๋ฅผ ์ •์˜ํ•ด์•ผ ํ•œ๋‹ค.
    • ๋‹ค์‹œ ๋นŒ๋“œํ•œ ๋‹ค์Œ ๋ฐฐํฌํ•ด์•ผํ•œ๋‹ค.
    • ์ƒ์œ„ ๋ ˆ๋ฒจ ๋ฉ”์†Œ๋“œ์—์„œ ํ•˜์œ„ ๋ ˆ๋ฒจ ๋ฉ”์†Œ๋“œ์˜ ๋””ํ…Œ์ผ์— ๋Œ€ํ•ด ์•Œ์•„์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์บก์Šํ™” ๋˜ํ•œ ๊นจ์ง„๋‹ค.

์˜ˆ์™ธ์— ์˜๋ฏธ๋ฅผ ์ œ๊ณตํ•˜๋ผ

  • ์˜ˆ์™ธ๋ฅผ ๋˜์งˆ ๋•Œ๋Š” ์ „ํ›„ ์ƒํ™ฉ์„ ์ถฉ๋ถ„ํžˆ ๋ง๋ถ™์ธ๋‹ค.
  • ์‹คํŒจํ•œ ์—ฐ์‚ฐ ์ด๋ฆ„๊ณผ ์‹คํŒจ ์œ ํ˜•๋„ ์–ธ๊ธ‰ํ•œ๋‹ค.

ํ˜ธ์ถœ์ž๋ฅผ ๊ณ ๋ คํ•ด ์˜ˆ์™ธ ํด๋ž˜์Šค๋ฅผ ์ •์˜ํ•ด๋ผ

  • Exception class๋ฅผ ๋งŒ๋“œ๋Š” ๋ฐ์—์„œ ๊ฐ€์žฅ ์ค‘์š”ํ•œ ๊ฒƒ์€ โ€œ์–ด๋–ค ๋ฐฉ์‹์œผ๋กœ ์˜ˆ์™ธ๋ฅผ ์žก์„๊นŒโ€์ด๋‹ค.
  • ์จ๋“œํŒŒํ‹ฐ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ ๊ทธ๊ฒƒ๋“ค์„ wrappingํ•จ์œผ๋กœ์จ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๊ต์ฒด ๋“ฑ์˜ ๋ณ€๊ฒฝ์ด ์žˆ๋Š” ๊ฒฝ์šฐ ๋Œ€์‘ํ•˜๊ธฐ ์‰ฌ์›Œ์ง„๋‹ค.
  • ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์“ฐ๋Š” ๊ณณ์„ ํ…Œ์ŠคํŠธํ•  ๊ฒฝ์šฐ ํ•ด๋‹น ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๊ฐ€์งœ๋กœ ๋งŒ๋“ค๊ฑฐ๋‚˜ ํ•จ์œผ๋กœ์จ ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์‰ฌ์›Œ์ง„๋‹ค.
  • ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ api ๋””์ž์ธ์— ์ข…์†์ ์ด์ง€ ์•Š๊ณ  ๋‚ด ์ž…๋ง›์— ๋งž๋Š” ๋””์ž์ธ์„ ์ ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ๋ณดํ†ต ํŠน์ • ๋ถ€๋ถ„์˜ ์ฝ”๋“œ์—๋Š” exception ํ•˜๋‚˜๋กœ ์ถฉ๋ถ„ํžˆ ์˜ˆ์™ธ์ฒ˜๋ฆฌ ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ํ•œ exception๋งŒ ์žก๊ณ  ๋‚˜๋จธ์ง€ ํ•˜๋‚˜๋Š” ๋‹ค์‹œ throwํ•˜๋Š” ๊ฒฝ์šฐ ๋“ฑ ์ •๋ง ํ•„์š”ํ•œ ๊ฒฝ์šฐ์—๋งŒ ๋‹ค๋ฅธ exception ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค์–ด ์‚ฌ์šฉํ•˜์ž.
// Bad
// ์™ธ๋ถ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ๋˜์งˆ ์˜ˆ์™ธ๋ฅผ ๋ชจ๋‘ ์žก์•„ ๋‚ธ๋‹ค. 
// ๊ฐ™์€ ์—๋Ÿฌ ์žก์•„ ๋‚ด๋Š” ์ฝ”๋“œ๊ฐ€ ๋งŽ๋‹ค. 
// ๋ณ€๊ฒฝํ•˜๊ธฐ ์–ด๋ ต๋‹ค.
 
ACMEPort port = new ACMEPort(12);
try {
  port.open();
} catch (DeviceResponseException e) {
  reportPortError(e);
  logger.log("Device response exception", e);
} catch (ATM1212UnlockedException e) {
  reportPortError(e);
  logger.log("Unlock exception", e);
} catch (GMXError e) {
  reportPortError(e);
  logger.log("Device response exception");
} finally {
  ...
}
// Good
// ํ˜ธ์ถœํ•˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ API๋ฅผ ๊ฐ์‹ธ๋ฉด์„œ ์˜ˆ์™ธ ์œ ํ˜• ํ•˜๋‚˜๋ฅผ ๋ฐ˜ํ™˜.
// Wrapperํด๋ž˜์Šค ๋•๋ถ„์— ์˜์กด์„ฑ์ด ํฌ๊ฒŒ ๊ฐ์†Œ.
 
LocalPort port = new LocalPort(12);
try {
  port.open();
} catch (PortDeviceFailure e) {
  reportError(e);
  logger.log(e.getMessage(), e);
} finally {
  ...
}
 
public class LocalPort {
  private ACMEPort innerPort;
  public LocalPort(int portNumber) {
    innerPort = new ACMEPort(portNumber);
  }
 
  public void open() {
    try {
      innerPort.open();
    } catch (DeviceResponseException e) {
      throw new PortDeviceFailure(e);
    } catch (ATM1212UnlockedException e) {
      throw new PortDeviceFailure(e);
    } catch (GMXError e) {
      throw new PortDeviceFailure(e);
    }
  }
  ...
}

์ •์ƒ ํ๋ฆ„์„ ์ •์˜ํ•˜๋ผ.

// Bad
// ์‹๋น„๋น„์šฉ ์กฐํšŒ๋ฅผ ์‹คํŒจํ•˜๋ฉด ์ผ์ผ ๊ธฐ๋ณธ ์‹๋น„๋ฅผ ์ด๊ณ„์— ๋”ํ•œ๋‹ค.
// ํŠน์ˆ˜ ์ƒํ™ฉ์„ ์ฒ˜๋ฆฌํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค๋ฉด ๋” ์ฝ”๋“œ๋Š” ๊ฐ„๊ฒฐํ•ด์ง„๋‹ค.
try { 
  MealExpenses expenses = expenseReportDAO.getMeals(employee.getID());
  m_total += expenses.getTotal(); 
} catch(MealExpensesNotFound e) {
   m_total += getMealPerDiem(); 
}
// Good
// caller logic.
MealExpenses expenses = expenseReportDAO.getMeals(employee.getID());
m_total += expenses.getTotal();
 
public class PerDiemMealExpenses implements MealExpenses {
  public int getTotal() {
    // ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ์ผ์ผ ๊ธฐ๋ณธ ์‹๋น„๋ฅผ ๋ฐ˜ํ™˜
  }
}

์œ„ ๊ฒฝ์šฐ๋ฅผ ํŠน์ˆ˜ ์‚ฌ๋ก€ ํŒจํ„ด(Special Case Pattern) ๋ถ€๋ฅธ๋‹ค. ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค๊ฑฐ๋‚˜ ๊ฐ์ฒด๋ฅผ ์กฐ์ž‘ํ•ด ํŠน์ˆ˜ ์‚ฌ๋ก€๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ์‹.

null๋ฅผ ๋ฐ˜ํ™˜ํ•˜์ง€ ๋งˆ๋ผ.

  • null๊ฐ’ ์ฒ˜๋ฆฌ๋ฅผ ํ˜ธ์ถœ์ž์—๊ฒŒ ๋ฌธ์ œ๋ฅผ ๋– ๋„˜๊ธฐ๋Š” ํ–‰์œ„์ด๋‹ค. ์ข‹์ง€ ๋ชปํ•œ ์Šต๊ด€.
  • null์„ ๋ฆฌํ„ดํ•˜๊ณ  ์‹ถ์€ ์ƒ๊ฐ์ด ๋“ค๋ฉด ์œ„์˜ Special Case object๋ฅผ ๋ฆฌํ„ดํ•˜๋ผ. ex) Collections.emptyList()
  • ์จ๋“œํŒŒํ‹ฐ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์—์„œ null์„ ๋ฆฌํ„ดํ•  ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ๋Š” ๋ฉ”์„œ๋“œ๊ฐ€ ์žˆ๋‹ค๋ฉด Exception์„ ๋˜์ง€๊ฑฐ๋‚˜ Special Case object๋ฅผ ๋ฆฌํ„ดํ•˜๋Š” ๋งค์„œ๋“œ๋กœ ๋ž˜ํ•‘ํ•˜๋ผ.
// BAD!!!!
public void registerItem(Item item) { 
  if (item != null) {
    ItemRegistry registry = peristentStore.getItemRegistry();
    if (registry != null) {
      Item existing = registry.getItem(item.getID());
      if (existing.getBillingPeriod().hasRetailOwner()) {
        existing.register(item);
      }
    }
  }
}
  // Bad
  List<Employee> employees = getEmployees();
  if (employees != null) {
    for(Employee e : employees) {
      totalPay += e.getPay();
    }
  }
  // Good
  // ๋” ์ฝ๊ธฐ ์‰ฌ์šด ์ฝ”๋“œ.
  List<Employee> employees = getEmployees();
  for(Employee e : employees) {
    totalPay += e.getPay();
  }
 
  public List<Employee> getEmployees() {
    if( .. there are no employees .. )
      return Collections.emptyList();
    }
  }

null์„ ์ „๋‹ฌํ•˜์ง€ ๋งˆ๋ผ.

  • null์„ ๋ฆฌํ„ดํ•˜๋Š” ๊ฒƒ๋„ ๋‚˜์˜์ง€๋งŒ null์„ ๋ฉ”์„œ๋“œ๋กœ ๋„˜๊ธฐ๋Š” ๊ฒƒ์€ ๋” ๋‚˜์˜๋‹ค.
  • null์„ ๋ฉ”์„œ๋“œ์˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋„ฃ์–ด์•ผ ํ•˜๋Š” API๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์•„๋‹ˆ๋ฉด null์„ ๋ฉ”์„œ๋“œ๋กœ ๋„˜๊ธฐ์ง€ ๋งˆ๋ผ.
  • ์ผ๋ฐ˜์ ์œผ๋กœ ๋Œ€๋‹ค์ˆ˜์˜ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด๋“ค์€ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋“ค์–ด์˜จ null์— ๋Œ€ํ•ด ์ ์ ˆํ•œ ๋ฐฉ๋ฒ•์„ ์ œ๊ณตํ•˜์ง€ ๋ชปํ•œ๋‹ค.
  • ๊ฐ€์žฅ ์ด์„ฑ์ ์ธ ํ•ด๋ฒ•์€ null์„ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋ฐ›์ง€ ๋ชปํ•˜๊ฒŒ ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.
// Bad
// Parameter์— null๊ฐ’์ด ๋“ค์–ด์˜ค๋ฉด NullPointerException ๋ฐœ์ƒ.
public class MetricsCalculator { 
  public double xProjection(Point p1, Point p2) { 
    return (p2.x โ€“ p1.x) * 1.5; 
  } 
}
// Better
public class MetricsCalculator { 
  public double xProjection(Point p1, Point p2) { 
    if(p1 == null || p2 == null){
      throw InvalidArgumentException("Invalid argument for MetricsCalculator.xProjection"); 
    } 
    return (p2.x โ€“ p1.x) * 1.5; 
  } 
}
// Good
public class MetricsCalculator { 
  public double xProjection(Point p1, Point p2) { 
    assert p1 != null : "p1 should not be null";
    assert p2 != null : "p2 should not be null";
    return (p2.x โ€“ p1.x) * 1.5; 
  } 
}

๊ฒฐ๋ก 

  • ๊นจ๋—ํ•œ ์ฝ”๋“œ๋Š” ์ฝ๊ธฐ๋„ ์ข‹์•„์•ผ ํ•˜์ง€๋งŒ ์•ˆ์ •์„ฑ๋„ ๋†’์•„์•ผ ํ•œ๋‹ค. ์ด ๋‘๊ฐ€์ง€์˜ ๋ชฉํ‘œ๋Š” ๋Œ€๋ฆฝ๋˜๋Š” ๋ชฉํ‘œ๊ฐ€ ์•„๋‹ˆ๋‹ค.