⚠개인적으로 공부하려고 메모해둔것 때문에 정리가안돼있고 깔끔하지않음
01.오류코드보다 예외를 사용하라
//7-1.DeviceController.java
public class DeviceController{
...
public void sendShutDown() {
DeviceHandle handle = getHandle(DEV1);
//디바이스 상태를 점검한다.
if (handle != DeviceHandle.INVALID)
{
//레코드 필드에 디바이스 상태를 저장한다.
retireveDeviceRecord(handle);
//디바이스가 일시정지 상태가 아니라면 종료한다.
if (record.getStatus() != DEVICE_SUSPENDED)
{
pauseDevice(handle);
clearDeviceWorkQueue(handle);
closeDevice(handle);
}
else
{
logger.log("Device suspened. Unable to shut down");
}
}
else
{
logger.log("Invalid handle for: " + DEV1.toString());
}
}
...
}
//7-2.DeviceController.java (예외사용)
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 = retireveDeviceRecord(handle);
pauseDevice(handle);
clearDeviceWorkQueue(handle);
closeDevice(handle);
}
private DeviceHandle getHandle(DeviceID id) {
throw new DeviceShutDownError("Invalid handle for : " + id.toString());
...
}
...
}
02.Try-Catch-Finally문부터 작성하라
//try 블록은 트랜잭션과 비슷하다.
//파일이 없으면 예외를 던지는지 알아보는 단위 테스트
@Test(expected = StorageException.class)
public void retrieveSectionShouldThrowOnInvalidFileName() {
sectionStore.retrieveSection("invalid - file");
}
//단위테스트에 맞춰 다음코드를 구현
//그런데 코드가 예외를 던지지 않으므로 단위테스트는 실패.
public List<RecordedGrip> retrieveSection(String sectionName) {
//실제로 구현할 때까지 비어 있는 더미를 반환한다.
return new ArrayList<RecordedGrip>();
}
//잘못된 파일 접근을 시도하게 구현을 변경
public List<RecordedGrip> retrieveSection(String sectionName) {
try {
FileInfutStream stream = new FileInputStream(sectionName)
} catch (Exception e){
throw new StorageException("retrieval error", e);
}
return new ArrayList<RecordedGrip>();
}
//코드가 예외를 던지므로 테스트 성공
//이 시점에서 리팩터링이 가능하다.
//catch 블록에서 예외 유형을 좁혀 실제로 FileInfutStream 생성자가 던지는 fileNotFoundException을 잡아낸다.
public List<RecordedGrip> retrieveSection(String sectionName) {
try {
FileInfutStream stream = new FileInputStream(sectionName);
stream.close();
} catch (FileNotFoundException e){
throw new StorageException("retrieval error", e);
}
return new ArrayList<RecordedGrip>();
}
05.호출자를 고려해 예외 클래스를 정의하라
//오류를 형편없이 분류한 사례
//외부 라이브러리를 호출하는 try-catch-finally 문을 포함한 코드로
//외부 라이브러리가 던질 예외를 모두 잡아낸다.
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 {
...
}
// 대다수 상황에서 오류를 처리하는 방식은
// 오류를 일으킨 원인과 무관하게 비교적 일정하다.
// 1) 오류를 기록한다.
// 2) 프로그램을 계속 수행해도 좋은지 확인한다.
// 위의 코드는 예외에 대응하는 방식이 예외 유형과 무관하게 거의 동일
// 그래서 코드를 간결하게 고치기가 아주 쉬움
// 호출하는 라이브러리 api를 감싸면서 예외 유형 하나를 반환
LocalPort port = new LocalPort(12);
try {
port.open();
} catch (PortDeviceFailure e){
reportError(e);
logger.log(e.getMessage(), e);
} finally {
...
}
// 여기서 LocalPort 클래스는 단순히 ACMEPort 클래스가 던지는 예외를 잡아 변환하는 감싸기 클래스일 뿐이다.
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 PortDeviceFailure(e);
} catch (GMXError e) {
throw new PortDeviceFailure(e);
}
...
}
// 감싸기 클래스의 장점
// 외부 api를 감싸면 외부 라이브러리와 프로그램 사이에서 의존성이 크게 줄어든다.
// 나중에 다른 라이브러리로 갈아타도 비용이 적다.
// 또한 감싸기 클래스에서 외부 api를 호출하는 대신 테스트 코드를 넣어주는 방법으로 프로그램을 테스트하기도 쉬워진다.
// 특정 업체가 api를 설계한 방식에 발목 잡히지 않는다.
06.정상 흐름을 정의하라
//다음은 비용 청구 애플리케이션에서 총계를 계산하는 허술한 코드
try {
MealExpenses expenses = expenseReportDAO.getMeals(emplyee.getId());
m_total += expenses.getTotal();
} catch(MealExpensesNotFound e){
m_total += getMealPerDiem();
}
//특수한 상황을 처리할 필요가 없다면?
MealExpenses expenses = expenseReportDAO.getMeals(emplyee.getId());
m_total += expenses.getTotal();
//이 코드가 가능할까?
ExpenseReportDAO를 고쳐 언제나 MealExpense 객체를 반환한다.
청구한 식비가 없다면 일일 기본 식비를 반환하는 MealExpense 객체를 반환한다.
public class PerDiemMealExpenses implements MealExpense {
public int getTotal{
//기본값으로 일일 기본 식비를 반환한다.
}
}
// 이를 특수사례패턴이라고 부른다.
클래스를 만들거나 객체를 조작해 특수 사례를 처리하는 방식이다.
그러면 클라이언트 코드가 예외적인 상황을 처리할 필요가 없어진다.
07.null을 반환하지마라
//
public void registerItem(Item item){
if(item != null){
ItemRegistry registry = peristentStore.getItemRegistry();
if(registry != null){
Item existing = registry.getItem(item.getID());
if(existing.getBillingPreiod().hasRetailOwner()){
existing.register(item);
}
}
}
}
//null을 반환하는 코드는 일거리를 늘릴뿐만 아니라 호출자에게 문제를 떠넘긴다.
//메서드에서 null을 반환하고프다면 그 대신 예외를 던지거나 특수사례객체를 바호나한다.
//사용하려는 외부 api가 null을 반환한다면 감싸기 메서드를 구현해 예외를 더닞거나 특수 사례 객체를 반환하는 방식을 고려한다.
List<Employee> employees = getEmployees();
if (employees != null) {
for(Employee e : employees){
totalPay += e.getPay();
}
}
//위에서 getEmployees는 null도 반환한다.
//하지만 반드시 null을 반환할 필요가 있을까?
//getEmployees를 변경해 빈 리스트를 반환한다면 코드가 훨씬 깔끔해진다.
List<Employee> employees = getEmployees();
for(Employee e : employees){
totalPay += e.getPay();
}
//자바에는 Colloections.emptyList()가 있어 미리 정의된 읽기 전용 리스트를 반환한다.
public List<Employee> getEmployees() {
if(..직원이 없다면..)
return Colloections.emptyList();
}
//코드도 깔끔해질 뿐더러 NullPointerException이 발생할 가능성도 줄어든다.
08.null을 전달하지마라
정상적인 인수로 null을 기대하는 API가 아니라면 메서드로 null을 전달하는 코드는 최대한 피한다.
//두 지점 사이의 거리를 계산하는 간단한 메서드
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));
//NullPointerException발생
//->새로운 예외 유형을 만들어 던지는 방법으로 해결
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;
}
}
//하지만 InvalidArgumentException 예외를 처리기가 필요
//->assert 문을 사용하는 방법
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을 넘기지 못하도록 금지하는 정책이 가장 합리적
*/
생각이 없는 나는..평소 null반환과 전달을 난무하여 사용했는데
많이 찔리는 챕터였다^^..왜 그렇게 null처리가 힘들었는지
내가 멍청해서였다는걸 깨달았당
❤
Copyright 2022. GA-YOUN. All rights reserved
'memo' 카테고리의 다른 글
[clean_code] 08장.경계 (0) | 2022.07.15 |
---|---|
[clean_code] 06장.객체와 자료구조 (0) | 2022.07.05 |
sql 문제 모음 (0) | 2022.05.11 |
[엑셀]주간 업무일지 파일 공유 (0) | 2021.10.01 |
Visual Studio Code 설치하기(한국어 변경) (0) | 2020.03.12 |
댓글