๊นจ๋—ํ•œ ์ฝ”๋“œ์™€ ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ๋Š” ํ™”์‹คํžˆ ์—ฐ๊ด€์„ฑ์ด ์žˆ๋‹ค. ํ•˜์ง€๋งŒ ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ๋กœ ์ธํ•ด ํ”„๋กœ๊ทธ๋žจ ๋…ผ๋ฆฌ๋ฅผ ์ดํ•ดํ•˜๊ธฐ ์–ด๋ ค์ค˜์ง„๋‹ค๋ฉด ๊นจ๋—ํ•œ ์ฝ”๋“œ๊ฐ€ ์•„๋‹ˆ๋‹ค.

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

์—์ „ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด๋Š” ์˜ˆ์™ธ๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š์•˜๋‹ค. ์ด ๋•Œ๋ฌธ์—, ์˜ค๋ฅ˜๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ์‹์€ ์ œํ•œ์ ์ผ ์ˆ˜ ๋ฐ–์— ์—†์—ˆ๋‹ค.

  • error flag๋ฅผ ์„ค์ •ํ•œ๋‹ค.
  • error code๋ฅผ ๋ฆฌํ„ดํ•œ๋‹ค.
  • ํ˜ธ์ถœํ•˜๋Š” ์ธก์—์„œ ์˜ˆ์™ธ์ฒ˜๋ฆฌ๋ฅผ ํ•œ๋‹ค.

์ด๋Ÿฐ ๋ฐฉ์‹์œผ๋กœ๋Š” ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋ฅผ ์žŠ๊ธฐ ์‰ฝ๊ฒŒํ•˜๊ณ  ๋…ผ๋ฆฌ๊ฐ€ ์„ž์ด์–ด ํ—ท๊ฐˆ๋ฆฌ๊ธฐ ์‰ฝ๋‹ค.

// Bad
public class DeviceController {
  ...
    public void sendShutDown() {
        DeviceHandle handle = getHandle(DEV1);
        // ๋””๋ฐ”์ด์Šค ์ƒํƒœ๋ฅผ ์ ๊ฒ€ํ•œ๋‹ค.
        if (handle != DeviceHandle.INVALID) {
            // ๋ ˆ์ฝ”๋“œ ํ•„๋“œ์— ๋””๋ฐ”์ด์Šค ์ƒํƒœ๋ฅผ ์ €์žฅํ•œ๋‹ค.
            retrieveDeviceRecord(handle);
            // ๋””๋ฐ”์ด์Šค ์ƒํƒœ๊ฐ€ ์ผ์‹œ์ •์ง€๊ฐ€ ์•„๋‹ˆ๋ผ๋ฉด ์ข…๋ฃŒํ•œ๋‹ค.
            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());
        ...
    }
    ...
}

์˜ˆ์™ธ๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ์•„์˜ˆ ๋ถ„๋ฆฌํ•˜์—ฌ ๋…ผ๋ฆฌ ์ฝ๊ธฐ๊ฐ€ ์‰ฌ์›Œ์กŒ๋‹ค. ๋””๋ฐ”์ด์Šค๋ฅผ ์ข…๋ฃŒํ•˜๋Š” ์•Œ๊ณ ๋ฆฌ์ฆ˜ (tryToShutDown())๊ณผ ์˜ค๋ฅ˜๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ์•Œ๊ณ ๋ฆฌ์ฆ˜ getHandle()์ด ๋ถ„๋ฆฌ๋˜์—ˆ๋‹ค.

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

try-catch ๋ฌธ์˜ ํŠน์ง•๋ถ€ํ„ฐ ์‚ดํŽด๋ณด์ž. ์ด๋…€์„์€ ์ƒ๋‹นํžˆ ํฅ๋ฏธ๋กœ์šด ๋…€์„์ด๋‹ค. ๊ทธ ์ด์œ ๋Š”, try ๋ธ”๋ก์•ˆ์— ๋“ค์–ด๊ฐ€๋Š” ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜๋ฉด ์–ด๋Š ์‹œ์ ์—๋“  ์‹คํ–‰์„ ์ค‘๋‹จํ•œ ํ›„ catch ๋ธ”๋ก์œผ๋กœ ๋„˜์–ด๊ฐˆ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ์ด๋Ÿฌํ•œ ํŠน์ง•์€ ๊ณง transection๊ณผ ๋น„์Šทํ•œ ์ ์ด๋ผ ํ•  ์ˆ˜ ์žˆ๋‹ค. ์–ด๋–ค try๋ฌธ์ด๋“ ์ง€๊ฐ„์— ์ƒ๊ด€์—†์ด ํ•ด๋‹น ๋ธ”๋ก์•ˆ์—์„œ ์ผ๊ด€๋œ ์ƒํƒœ๋กœ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ์ด ๋ธ”๋ก์ด ์‹คํ–‰๋˜๊ธฐ ์ „๊ณผ ํ›„์— ๋‚˜์˜ฌ ์ˆ˜ ์žˆ๋Š” ๊ฒฐ๊ณผ๊ฐ€ ์ œํ•œ๋œ๋‹ค.

  // 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>();
}

๋จผ์ €, error๋ฅผ ๋ฐœ์ƒํ•˜๋Š”์ง€ ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์œ„ํ•œ ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค์ž. ๊ทธ๋ฆฌ๊ณ , ์ด ํ•จ์ˆ˜์˜ ๊ป๋ฐ๊ธฐ๋ฅผ ๋งŒ๋“ค์–ด์„œ ํ•ด๋‹น ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํŒจํ•˜๋„๋ก ๋งŒ๋“ค์ž.

// 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>();
}

์ด์ œ ์‹คํŒจํ•œ ํ…Œ์ŠคํŠธ๋ฅผ ํ†ต๊ณผ์‹œํ‚ค๊ธฐ ์œ„ํ•ด ๋‚ด๋ถ€ ๊ตฌํ˜„์„ ์‹œ๋„ํ•˜์ž. throw๋ฅผ ๋˜์ง€๊ธฐ ๋•Œ๋ฌธ์— ์ด์ œ ์œ„์˜ test ํ•จ์ˆ˜๋Š” ํ†ต๊ณผํ•œ๋‹ค.

// 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>();
}

๋งˆ์ง€๋ง‰์œผ๋กœ ์—๋Ÿฌ์˜ ๋ฒ”์œ„๋ฅผ ์ค„์ด์ž. FileNotFoundException์œผ๋กœ ๋ฒ”์œ„๋ฅผ ์ค„์˜€๋‹ค. ์ด๋ ‡๊ฒŒ ๊ฐ•์ œ๋กœ ์˜ˆ์™ธ๋ฅผ ์ผ์œผํ‚ค๋Š” ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋ฅผ ์ž‘์„ฑํ•œ ํ›„ ํ…Œ์ŠคํŠธ๋ฅผ ํ†ต๊ณผํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์ฝ”๋“œ๋ฅผ ๊ตฌ์„ฑํ•˜๊ฒŒ ๋˜๋ฉด try ๋ธ”๋ก์˜ transection ๋ฒ”์œ„๋‚ด์—์„œ ๊ตฌํ˜„ํ•˜๊ฒŒ ๋œ๋‹ค. ๊ฒฐ๊ณผ์ ์œผ๋กœ ์ฝ”๋“œ์˜ ๋ณธ์งˆ์„ ๋ฒ—์–ด๋‚˜์ง€ ์•Š๋„๋ก ํ•  ์ˆ˜ ์žˆ๋‹ค.

๋ฏธํ™•์ธ ์—์™ธ๋ฅผ ์‚ฌ์šฉํ•˜๋ผ

ํ•ด๋‹น ๋‹จ์„ ์ดํ•ดํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” Checked Exception๊ณผ Unchecked Exception์— ๋Œ€ํ•œ ์ •์˜๋ถ€ํ„ฐ ์•Œ๊ณ ๊ฐ€์•ผ ํ•œ๋‹ค.

Checked ExceptionUnchecked Exception
์‰ฌ์šด ์ดํ•ด๊ผญ ์ฒ˜๋ฆฌ๋ฅผ ํ•ด์ค˜์•ผ ํ•˜๋Š” ์˜ˆ์™ธ
(๋กœ์ง์ ์œผ๋กœ ์ฒ˜๋ฆฌ๋ฅผ ํ•ด์ค˜์•ผ ํ•จ)
๊ผญ ์ฒ˜๋ฆฌํ•˜์ง€ ์•Š์•„๋„ ๋˜๋Š” ์˜ˆ์™ธ
(๊ฐœ๋ฐœ์ž ๋ถ€์ฃผ์˜)
ํ™•์ธ ์‹œ์ ์ปดํŒŒ์ผ ํƒ€์ž„๋Ÿฐ ํƒ€์ž„
์˜ˆ์™ธ ๋ฐœ์ƒ์‹œ ํŠธ๋žœ์žญ์…˜ ์ฒ˜๋ฆฌRoll-back ORoll-back X
์—์‹œException ์ƒ์† ํ•˜์œ„ ํด๋ž˜์Šค ์ค‘ Runtime Exception์„ ์ œ์™ธํ•œ ๋…€์„๋“ค
- IOException
- SQLException
RuntimeException
- NullPointerException
- IndexOutOfBoundException
- SystemException

์˜ˆ์‹œ๋ฅผ ๋ณด๋ฉด ๊ฐ„๋‹จํžˆ ์ดํ•ดํ•  ์ˆ˜ ์žˆ๋‹ค. ๋ช…ํ™•ํ•˜๊ฒŒ ์—๋Ÿฌ๋ฅผ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ๋ฅผ Checked Exception, ๊ฐœ๋ฐœ์ž ๋ถ€์ฃผ์˜, system์ ์œผ๋กœ ๋ฐœ์ƒํ•˜๋Š” ์˜ˆ์™ธ๋“ฑ์„ Unchecked Exception์ด๋ผ ํ•œ๋‹ค.

์ด๋Ÿฌํ•œ ๋งฅ๋ฝ์—์„œ ์ €์ž๋Š” ์–ด๋– ํ•œ ์ฃผ์žฅ์„ ๊ฐ€์ง€๊ณ  ์žˆ์„๊นŒ? ์ด์ „ java์—์„œ ์ฒ˜์Œ์œผ๋กœ Exception์ด ๋‚˜์™”์„ ๋•Œ๋Š” ๋ฉ‹์ง€๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ๋‹ค. ์ด ๋•Œ๋ฌธ์—, ๋ฉ”์„œ๋“œ๊ฐ€ ๋ฐ˜ํ™˜ํ•˜๋Š” ์˜ˆ์™ธ๋ฅผ ๋ชจ๋‘ ์—ด๊ฑฐํ•˜๋Š” ๋ฐฉ์‹์„ ์‚ฌ์šฉํ–ˆ๋‹ค. ํ•˜์ง€๋งŒ ์•ˆ์ •์ ์ธ ์†Œํ”„ํŠธ์›จ์–ด๋ฅผ ์ œ์ž‘ํ•˜๋Š” ์š”์†Œ๋กœ Checked Exception์ด ๋ฐ˜๋“œ์‹œ ํ•„์š”ํ•˜์ง€๋Š” ์•Š๋‹ค๋Š” ์‚ฌ์‹ค์ด ๋ถ„๋ช…ํ•ด์กŒ๋‹ค. ์˜คํžˆ๋ ค ์šฐ๋ฆฌ๊ฐ€ ์ƒ๊ฐํ•ด์•ผ ํ•˜๋Š” ๊ฒƒ์€ Checked exception์„ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ์น˜๋ฅด๋Š” ๋น„์šฉ์„ ์ƒ๊ฐํ•ด๋ณด์•„์•ผ ํ•œ๋‹ค.

์–ด๋–ค ๋น„์šฉ์ด ๋“œ๋Š”์ง€ ์ƒ๊ฐํ•ด๋ณด์ž.

null

  1. ํŠน์ • ๋ฉ”์†Œ๋“œ์—์„œ checked exception์„ throwํ•˜๊ณ 
  2. 3๋‹จ๊ณ„(๋ฉ”์†Œ๋“œ ์ฝœ) ์œ„์˜ ๋ฉ”์†Œ๋“œ์—์„œ ๊ทธ exception์„ catchํ•œ๋‹ค๋ฉด
  3. ๋ชจ๋“  ์ค‘๊ฐ„๋‹จ๊ณ„ ๋ฉ”์†Œ๋“œ์— exception์„ ์ •์˜ํ•ด์•ผ ํ•œ๋‹ค.(์ž๋ฐ”์˜ ๊ฒฝ์šฐ ๋ฉ”์†Œ๋“œ ์„ ์–ธ์— throws ๊ตฌ๋ฌธ์„ ๋ถ™์ด๋Š” ๋“ฑ)

์ด๋Š” ์—ฐ์‡„์ ์ธ ์ˆ˜์ •์„ ํ•ด์•ผํ•˜๊ธฐ ๋•Œ๋ฌธ์— OCP(Open Closed Principle)์„ ์œ„๋ฐ˜ํ•œ๋‹ค. ์ƒ์œ„ ๋ ˆ๋ฒจ ๋ฉ”์†Œ๋“œ์—์„œ ํ•˜์œ„ ๋ ˆ๋ฒจ ๋ฉ”์†Œ๋“œ์˜ ๋””ํ…Œ์ผ์— ๋Œ€ํ•ด ์•Œ์•„์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์บก์Šํ™”๋„ ๊นจ์ง„๋‹ค.

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

ํ˜ธ์ถœ ์Šคํƒ์ด ์ด๋Ÿฌํ•œ ๋งฅ๋ฝ์„ ์ œ๊ณตํ•˜๊ธฐ๋Š” ํ•˜์ง€๋งŒ, ์ด ์ •๋„๋ฅผ ๋ถˆ์ถฉ๋ถ„ํ•˜๋‹ค. ์—์™ธ๊ฐ€ ๋ฐœ์ƒํ•œ ์ด์œ ์™€ ์ข€ ๋” ๊ตฌ์ฒด์ ์ธ Exception Type์„ ํ†ตํ•ด ์ดํ•ดํ•˜๊ธฐ ํŽธํ•˜๋„๋ก ํ•ด๋ผ.

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

Exception class๋ฅผ ๋งŒ๋“ค ๋•Œ ๊ฐ€์žฅ ์ค‘์š”ํ•œ ๊ฒƒ์€ โ€œ์–ด๋–ค ๋ฐฉ์‹์œผ๋กœ ์˜ˆ์™ธ๋ฅผ ์ฒ˜๋ฆฌํ• ๊นŒโ€์ด๋‹ค. ์ด ๊ณผ์ •์—์„œ Third Party library๋ฅผ ์‚ฌ์šฉํ•˜๊ณ , ์—ฌ๊ธฐ์„œ ๋˜์ง€๋Š” ์—๋Ÿฌ๋ฅผ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•œ๋‹ค๋ฉด, wrapping ํ•˜์—ฌ ๊ด€๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.

  1. ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๊ต์ฒด ๋“ฑ์˜ ๋ณ€๊ฒฝ์— ๋Œ€์‘ํ•˜๊ธฐ ์‰ฝ๋‹ค.
  2. ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์“ฐ๋Š” ๊ณณ์„ ํ…Œ์ŠคํŠธํ•˜๋Š” ๊ฒฝ์šฐ, ํ•ด๋‹น ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๊ฐ€์งœ๋กœ ๋งŒ๋“ค์–ด ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์‰ฌ์›Œ์ง„๋‹ค.
  3. ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ API ๋””์ž์ธ์— ๊ด€๊ณ„์—†์ด ๋‚ด ํ”„๋กœ๊ทธ๋žจ์— ๋งž๋„๋ก ์ •์ œํ•˜์—ฌ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
// Bad
// catch๋ฌธ์˜ ๋‚ด์šฉ์ด ๊ฑฐ์˜ ๊ฐ™๋‹ค. ์ค‘๋ณต์ด ๋งŽ๋‹ค.
 
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
// ACME ํด๋ž˜์Šค๋ฅผ LocalPort ํด๋ž˜์Šค๋กœ ๋ž˜ํ•‘ํ•ด new ACMEPort().open() ๋ฉ”์†Œ๋“œ์—์„œ ๋˜์งˆ ์ˆ˜ ์žˆ๋Š” exception๋“ค์„ ๊ฐ„๋žตํ™”
  
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);
        }
    }
    ...
}

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

์ง€๊ธˆ๊นŒ์ง€ ๋ณธ ๋ฐฉ์‹์œผ๋กœ ์˜ˆ์™ธ์ฒ˜๋ฆฌ๋ฅผ ํ•˜๊ฒŒ๋˜๋ฉด ๊น”๋”ํ•œ ์ฝ”๋“œ๋ฅผ ๋ณด์žฅํ•  ์ˆ˜ ์žˆ๋‹ค. ํ•˜์ง€๋งŒ catch๋ฌธ์—์„œ ์˜ˆ์™ธ์ ์ธ ์ƒํ™ฉ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒฝ์šฐ ์˜คํžˆ๋ ค ๋”๋Ÿฌ์›Œ์ง„๋‹ค. ์ •๋ง ์˜ˆ์™ธ์ธ์ง€ ๊ธฐ๋ณธ๋กœ์ง์— ํŽธ์ž…ํ•ด์•ผ ํ•˜๋Š”์ง€ ํŒ๋‹จํ•˜๋Š” ์Šต๊ด€์„ ๊ฐ€์ ธ์•ผ ํ•œ๋‹ค.

// 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() {
    // return the per diem default
    }
}
 
// ์ดํ•ด๋ฅผ ๋•๊ธฐ ์œ„ํ•ด ์ง์ ‘ ์ถ”๊ฐ€ํ•œ ํด๋ž˜์Šค
public class ExpenseReportDAO {
    ...
    public MealExpenses getMeals(int employeeId) {
    MealExpenses expenses;
    try {
        expenses = expenseReportDAO.getMeals(employee.getID());
    } catch(MealExpensesNotFound e) {
        expenses = new PerDiemMealExpenses();
    }
    
    return expenses;
    }
    ...
}

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

null์„ ๋ฆฌํ„ดํ•˜๊ณ  ์‹ถ์€ ์ƒ๊ฐ์ด ๋“ค๋ฉด ์œ„์—์„œ ์„ค๋ช…ํ•œ special case object๋ฅผ ๋ฆฌํ„ดํ•˜์ž. third party ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์—์„œ 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);
        }
    }
    }
}
 

์—ฌ๊ธฐ์„œ null ์ฒดํฌ๋ฅผ ๋ชปํ•œ ๋ถ€๋ถ„์—์„œ ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธด๋‹ค๋ฉด ์ฐพ๊ธฐ๊ฐ€ ๋„ˆ๋ฌด ํž˜๋“ค๋‹ค. ๋‹น์žฅ ์œ„์—๋งŒ ๋ณด์•„๋„ peristentStore๊ฐ€ null์ธ ๊ฒฝ์šฐ์— ๋Œ€ํ•œ ์˜ˆ์™ธ์ฒ˜๋ฆฌ๊ฐ€ ์•ˆ๋œ๋‹ค!! ๋งŒ์•ฝ์— null์ธ ์ƒํƒœ๋กœ ์•„๋ž˜ ๋กœ์ง์„ ๋”ฐ๋ผ๊ฐ„๋‹ค๋ฉด ์–ด๋–ป๊ฒŒ ๋ ๊นŒ? NullPointerException๊ฐ€ ๋ฐœ์ƒํ•  ๊ฑฐ๊ณ  ์ด๋ฅผ ์ฒ˜๋ฆฌํ•ด์ค˜์•ผ ํ•œ๋‹ค.์–ด๋””์„œ ํ•ด์ค„๊นŒ? ์ˆ˜์‹ญ๋‹จ๊ณ„ ์œ„์˜ ๋ฉ”์†Œ๋“œ์—์„œ ์ฒ˜๋ฆฌํ•ด์ค˜์•ผ ํ•˜๋‚˜? ์ด ๋ฉ”์†Œ๋“œ์˜ ๋ฌธ์ œ์ ์€ null ์ฒดํฌ๊ฐ€ ๋ถ€์กฑํ•œ๊ฒŒ ์•„๋‹ˆ๋ผ null์ฒดํฌ๊ฐ€ ๋„ˆ๋ฌด ๋งŽ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.

// 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์— ๋Œ€ํ•ด ์ ์ ˆํ•œ ๋ฐฉ๋ฒ•์„ ์ œ๊ณตํ•˜์ง€ ๋ชปํ•œ๋‹ค. ์˜ˆ์‹œ๋ฅผ ๋ณด๋ฉด ์ดํ•ด๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค.

// Bad
// calculator.xProjection(null, new Point(12, 13));
// ์œ„์™€ ๊ฐ™์ด ๋ถ€๋ฅผ ๊ฒฝ์šฐ NullPointerException ๋ฐœ์ƒ
public class MetricsCalculator {
  public double xProjection(Point p1, Point p2) {
    return (p2.x โ€“ p1.x) * 1.5;
  }
  ...
}
 
// Bad
// NullPointerException์€ ์•ˆ๋‚˜์ง€๋งŒ ์œ—๋‹จ๊ณ„์—์„œ InvalidArgumentException์ด ๋ฐœ์ƒํ•  ๊ฒฝ์šฐ ์ฒ˜๋ฆฌํ•ด์ค˜์•ผ ํ•จ.
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;
  }
}
 
// Bad
// ์ข‹์€ ๋ช…์„ธ์ด์ง€๋งŒ ์ฒซ๋ฒˆ์งธ ์˜ˆ์‹œ์™€ ๊ฐ™์ด NullPointerException ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜์ง€ ๋ชปํ•œ๋‹ค.
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;
  }
}

๊ฒฐ๋ก 

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

  • Exception์„ ํ™œ์šฉํ•˜์ž.
  • Try-Catch๋ฌธ์„ ๋จผ์ € ์ž‘์„ฑํ•˜๊ณ  ๋กœ์ง์„ ๋„ฃ์ž.
  • ์ง€๋‚˜์นœ unchecked exception์€ ์ข‹์ง€ ์•Š๋‹ค.
  • Exception์„ ์‚ฌ์šฉํ•  ์‹œ, ์ด์œ ์™€ Type์„ ๊ตฌ์ฒด์ ์œผ๋กœ ์ ์–ด๋ผ.
  • ์‚ฌ์šฉ์— ๋งž๊ฒŒ(third party library) Exception์„ wrappingํ•˜๋ผ.
  • Exception์„ ๋‚จ๋ฐœํ•˜๊ธฐ๋ณด๋‹ค ์ •์ƒ์ ์ธ ์ƒํ™ฉ์„ ์ •์˜ํ•˜์—ฌ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด ๊ทธ๋ ‡๊ฒŒ ํ•˜๋ผ.
  • Null์€ ๋ฆฌํ„ดํ•˜์ง€๋„, ๋„˜๊ธฐ์ง€๋„ ๋งˆ๋ผ.

Reference