[Clean Coding] 7. Error Handling

๋ผ์„ยท2024๋…„ 12์›” 9์ผ

Clean Coding

๋ชฉ๋ก ๋ณด๊ธฐ
7/10

๐Ÿ”ป ์ฃผ์š” ๋‚ด์šฉ : Wrapping Class, special case pattern

๐Ÿ–‹๏ธ Error Handling

  • Error : ๊ฒฐํ•จ, ์‹คํŒจ ํ˜น์€ ํ”„๋กœ๊ทธ๋žจ์˜ ์ด์ƒ์œผ๋กœ ์ธํ•ด ์˜ˆ์ƒ์น˜ ๋ชปํ•˜๊ฑฐ๋‚˜ ํ‹€๋ฆฐ ๊ฒฐ๊ณผ๊ฐ€ ์ƒ์„ฑ๋˜๊ฑฐ๋‚˜ ์˜๋„ํ•˜์ง€ ์•Š์€ ๋ฐฉ์‹์œผ๋กœ ์ž‘๋™ํ•˜๊ฒŒ ๋˜๋Š” ๊ฒƒ
  • Error Handling : ์˜ˆ์ƒ์น˜๋ชปํ•œ ์—๋Ÿฌ๋กœ๋ถ€ํ„ฐ ํšŒ๋ณตํ•˜๋Š” ๊ฒƒ
    • ex. request user intervention, recover on its own, shut down the system

ํ”„๋กœ๊ทธ๋ž˜๋จธ๋Š” ์ž˜๋ชป๋  ๊ฒƒ์„ ๋Œ€๋น„ํ•˜์—ฌ์„œ ํ”„๋กœ๊ทธ๋žจ์ด ์›๋ž˜ ์ž‘๋™๋˜์–ด์•ผ ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์ž‘๋™๋˜๋„๋ก ์„ค๊ณ„ํ•˜๋Š” ๊ฒƒ์— ์ฑ…์ž„์ด ์žˆ๋‹ค

๋งŽ์€ ์ฝ”๋“œ๋Š” ์—๋Ÿฌ ํ•ธ๋“ค๋ง์— ์ง€๋ฐฐ๋ฅผ ๋ฐ›์•„ ์—ฌ๊ธฐ์ €๊ธฐ ํฉ์–ด์ง„ ์—๋Ÿฌ ํ•ธ๋“ค๋ง์œผ๋กœ ์ธํ•ด ์ฝ”๋“œ๊ฐ€ ์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•˜๋Š”์ง€ ํŒŒ์•…ํ•˜๊ธฐ๊ฐ€ ์–ด๋ ต๋‹ค

์—๋Ÿฌ ํ•ธ๋“ค๋ง์€ ์ค‘์š”ํ•˜์ง€๋งŒ, ๋งŒ์•ฝ ๋กœ์ง์„ ๋ถˆ๋ช…ํ™•ํ•˜๊ฒŒ ๋งŒ๋“ ๋‹ค๋ฉด ํ‹€๋ฆฐ ๊ฒƒ์ด๋‹ค. ๋”ฐ๋ผ์„œ ์–ด๋–ป๊ฒŒ ์—๋Ÿฌ ํ•ธ๋“ค๋ง์„ ์ž˜ ํ•  ์ˆ˜ ์žˆ๋Š”์ง€๋ฅผ ์‚ดํŽด๋ณผ ๊ฒƒ์ด๋‹ค!

๐Ÿ“Œ [์ž˜๋ชป๋œ ๋ฐฉ๋ฒ•] ERROR FLAG

  • error flag์„ ์„ค์ •ํ•˜๊ฑฐ๋‚˜, error code๋ฅผ ๋ฐ˜ํ™˜ํ•˜์—ฌ ํ˜ธ์ถœ์ž๊ฐ€ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” ๋ฐฉ๋ฒ•
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
            DeviceRecord record = 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());
        }
    }
    ....
}

๋‘ ๋ฒˆ์งธ if๋ฌธ ์•ˆ์˜ ์„ธ๊ฐœ์˜ ๋ฉ”์†Œ๋“œ๊ฐ€ main logic!
DeviceRecord record = retrieveDevice(handle) ์—์„œ ๊ฐ’์„ ๋„ฃ์–ด์คฌ๊ธฐ ๋•Œ๋ฌธ์— if๊ฐ€ ํ•„์š”ํ•œ ๊ฒƒ์ด๊ณ , ์ž˜๋ชป๋œ ๊ฒƒ์„ ๋„ฃ์—ˆ์„ ๋•Œ๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ์ฝ”๋“œ์ด๋‹ค

๋ฌธ์ œ์ 

  • ํ˜ธ์ถœ์ž์—๊ฒŒ ๋ถˆํ•„์š”ํ•œ ๋ณต์žก์„ฑ์„ ์œ ๋ฐœ : ํ˜ธ์ถœ ์ฝ”๋“œ์˜ ๋กœ์ง์ด ์—๋Ÿฌ ์ฒ˜๋ฆฌ ์ฝ”๋“œ๋กœ ์ธํ•ด ๊ฐ€๋ ค์ ธ ํ˜ผ๋ž€์Šค๋Ÿฌ์›Œ์งˆ ์ˆ˜ ์žˆ๋‹ค
  • ์—๋Ÿฌ ํ™•์ธ์˜ ์˜๋ฌด : ํ˜ธ์ถœ์ž๋Š” ํ˜ธ์ถœ ์งํ›„์— ๋ฐ˜๋“œ์‹œ ์—๋Ÿฌ๋ฅผ ํ™•์ธํ•ด์•ผ ํ•˜์ง€๋งŒ, ์ด๋ฅผ ์žŠ์–ด๋ฒ„๋ฆฌ๊ธฐ ์‰ฝ๋‹ค

๐Ÿ“Œ [์˜ณ์€ ๋ฐฉ๋ฒ•] Throwing Exception

  • ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ์˜ˆ์™ธ๋ฅผ ๋˜์ง€๋Š” ๊ฒƒ(throw an exception)์ด ๋” ๋‚˜์€ ์ ‘๊ทผ
    • ํ˜ธ์ถœ ์ฝ”๋“œ๊ฐ€ ๋” ๊ฐ„๊ฒฐํ•ด์ง„๋‹ค
    • ๋กœ์ง์ด ์—๋Ÿฌ ์ฒ˜๋ฆฌ ์ฝ”๋“œ๋กœ ๊ฐ€๋ ค์ง€์ง€ ์•Š๋Š”๋‹ค
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(DeivceID id) {
    	...
        throw new DeivceShutDownError("Invalid handle for: " + id.toString());
        ...
    }
    ...
}
  • DeviceShutDownError์— ์•ˆ์— ์ ํžŒ ๋‚ด์šฉ์„ try-catch๋ฌธ์—์„œ ํ•˜๋‚˜์”ฉ ์ฒ˜๋ฆฌํ•ด์ฃผ๋ฉด ์ง€์ €๋ถ„ํ•ด์งˆ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— method๋ฅผ ์ƒ์„ฑํ•ด์„œ ๊ทธ ๋ฉ”์„œ๋“œ์—์„œ ํ•œ ๋ฒˆ์— ๋‹ค ์ฒ˜๋ฆฌํ•˜๋„๋ก ๊ตฌํ˜„ํ•œ ์ฝ”๋“œ์ด๋‹ค
  • ๋งˆ์ง€๋ง‰ throw new DeviceShutDownError~ ๋ถ€๋ถ„๋„ ์›๋ž˜๋Š” return ~ ์ด์—ˆ์ง€๋งŒ, ๊ฐ„๊ฒฐํ•˜๊ฒŒ ์—๋Ÿฌ๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ์œ„ ์ฝ”๋“œ์™€ ๊ฐ™์ด ์ž‘์„ฑ๋˜์—ˆ๋‹ค

์ด๋กœ์จ ์ฝ”๋“œ๋ฅผ ๋…๋ฆฝ์ ์œผ๋กœ ๋ณด๊ณ  ์ดํ•ดํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค

๊ฒฐ๋ก ! return ์ฝ”๋“œ๋ณด๋‹ค๋Š” exceptions์„ ์‚ฌ์šฉํ•˜๋ผ!

๐Ÿ”ป try-catch-(finally)๋ฅผ ๋จผ์ € ์ž‘์„ฑํ•˜๊ณ  ์‹œ์ž‘ํ•  ๊ฒƒ!

try { 
	//do something
} catch (XException e) {
	//handle XException
} catch (YException e) {
	//handle YException
} finally {
	// clean up
}
  • try blocks
  • catch blocks

์˜ˆ์™ธ์ฒ˜๋ฆฌ๋ฅผ ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒฝ์šฐ์—, try-catch-finally์„ ๋จผ์ € ์„ ์–ธํ•˜๊ณ  ์‹œ์ž‘ํ•˜๋Š” ์—ฐ์Šต์„ ํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค

โœ๏ธ [STEP 1] : the first draft

public List<RecordedGrip> retrieveSection(String sectionName) {
	//dummy return until we have a real implemenation
    return new ArrayList<RecordedGrip>();
}
  1. ํ•จ์ˆ˜์˜ ์ž…๋ ฅ ์ •ํ•˜๊ธฐ (retrieveSection)
  2. ํ•จ์ˆ˜์˜ ์ถœ๋ ฅ ์ •ํ•˜๊ธฐ (String sectionName)
  3. return value (return ๋ฌธ)

compile์€ ๋˜์–ด์•ผ ํ•˜๋‹ˆ ์šฐ์„  ๋นˆ array๋ฅผ ๋„ฃ๋Š” ๊ฒƒ!


โœ๏ธ [STEP 2] : try-catch-finally์„ ๋จผ์ € ์ž‘์„ฑํ•˜๊ธฐ

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

์—ฌ๊ธฐ์„œ new~ ๋ฅผ ํ†ตํ•ด ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค์–ด์ฃผ์—ˆ๋Š”๋ฐ, ๊ฐ์ฒด ์‚ฌ์šฉ ํ›„์—” ๋‚ ๋ ค์ฃผ๋Š” ๊ฒŒ ์ข‹๋‹ค (open -> closeํ•ด์•ผ)

์ด์ฒ˜๋Ÿผ ๋ผˆ๋Œ€๋ถ€ํ„ฐ ์žก๊ณ  ์ฑ„์›Œ๋‚˜๊ฐ€๋Š” ๊ฒƒ!


โœ๏ธ [STEP 3] : ๋ฆฌํŒฉํ† ๋ง์„ ํ•˜์—ฌ ์˜ˆ์™ธ์˜ ์ข…๋ฅ˜๋ฅผ ์ขํ˜€๋‚˜๊ฐˆ ๊ฒƒ

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>();
}
  • stream.close()๋ถ€ํ„ฐ ์ž‘์„ฑํ•˜๊ณ  ๋นˆ ๊ณณ์„ ์ฑ„์šฐ๋Š” ๊ฒƒ!

โœ๏ธ [STEP 4] : ๋‚˜๋จธ์ง€ ๋กœ์ง์„ ์ฑ„์›Œ๋‚˜๊ฐˆ ๊ฒƒ

public List<RecordedGrip> retrieveSection(String sectionName) {
	try {
    	FileInputStream stream = new FileInputStream(sectionName)
        //๋‚˜๋จธ์ง€ ๋กœ์ง ์—ฌ๊ธฐ์—
        stream.close();
    } catch (FileNotFoundException e) {
    	throw new StorageException("retrieval error", e);
    }
    return ...;
}

๐Ÿ“Œ ์˜ˆ์™ธ์ฒ˜๋ฆฌ๋ฅผ ๋‚ด์šฉ๊ณผ ํ•จ๊ป˜ ์ œ๊ณตํ•˜๊ธฐ

  • ๊ฐ๊ฐ์˜ ์˜ˆ์™ธ๋Š” ์ถฉ๋ถ„ํ•œ ๋‚ด์šฉ์„ ์ œ๊ณตํ•ด์•ผ ํ•œ๋‹ค
  • debugging์ด ํŽธํ•˜๋„๋ก ์ƒ์„ฑํ•ด์•ผ ํ•œ๋‹ค
    • ์–ด๋””์„œ ๋ฐœ์ƒํ–ˆ๋Š”์ง€
    • ์™œ ๋ฐœ์ƒํ–ˆ๋Š”์ง€

์‹คํŒจํ•œ ํ•ด๋‹น operation๊ณผ ์‹คํŒจ ์ข…๋ฅ˜๋ฅผ ์–ธ๊ธ‰ํ•œ๋‹ค
catch๋ฌธ์— ์ด๋ฅผ ์ž‘์„ฑํ•˜์—ฌ ์—๋Ÿฌ๋ฅผ log ํ•  ์ˆ˜ ์žˆ๋‹ค

์˜ˆ์‹œ

public static void main(String[] args) {
	Scanner scanner = new Scanner(System.in); //#1
    int firstNumber, secondNumber, result;
    
    firstNumber = scanner.nextInt();
    secondNumber = scanner.nextInt();
    scanner.close(); //#1
    try{
    	result = divideInt(firstNumber, secondNumber);
    } catch (DivideByZeroException e) {
    	System.out.println(e.getMessage());
    }
}

public static int divideInt(int dividend, int divisor) {
	if (divisor == 0)
    	throw new DivideByZeroException("divideInt() : divisor cannot be 0");
    retrun dividend / divisor;
}
  • #1 : ์„ธํŠธ๋กœ ๋ฐ•์•„๋†“๊ณ  ์‹œ์ž‘ํ•˜๊ธฐ!!
  • DivideByZeroException("divideInt() : divisor cannot be 0") ์—์„œ
    • divideInt() : ์–ด๋””
    • divisor cannot be 0 : ์™œ

โ—๏ธtry ๋ฌธ์—์„œ divideInt๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š” ์ด์œ 

  1. step down rule์„ ๋”ฐ๋ฅด๊ธฐ ์œ„ํ•จ
  2. ํ•จ์ˆ˜๋กœ ๋–ผ์–ด๋†“์ง€ ์•Š์œผ๋ฉด testable ํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ. Unit Test๊ฐ€ ๊ฐ€๋Šฅํ•ด์•ผ ํ•˜๋ฏ€๋กœ ๋ถ„๋ฆฌ!

๐Ÿ“Œ ํ˜ธ์ถœ์ž์˜ ํ•„์š”์— ๋”ฐ๋ผ ์˜ˆ์™ธ ํด๋ž˜์Šค๋ฅผ ์ •์˜ํ•  ๊ฒƒ

์—๋Ÿฌ๋ฅผ ๋ถ„๋ฅ˜ํ•˜๋Š” ๋ฐฉ๋ฒ•์—๋Š” ์—ฌ๋Ÿฌ ๋ฐฉ๋ฒ•์ด ์กด์žฌํ•œ๋‹ค

  • ์†Œ์Šค๋กœ (ex. ์–ด๋–ค ์ปดํฌ๋„ŒํŠธ์ธ์ง€)
  • ์ข…๋ฅ˜๋กœ (ex. ์žฅ์น˜ ๊ฒฐํ•จ, ๋„คํŠธ์›Œํฌ ์˜ค๋ฅ˜)
  • ...

์ด๋ ‡๊ฒŒ ๋„ˆ๋ฌด ๋‹ค์–‘ํ•˜๊ฒŒ ๋˜์ง€๋ฉด ๋ฐ›๋Š” ์ชฝ์—์„œ ๊ดด๋กœ์›Œ์ง€๊ณ , ์ฝ”๋“œ๋„ ์ง€์ €๋ถ„ํ•ด์ง„๋‹ค

์˜ˆ์™ธ ํด๋ž˜์Šค๋ฅผ ์ •์˜ํ•  ๋•Œ๋Š” ๊ทธ๋“ค์ด ์–ด๋–ป๊ฒŒ ์žกํž ๊ฒƒ์ธ์ง€๋ฅผ ๊ฐ€์žฅ ์ค‘์š”ํ•˜๊ฒŒ ๊ณ ๋ คํ•ด์•ผ ํ•œ๋‹ค. ์ฆ‰, ํ˜ธ์ถœ์ž์— ์˜ํ•ด ์–ด๋–ป๊ฒŒ ํ•ธ๋“ค๋ง ๋  ๊ฒƒ์ธ์ง€๋ฅผ ๋งํ•˜๋Š” ๊ฒƒ

๊ธฐ๋ณธ์ ์œผ๋กœ๋Š” ๋ถ„๋ฆฌํ•˜์ง€ ์•Š๊ณ  ๊ฐ™์ด ์ฒ˜๋ฆฌํ•˜๊ณ ,
exception๋ผ๋ฆฌ ๋‹ค๋ฅด๊ฒŒ ์ฒ˜๋ฆฌํ•˜๊ธธ ๋ฐ”๋ž„ ๋•Œ ๋ถ„๋ฆฌ๋ฅผ ํ•  ๊ฒƒ!

๐Ÿ“ Bad example

  • ์—ฌ๊ธฐ์„œ ACMEPort๋Š” third-party ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ, ์—ฌ๋Ÿฌ ์ข…๋ฅ˜์˜ ์˜ˆ์™ธ๋ฅผ ๋˜์งˆ ์ˆ˜ ์žˆ๋‹ค
  • ์ฝ”๋“œ์—์„œ๋Š” ๋ชจ๋“  ๊ฐ€๋Šฅํ•œ ์˜ˆ์™ธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋ ค๊ณ  ์‹œ๋„ํ•˜๋ฉฐ, ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค
    • ์ค‘๋ณต ์ฝ”๋“œ : ๋ชจ๋“  catch ๋ธ”๋ก์—์„œ ๋น„์Šทํ•œ ์ž‘์—… (์˜ˆ: ์—๋Ÿฌ ๊ธฐ๋ก)์„ ๋ฐ˜๋ณต
    • ์œ ์ง€๋ณด์ˆ˜ ์–ด๋ ค์›€ : ์ƒˆ๋กœ์šด ์˜ˆ์™ธ๊ฐ€ ์ถ”๊ฐ€๋˜๋ฉด ๋” ๋งŽ์€ catch ๋ธ”๋ก์„ ์ž‘์„ฑํ•ด์•ผํ•จ
ACMEPort port = new ACMEPort(12);

try {
    port.open();
} catch (DeviceResponseException e) {
    reportPortError(e);
    logger.log("Device response exception", e);
} catch (ATM121UnlockedException e) {
    reportPortError(e);
    logger.log("Unlock exception", e);
} catch (GMXError e) {
    reportPortError(e);
    logger.log("Device response exception");
}

๋ชจ๋“  ์˜ˆ์™ธ์—์„œ reportPortError(e)์™€ ๋กœ๊ทธ ๊ธฐ๋ก(logger.log)์„ ๋ฐ˜๋ณต - ๋™์ผํ•œ ํŒจํ„ด์ด ๋ฐ˜๋ณต๋˜๋ฏ€๋กœ ์ฝ”๋“œ๊ฐ€ ๋ถˆํ•„์š”ํ•˜๊ฒŒ ๊ธธ์–ด์ง.


๐Ÿ“ Better example

  • 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; //1
    public LocalPort(int portNumber) {
    	innerPort =new ACMEPort(portNumber); //2
    }
    
    public void open() {
    	try {
        	innerPort.open(); //3
        } catch (DeviceResponseException e) {
        	throw new PortDeviceFailure(e); //4
        } catch (ATM121UnlockedException e) {
        	throw new PortDeviceFailure(e);
        } catch (GMXError e) {
        	throw new PortDeviceFailure(e);
        }
    }
}
  1. ํ•˜๋‚˜๋กœ๋งŒ ํ†ต์ผํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ๊ฑ”๋ฅผ ๊ฐ์‹ธ๋Š” wrapper class๋ฅผ ๋งŒ๋“ ๋‹ค!
  2. ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•ด์ค€๋‹ค
  3. ์„ธ ๊ฐ€์ง€ ํƒ€์ž…์„ ๋˜์กŒ์ง€๋งŒ
  4. PortDeviceFailure(e)๋กœ ํ†ต์ผ์‹œ์ผœ์ค€ ๊ฒƒ!

๐Ÿ–‹๏ธ Wrapping Class

  • third-party API๋ฅผ wrapping ํ•˜๋Š” ๊ฒƒ์€ ์ข‹์€ ์—ฐ์Šต์ด๋‹ค!

๐Ÿ”ป ์žฅ์ 

  • ์ข…์†์„ฑ์„ ์ค„์ธ๋‹ค
    • ํŠน์ • ์„œ๋“œํŒŒํ‹ฐ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์—์„œ ๋‹ค๋ฅธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ ์ „ํ™˜ํ•  ๋•Œ ์ฝ”๋“œ ์ˆ˜์ • ๋น„์šฉ์ด ์ ์Œ
  • ์œ ์—ฐํ•œ API ์„ค๊ณ„
    • ํŠน์ • third-party ์ œ๊ณต์ž์˜ API ์„ค๊ณ„ ์„ ํƒ์— ๊ตฌ์†๋˜์ง€ ์•Š๋Š”๋‹ค
    • ์ž์‹ ์ด ์‚ฌ์šฉํ•˜๊ธฐ ํŽธ๋ฆฌํ•œ API๋ฅผ ์ •์˜ํ•˜์—ฌ ํ™œ์šฉ ๊ฐ€๋Šฅํ•˜๋‹ค

์˜ˆ๋ฅผ ๋“ค์–ด๋ณด์ž

class XCalc {
	int plus(int a, int b)...
    int minus(int a, int b)...
}

์—ฌ๊ธฐ์„œ ๋งŒ์•ฝ ์ด๋ฆ„์„ add, subtract๋กœ ์ˆ˜์ •ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด

class MyCalc {
	XCalc innerCalc = new XCalc();
    int add(int a, int b) {
    	return innerCalc.plus(a, b);
    }
    
    int subtract(int a, int b) {
    	return innerCalc.minus(a, b);
    }
}

Define the Normal Flow

์œ„ ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด

  • normal case : ์‹๋น„๊ฐ€ ์ง€๋ถˆ๋œ ๊ฒฝ์šฐ๋ฅผ normal case๋กœ ๋ณธ๊ฒƒ
  • special case : ์‹๋น„๋ฅผ ์ง€๋ถˆํ•˜์ง€ ์•Š์•˜์„ ๊ฒฝ์šฐ ์ง์›๋“ค์€ ๊ทธ๋‚ ์— ๋Œ€ํ•œ ์‹๋น„ ์ผ๋‹น์„ ๋ฐ›์Œ

special case๋Š” ์—๋Ÿฌ๋Š” ์•„๋‹ˆ๋ฉฐ, ์—ฌ๊ธฐ์„œ๋Š” exception์œผ๋กœ ์ฒ˜๋ฆฌํ•œ ๊ฒƒ์ด๋‹ค

Problem) try-catch๋ฌธ ์ž์ฒด๊ฐ€ ์ฝ”๋“œ๊ฐ€ ์ง€์ €๋ถ„ํ•ด์ง„ ๊ฒƒ
Solution) ์ž์‹ ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค์–ด์„œ ๊ฐ™์€ ๋ฉ”์†Œ๋“œ๋ช…์œผ๋กœ ์ฒ˜๋ฆฌํ•œ๋‹ค

class A {
	void f(){..}
}

class B extends A {
	void f(){..}
}

class C extends A {
	void f(){..}
}
A a = new A();
a.f(); //1

a = new B();
//overriding
a.f(); //2

a = new C();
a.f(); //3
  1. A์˜ f๊ฐ€ ํ˜ธ์ถœ๋œ๋‹ค
  2. B์˜ f๊ฐ€ ํ˜ธ์ถœ๋œ๋‹ค. ์ƒ์†์„ ๋ฐ›์•˜์ง€๋งŒ overridingํ•˜์—ฌ ๋‚ด๊บผ๋กœ ๋ฎ์–ด์น˜์šด ๊ฒƒ
  3. C์˜ f๊ฐ€ ํ˜ธ์ถœ๋œ๋‹ค

์ด์ฒ˜๋Ÿผ Special Case Pattern์„ ์‚ฌ์šฉํ•จ์œผ๋กœ์จ, ์ŠคํŽ˜์…œ ์ผ€์ด์Šค๊ฐ€ ์ฝ”๋“œ์˜ ๋ณต์žก์„ฑ์„ ๋†’์ด์ง€ ๋ชปํ•˜๊ฒŒ ํ•˜๋Š” ๊ฒƒ์ด๋‹ค

  • ExpenseReportDAO.getMeals()๋ฅผ ์ˆ˜์ •ํ•˜์—ฌ, ํ•ญ์ƒ MealExpense ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ํ•œ๋‹ค
    • ๋” ์ด์ƒ MealExpensesNotFound๋ฅผ ๋˜์ง€์ง€ ์•Š์•„๋„ ๋œ๋‹ค
  • ๋งŒ์•ฝ ์‹๋น„ ์ง€๋ถˆ์ด ์—†๋‹ค๋ฉด, PerDiemMealExpenses ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•  ๊ฒƒ์ด๋‹ค
    • PerDiemMealExpenses : MealExpenses์˜ subclass

PerDiemMealExpenses๋ผ๋Š” ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑํ•˜์—ฌ MealExpenses๋ฅผ ํ™•์žฅ

public class PerDiemMealExpenses extends MealExpenses {
	public int getTotal() {
    	//return the per diem default
    }
}
  • getTotal() ๋ฉ”์„œ๋“œ๋ฅผ ๊ตฌํ˜„ํ•˜์—ฌ ์ผ๋‹น(per diem) ๊ธฐ๋ณธ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค

๊ฒฐ๊ณผ

  • ์ผ๋‹น์ด ์žˆ๋Š” ๊ฒฝ์šฐ : MealExpenses.getTotal()์ด ํ˜ธ์ถœ
  • ์ผ๋‹น์ด ์—†๋Š” ๊ฒฝ์šฐ : PerDiemMealExpenses.getTotal()์ด ํ˜ธ์ถœ
MealExpenses expenses = expenseReportDAO.getMeals(employee.getID());
m_total += expenses.getTotal();

๐Ÿ–‹๏ธ Special Case Pattern

  • special class๋ฅผ ํ•ธ๋“ค๋ง ํ•˜๊ธฐ ์œ„ํ•œ subclass๋ฅผ ๋งŒ๋“œ๋Š” ๊ฒƒ
  • ์˜ˆ์™ธ๋ฅผ ๋˜์ง€๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ special case object๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ฒŒ ํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค
  • ์ด๋กœ์จ, ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋ฅผ ๋‹ค๋ฃจ์ง€ ์•Š์•„๋„ ๋˜๊ณ  special case ๊ฐ์ฒด ๋‚ด๋ถ€์— ๋™์ž‘์„ ์บก์Аํ™”ํ•˜์—ฌ ์ฝ”๋“œ์˜ ๊ฐ„๊ฒฐ์„ฑ์„ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค
MealExpenses expenses = expenseReportDAO.getMeals(employee.getID());
m_total += expenses.getTotal();

๐Ÿ“Œ Null ๋ฐ˜ํ™˜ํ•˜์ง€ ์•Š๊ธฐ

๋งŒ์•ฝ ๋ฉ”์„œ๋“œ๊ฐ€ null์„ ๋ฐ˜ํ™˜ํ•˜๊ฒŒ ํ•˜๋ฉด, ํ˜ธ์ถœํ•˜๋Š” ์‚ฌ๋žŒ๋งˆ๋‹ค null check์„ ํ•ด์•ผ ํ•œ๋‹ค

๊ทธ๋Ÿฌ๋ฉด ๋งŽ์€ ๋„์ฒดํฌ๋กœ ์ธํ•ด ์ฝ”๋“œ๊ฐ€ ๋ถˆ๋ช…ํ™•ํ•ด์งˆ ์ˆ˜ ์žˆ๋‹ค
๋”๋ถˆ์–ด, ๋„์ฒดํฌ ํ•˜๋‚˜๋ฅผ ๋†“์น˜๋ฉด ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ œ์–ดํ•˜๊ธฐ ์–ด๋ ค์›Œ์ง„๋‹ค - NullPointException ๋ฐœ์ƒ

public void registerItem(Item item) {
	if (item != null) {
    	ItemRegistery registery = persistentStore.getItemRegistery();
        if (registery != null) {
        	Item existing = registery.getItem(item.getID());
            if (existing.getBillingPeriod().hasRetailOwner()) {
            	existing.register(item);
            }
        }
    }
}

๋‹ค์Œ ์ฝ”๋“œ๋Š” ๋„ˆ๋ฌด ๋งŽ์€ ๋„์ฒดํฌ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค
๋„ ์ฒดํฌ๋ฅผ ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” if๋ฌธ์ด ํ•„์š”ํ•˜๊ธฐ์—, ์ค‘์ฒฉ๋œ if๋ฌธ์ด ๋ฐœ์ƒํ•œ๋‹ค

๋”ฐ๋ผ์„œ null์„ ๋ฐ˜ํ™˜ํ•˜์ง€ ์•Š๋„๋ก ํŒ€๋ผ๋ฆฌ ์•ฝ์†ํ•ด์•ผํ•œ๋‹ค!

์ด๋Ÿฐ ๊ฒฝ์šฐ, special cas object๋ฅผ ๋Œ€์‹  ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ด๋ณผ ์ˆ˜ ์žˆ๋‹ค!

์˜ˆ์‹œ

List<Employee> employees = getEmployees();
if (employees != null) {
	for (Employee e : employees) {
    	totalPay += e.getPay();
    }
}

getEmployees()๋Š” null์„ ๋ฆฌํ„ดํ•  ์ˆ˜ ์žˆ๋‹ค. ํ•˜์ง€๋งŒ ๊ทธ๋ž˜์•ผ๋งŒ ํ•˜๋Š”๊ฐ€?

โ–ฝโ–ฝโ–ฝโ–ฝโ–ฝโ–ฝโ–ฝโ–ฝโ–ฝโ–ฝโ–ฝโ–ฝโ–ฝโ–ฝโ–ฝโ–ฝโ–ฝโ–ฝโ–ฝโ–ฝโ–ฝโ–ฝโ–ฝโ–ฝ

List<Employee> employees = getEmployees();
for (Employee e : employees) {
	totalPay += e.getPay();
}

๋นˆ ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ฆฌํ„ดํ•˜๋„๋ก ํ•˜๋ฉด ์ฝ”๋“œ๋ฅผ ๊ฐ„๊ฒฐํ•˜๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค!

๐Ÿ”ป argument๋กœ๋„ Null์„ ์ฃผ์ง€ ๋ง ๊ฒƒ

  • Java๋Š” Collections.emptyList()๋ฅผ ์ œ๊ณตํ•œ๋‹ค
  • ์ด๋ฏธ ์ •์˜๋œ ๋ถˆ๋ณ€ ๋ฆฌ์ŠคํŠธ (Immutable List)์ธ Collections.emptyList() ๋ฅผ ์ œ๊ณต
  • Null ๋Œ€์‹  ๋นˆ ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ฐ˜ํ™˜ํ•˜์—ฌ NullPointerException ๋ฐœ์ƒ ๊ฐ€๋Šฅ์„ฑ์„ ์ค„์ž„

๊ฐœ์„ ๋œ ์ฝ”๋“œ๋ฅผ ์‚ดํŽด๋ณด์ž

public List<Employee> getEmployees() {
    ...
    if (... there are employees ...) {
        return employees;
    } else if (... there are no employees ...) {
        return Collections.emptyList();
    }
}

NullPointerException ๋ฐœ์ƒ ๊ฐ€๋Šฅ์„ฑ์„ ์ตœ์†Œํ™”ํ•˜๊ณ , ์ฝ”๋“œ ๊ฐ€๋…์„ฑ ๋ฐ ์œ ์ง€๋ณด์ˆ˜์„ฑ์„ ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ๋‹ค


๐Ÿ“Œ Null Passํ•˜์ง€ ์•Š๊ธฐ

๋ฉ”์„œ๋“œ์—์„œ Null์„ ๋ฆฌํ„ดํ•˜๋Š” ๊ฒƒ๋„ ๋‚˜์˜์ง€๋งŒ, ๋ฉ”์„œ๋“œ์—๊ฒŒ null์„ ํŒจ์Šคํ•˜๋Š” ๊ฒƒ์€ ๋” ๋‚˜์˜๋‹ค

null์„ ์ฃผ๊ณ  ๋ฐ›๊ธฐ ์‹œ์ž‘ํ•˜๋ฉด ๋ฐ›๋Š” ์ชฝ์—์„œ ํ•  ์ˆ˜ ์žˆ๋Š”๊ฒŒ ๋งŽ์ง€ ์•Š๋‹ค -> if๋ฌธ์œผ๋กœ checkํ•ด๋„ exception์„ throwํ•˜๋Š” ๊ฒƒ๋ฐ–์— ํ•˜์ง€ ๋ชปํ•œ๋‹ค

์•„๋ž˜์™€ ๊ฐ™์€ ํด๋ž˜์Šค๊ฐ€ ์žˆ๋‹ค๊ณ  ํ•ด๋ณด์ž

public class MetricsCalculator {
	public double xProjection(Point p1, Point p2) {
    	return (p2.x - p1.x) * 1.5;
    }
    ...
}

์–ด๋–ค ์‚ฌ๋žŒ์ด ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ null์„ ํŒจ์Šคํ•˜๋ฉด ์–ด๋–ป๊ฒŒ ๋ ๊นŒ?

calculator.xProjection(null, new Point(12,13));

๋‹น์—ฐํžˆ NullPointException์„ ๋ฐ›์„ ๊ฒƒ์ด๋‹ค

๐Ÿ”ป Null ์ „๋‹ฌ ๋ฐฉ์ง€ ๋ฐฉ์•ˆ

๐Ÿ“ ์ƒˆ ์˜ˆ์™ธ ์œ ํ˜• ์ •์˜ํ•˜๊ธฐ

  • Null ๊ฐ’์ด ์ „๋‹ฌ๋  ๊ฒฝ์šฐ๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ์ƒˆ ์˜ˆ์™ธ ์œ ํ˜• (InvalidArgumentException)์„ ์ •์˜ํ•˜๊ณ  ์ด๋ฅผ ๋˜์ง„๋‹ค
public class MetricsCalculator {
    public double xProjection(Point p1, Point p2) {
        if (p1 == null || p2 == null) {
            throw new InvalidArgumentException(
                "Invalid argument for MetricsCalculator.xProjection");
        }
        return (p2.x - p1.x) * 1.5;
    }
}

์žฅ๋‹จ์ 

  • NullPointerException๋ณด๋‹ค ๋‚˜์€ ์„ ํƒ์ผ ์ˆ˜ ์žˆ๋‹ค
  • ํ•˜์ง€๋งŒ, InvalidArguementException์„ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์ •์˜ํ•ด์•ผ ํ•œ๋‹ค
  • ํ•ธ๋“ค๋Ÿฌ์—์„œ ์–ด๋–ค ์ฒ˜๋ฆฌ๋ฅผ ํ•ด์•ผํ• ์ง€ ๋ช…ํ™•ํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ๋‹ค

๐Ÿ“ Assertion ์‚ฌ์šฉ

  • Null ๊ฒ€์‚ฌ๋ฅผ ์œ„ํ•ด Assertion์„ ์ถ”๊ฐ€ํ•œ๋‹ค
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;
    }
}

์žฅ์ 

  • ์ฝ”๋“œ ๋ฌธ์„œํ™”๋กœ์„œ์˜ ์—ญํ• ์„ ์ˆ˜ํ–‰ํ•œ๋‹ค
  • ์ฝ”๋“œ ๋‚ด null ๊ฒ€์‚ฌ ํ•„์š”์„ฑ์„ ๋ช…ํ™•ํžˆ ๋“œ๋Ÿฌ๋‚ธ๋‹ค

๋‹จ์ 

  • Null์ด ์ „๋‹ฌ๋˜๋ฉด ๋Ÿฐํƒ€์ž„ ์—๋Ÿฌ๊ฐ€ ์—ฌ์ „ํžˆ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค
  • Assertion์€ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ๋””๋ฒ„๊น…์— ๋„์›€์„ ์ค„ ๋ฟ์ด๋‹ค

assert๋ฌธ์€ ๋ชจ๋“  ํ…Œ์ŠคํŠธ๋ฅผ ํ†ต๊ณผํ•˜๋ฉด ์ถœ์‹œํ•  ๋• ์ด ๋‘ ์ค„์„ ์ œ๊ฑฐํ•˜๊ณ  ์ถœ์‹œํ•œ๋‹ค! ์ผ์ข…์˜ bug detection์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ!


๊ฒฐ๋ก 

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

0๊ฐœ์˜ ๋Œ“๊ธ€