๐ป ์ฃผ์ ๋ด์ฉ : Wrapping Class, special case pattern
ํ๋ก๊ทธ๋๋จธ๋ ์๋ชป๋ ๊ฒ์ ๋๋นํ์ฌ์ ํ๋ก๊ทธ๋จ์ด ์๋ ์๋๋์ด์ผ ํ๋ ๋ฐฉ์์ผ๋ก ์๋๋๋๋ก ์ค๊ณํ๋ ๊ฒ์ ์ฑ ์์ด ์๋ค
๋ง์ ์ฝ๋๋ ์๋ฌ ํธ๋ค๋ง์ ์ง๋ฐฐ๋ฅผ ๋ฐ์ ์ฌ๊ธฐ์ ๊ธฐ ํฉ์ด์ง ์๋ฌ ํธ๋ค๋ง์ผ๋ก ์ธํด ์ฝ๋๊ฐ ์ด๋ป๊ฒ ์๋ํ๋์ง ํ์ ํ๊ธฐ๊ฐ ์ด๋ ต๋ค
์๋ฌ ํธ๋ค๋ง์ ์ค์ํ์ง๋ง, ๋ง์ฝ ๋ก์ง์ ๋ถ๋ช ํํ๊ฒ ๋ง๋ ๋ค๋ฉด ํ๋ฆฐ ๊ฒ์ด๋ค. ๋ฐ๋ผ์ ์ด๋ป๊ฒ ์๋ฌ ํธ๋ค๋ง์ ์ ํ ์ ์๋์ง๋ฅผ ์ดํด๋ณผ ๊ฒ์ด๋ค!
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๊ฐ ํ์ํ ๊ฒ์ด๊ณ , ์๋ชป๋ ๊ฒ์ ๋ฃ์์ ๋๋ฅผ ๋ณด์ฌ์ฃผ๋ ์ฝ๋์ด๋ค
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());
...
}
...
}
์ด๋ก์จ ์ฝ๋๋ฅผ ๋ ๋ฆฝ์ ์ผ๋ก ๋ณด๊ณ ์ดํดํ ์ ์๊ฒ ๋๋ค
๊ฒฐ๋ก ! return ์ฝ๋๋ณด๋ค๋ exceptions์ ์ฌ์ฉํ๋ผ!
try {
//do something
} catch (XException e) {
//handle XException
} catch (YException e) {
//handle YException
} finally {
// clean up
}
์์ธ์ฒ๋ฆฌ๋ฅผ ํ ์ ์๋ ๊ฒฝ์ฐ์, 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>();
}
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>();
}
โ๏ธ [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 ...;
}
์คํจํ ํด๋น 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;
}
DivideByZeroException("divideInt() : divisor cannot be 0") ์์โ๏ธtry ๋ฌธ์์ divideInt๋ฅผ ์ฌ์ฉํด์ผ ํ๋ ์ด์
์๋ฌ๋ฅผ ๋ถ๋ฅํ๋ ๋ฐฉ๋ฒ์๋ ์ฌ๋ฌ ๋ฐฉ๋ฒ์ด ์กด์ฌํ๋ค
์ด๋ ๊ฒ ๋๋ฌด ๋ค์ํ๊ฒ ๋์ง๋ฉด ๋ฐ๋ ์ชฝ์์ ๊ดด๋ก์์ง๊ณ , ์ฝ๋๋ ์ง์ ๋ถํด์ง๋ค
์์ธ ํด๋์ค๋ฅผ ์ ์ํ ๋๋ ๊ทธ๋ค์ด ์ด๋ป๊ฒ ์กํ ๊ฒ์ธ์ง๋ฅผ ๊ฐ์ฅ ์ค์ํ๊ฒ ๊ณ ๋ คํด์ผ ํ๋ค. ์ฆ, ํธ์ถ์์ ์ํด ์ด๋ป๊ฒ ํธ๋ค๋ง ๋ ๊ฒ์ธ์ง๋ฅผ ๋งํ๋ ๊ฒ
๊ธฐ๋ณธ์ ์ผ๋ก๋ ๋ถ๋ฆฌํ์ง ์๊ณ ๊ฐ์ด ์ฒ๋ฆฌํ๊ณ ,
exception๋ผ๋ฆฌ ๋ค๋ฅด๊ฒ ์ฒ๋ฆฌํ๊ธธ ๋ฐ๋ ๋ ๋ถ๋ฆฌ๋ฅผ ํ ๊ฒ!
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)์ ๋ฐ๋ณต - ๋์ผํ ํจํด์ด ๋ฐ๋ณต๋๋ฏ๋ก ์ฝ๋๊ฐ ๋ถํ์ํ๊ฒ ๊ธธ์ด์ง.
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);
}
}
}

์๋ฅผ ๋ค์ด๋ณด์
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);
}
}

์ ์ฝ๋๋ฅผ ๋ณด๋ฉด
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
์ด์ฒ๋ผ Special Case Pattern์ ์ฌ์ฉํจ์ผ๋ก์จ, ์คํ์ ์ผ์ด์ค๊ฐ ์ฝ๋์ ๋ณต์ก์ฑ์ ๋์ด์ง ๋ชปํ๊ฒ ํ๋ ๊ฒ์ด๋ค

ExpenseReportDAO.getMeals()๋ฅผ ์์ ํ์ฌ, ํญ์ MealExpense ๊ฐ์ฒด๋ฅผ ๋ฐํํ๋๋ก ํ๋คMealExpensesNotFound๋ฅผ ๋์ง์ง ์์๋ ๋๋คPerDiemMealExpenses ๊ฐ์ฒด๋ฅผ ๋ฐํํ ๊ฒ์ด๋คPerDiemMealExpenses : MealExpenses์ subclassPerDiemMealExpenses๋ผ๋ ํด๋์ค๋ฅผ ์์ฑํ์ฌ MealExpenses๋ฅผ ํ์ฅ
public class PerDiemMealExpenses extends MealExpenses {
public int getTotal() {
//return the per diem default
}
}
๊ฒฐ๊ณผ
MealExpenses expenses = expenseReportDAO.getMeals(employee.getID());
m_total += expenses.getTotal();
MealExpenses expenses = expenseReportDAO.getMeals(employee.getID());
m_total += expenses.getTotal();
๋ง์ฝ ๋ฉ์๋๊ฐ 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();
}
๋น ๋ฆฌ์คํธ๋ฅผ ๋ฆฌํดํ๋๋ก ํ๋ฉด ์ฝ๋๋ฅผ ๊ฐ๊ฒฐํ๊ฒ ๋ง๋ค ์ ์๋ค!
Collections.emptyList()๋ฅผ ์ ๊ณตํ๋ค๊ฐ์ ๋ ์ฝ๋๋ฅผ ์ดํด๋ณด์
public List<Employee> getEmployees() {
...
if (... there are employees ...) {
return employees;
} else if (... there are no employees ...) {
return Collections.emptyList();
}
}
NullPointerException ๋ฐ์ ๊ฐ๋ฅ์ฑ์ ์ต์ํํ๊ณ , ์ฝ๋ ๊ฐ๋ ์ฑ ๋ฐ ์ ์ง๋ณด์์ฑ์ ๊ฐ์ ํ ์ ์๋ค
๋ฉ์๋์์ 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์ ๋ฐ์ ๊ฒ์ด๋ค
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;
}
}
์ฅ๋จ์
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;
}
}
์ฅ์
๋จ์
assert๋ฌธ์ ๋ชจ๋ ํ ์คํธ๋ฅผ ํต๊ณผํ๋ฉด ์ถ์ํ ๋ ์ด ๋ ์ค์ ์ ๊ฑฐํ๊ณ ์ถ์ํ๋ค! ์ผ์ข ์ bug detection์ผ๋ก ์ฌ์ฉํ๋ ๊ฒ์ด๊ธฐ ๋๋ฌธ!