์ค๋ฅ ์ฒ๋ฆฌ
์ค๋ฅ ์ฝ๋๋ณด๋ค ์์ธ๋ฅผ ์ฌ์ฉํ๋ผ
// 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;
}
}
๊ฒฐ๋ก
- ๊นจ๋ํ ์ฝ๋๋ ์ฝ๊ธฐ๋ ์ข์์ผ ํ์ง๋ง ์์ ์ฑ๋ ๋์์ผ ํ๋ค. ์ด ๋๊ฐ์ง์ ๋ชฉํ๋ ๋๋ฆฝ๋๋ ๋ชฉํ๊ฐ ์๋๋ค.