클린코드 5장(객체와 자료구조), 6장 (오류처리)

JAVA/클린코드|2020. 6. 29. 11:25

 

객체와 자료구조


  • 객체에서 자료를 세세하게 공개하는 것 보다 추상화를 통해 표현하는 것이 더 좋다.
  • 객체는 동작을 공개하고 자료를 숨긴다.
  • 복잡한 시스템을 짜다보면 새로운 함수가 아니라 새로운 자료타입이 필요한 경우가 발생하는데 이 때는 클래스와 객체 지향 기법이 적합하다. 하지만 새로운 함수가 필요하다면 절차지향 코드와 자료구조 형태가 더 적합한 코드이다.

 

 

 

 

오류처리


 

  • 오류를 일일히 처리하는 것보다 차라리 예외를 던저버리는게 더 깔끔하다.
  • 확인된 예외를 처리하기 위해서 하위 메소드에서 throws를 하게되면 상위 메소드에서 이 예외에 대한 명시가 되어야 하기 때문에 하위의 예외 때문에 상위 메소드가 수정되어야 하는 불상사가 발생하기 때문에 수정에 닫혀 있어야 하다는 OCP규칙을 위반한 것이다.
  • 예외에 의미있는 내용을 함께 던져서 의미 파악에 도움이 되게 하라.
  • 에러가 발생하였을 때 null을 반환하는 코드는 일거리를 늘릴 뿐만 아니라 호출자에게 일감을 더 주게 되는 문제가 있다.
  • 에러를 모두 한 곳에서 처리하게 되면 아래와 같이 귀찮게 된다. 이를 매번 open()을 사용하는 곳에서 처리하게 되면 엄청나게 귀찮게 되고 의존성이 있어 하나의 오류가 늘어나게 되면 모든 사용하는 곳에서 처리를 해줘야 한다. 그래서 이를 별도의 클래스를 만들어서 클래스 내부에서 innerPort.open()을 실행시키게 해서 그 곳에서만 에러 내용을 처리해주고 정재된 Exception만 다시 던지게 하는 것도 방법이다.
// 문제
public void open() {
	try { 
		innerPort.open();
	} catch (DeviceResponseException e) {
		...
  } catch (NetworkErrorException e) { 
		...
  } catch (BindingException e) {
    ....
  } catch ....

} 


// 감싸기 클래스 사용
public class LocalPort {

  private ACMEPort innerPort;

  public void open() {
		try { 
			innerPort.open();
		} catch (DeviceResponseException e) {
			// 내부에서 약속된 하나의 에러 패턴을 사용.
			throw new PortDeviceFailuer(e);
	  } catch (NetworkErrorException e) { 
			throw new PortDeviceFailuer(e);
	  } catch (BindingException e) {
			throw new PortDeviceFailuer(e);
	  } catch ....
	  }
	}
}

 

 

 

 

결론

에러처리를 잘못하면 코드가 굉장히 더러워진다. 실제로 많이 경험해봤고 아직도 어렵다. 중요한건 내부에서 발생한 에러로 인해 상위에서 호출하는 함수의 코드가 변해야 하는 OCP 위반 사항을 발생시키지 않도록 하는 것 같다.

댓글()

Elasticsearch version conflict 에러

배치를 이용해서 Elasticsearch에 데이터를 삽입하던 중 version conflict라는 오류가 자주 발생했다. 처음에는 Elasticsearch 버전이 동일한데 왜? 오류가 나는지 몰랐다.

그래서 검색해보니 인덱스안에 document에는 각자 관리하는 version이 존재한다. 이 version은 document가 수정될 때 하나씩 올라가게 되는데 version이 10인 상태에 document에 여러 서버 모듈에서 해당 document에 업데이트를 하려고 하니 문제가 발생하였다.

그 이유는 version 10인 상태에서 작업에 들어간 두 모듈은 한 모듈이 먼저 11로 업데이트를 시키고 다음 모듈이 작업을 진행하려고 할 때 자기가 알고 있던 마지막 version인 10이 아니라 11로 바껴있는것을 보고 에러를 뱉어내는것이다. 이렇게 까지 세심하게 챙겨줄지 몰랐다. 알면 알수록 elasticsearch라는 db는 정말 매력적이다.

PUT wedul_index 
{
  "mappings": {
      "_doc": {
        "dynamic": "false",
        "properties": {
          "name": {
            "type": "text"
          }
        }
      }
  }
}

위와 같이 인덱스가 있고 document 하나가 들어있다. 여기에 age라는 값과 gender를 집어넣어보자. 이를 동시에 호출해보자.

document

그럼 document 하나에 필드를 동시에 업데이트하는 update.sh라는 스크립트를 만들어서 실행시켜보자.

curl -X POST "localhost:9200/wedul_index/_update_by_query" -H 'Content-Type: application/json' -d' { "script": { "source": "ctx._source[\u0027gender\u0027] = \u0027M\u0027"}, "query": { "match": { "name": "위들" } } } ‘
curl -X POST "localhost:9200/wedul_index/_update_by_query" -H 'Content-Type: application/json' -d' { "script": { "source": "ctx._source.age = 10", "lang": "painless" }, "query": { "match": { "name": "위들" } } } ‘

그럼 위에 설명했던 것 처럼 버전이 먼저 변경이 되면서 다음과 같은 에러를 뱉어낸다.

[{"index":"wedul_index","type":"_doc","id":"3MSd5WsB_jV9Cf9TkYLV","cause":{"type":"version_conflict_engine_exception","reason":"[_doc][3MSd5WsB_jV9Cf9TkYLV]: version conflict, current version [3] is different than the one provided [2]","index_uuid":"sJI8sBnrTP-OW8OG8YBqWA","shard":"3","index":"wedul_index"},"status":409}]

 

이를 해결하기 위해서는 retry_on_conflict 옵션을 함꼐 부여할 수 있는데 이 옵션은 version conflict이 발생했을 때, 업데이트 재시도를 몇회 할건지 지정하는 옵션이다.

좀 더 자세한 사항은 아래 elasticsearch 메뉴얼을 보면 자세히 나와있다.

참조
https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-update-by-query.html
https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-update.html

댓글()

lombok 사용시 Generating equals/hashCode implementation 에러 수정방법

web/Spring|2018. 6. 3. 21:01

lombok 사용할 때 다음과 같은 에러를 본적이 있을 것이다.


[에러내용]

Generating equals/hashCode implementation but without a call to superclass, even though this class does not extend java.lang.Object. If this is intentional, add '@EqualsAndHashCode(callSuper=false)' to your type.


이 에러는 상속을 받은 자식클래스에 발생하는 에러로서 다음과 같이 해결해줄 수 있다


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package com.wedul.wedulpos.user.dto;
 
import org.apache.ibatis.type.Alias;
 
import com.wedul.common.dto.CommonDto;
import com.wedul.common.util.HashUtil;
 
import lombok.Data;
import lombok.EqualsAndHashCode;
 
/**
 * User정보 Dto 
 * 
 * @author wedul
 * @date 2017. 11. 4.
 * @name UserDto
 */
@Alias("UserDto")
@Data
@EqualsAndHashCode(callSuper=false)
public class UserDto extends CommonDto {
    private String email;
    private String password;
    private boolean isadmin;
    
    public UserDto() {}
    
    public UserDto(String email) {
        this.email = email;
    }
    
    public UserDto(String email, String password) {
        this.email = email;
        this.password = password;
    }
    
    public UserDto(String email, String password, boolean isadmin) {
        this.email = email;
        this.password = password;
        this.isadmin = isadmin;
    }
    
    public String getEcPassword() {
        return HashUtil.sha256(this.password);
    }
    
}
 
cs




댓글()

규칙 58 복구가능 상태에는 점검지정 예외를 사용하고, 프로그래밍 오류에는 실행시점 예외를 이용하라.

JAVA/Effective Java|2018. 5. 29. 23:53

자바에는 몇 가지에 throwable을 제공한다.

점검지정 예외 (checked error)
컴파일 시점에 예외가 발생하는 부분으로 컴파일 시에 에러를 처리하는 코드를 삽입하지 않으면 컴파일이 되지 않는다.


1
2
3
4
5
6
7
8
9
10
public void ioOperation(boolean isResourceAvailable) {
  try {
    if (!isResourceAvailable) {
      throw new IOException();
    }
  } catch(IOException e) {
    // Handle caught exceptions.
  }
}
 
cs




unchecked error
컴파일 시점에 체크되지 않는 에러
실생시점 예외(runtime exception)와 오류(error)은 모두 unchecked error에 속한다.



실행시점 예외
NullPointerException, ArrayIndexOutOfBoundException와 같이 실행시간에 발생되는 에러



오류
OutofMemoryError ThreadDeath 같은 에러
catch 블럭으로 잡아도 대응 방법이 없다.




이런 예외들을 어떻게 기준을 잡고 처리해야 하는지 알아보자.

unchecked error의 기본적인 규칙은 caller 측에서 복구할것으로 여겨지는 상황에 대해서는 점검지점 예외를 사용해야한다.

-> 점검지점 예외를 사용한다는 것은 API 사용자에게 복구할 권한을 준다는 것이다. 그러므로 점검지점 예에서 발생한 에러는 catch에서 무조건 처리를 해야한다.


프로그래밍 오류를 표현할 때는 실행시점 예외를 사용하라.

-> 실행시점에 발생하는 대다수의 에러는 규칙위반으로 발생한다. 예를 들어 배열의 인덱스를 넘어가는 접근을 할때 발생하는 ArrayIndexOutOfBoundException이 대표적인 예이다.


예외 정보 문자열을 파싱해서 원하는 정보를 얻어서 처리하는 것은 굉장히 위험한 행동이다.

-> 예외정보는 각 버전 별로 변경될 수 있으며, 문자열에 경우 내용이 변하거나 없어질 수있다. 또한 정확한 예외에 대한 명세를 한 클래스 자체가 거의 없기 때문에 더욱더 위험하다.



결론을 말하면 복구 가능한경우에는 try-catch, throws를 사용하여 점검가능한 예외를 사용하고, 그것이 아닌 경우에는 실행시점 예외를 사용하라.

출처 : 조슈아 블로크, 『 Effective Java 2/E』, 이병준 옮김, 인사이트(2014.9.1), 규칙58 인용.

댓글()

npm 설치시 ENOSELF 오류 해결 방법

web/node.js|2018. 5. 27. 01:10

프로젝트 진행을 위해 필요한 라이브러리 설치를 위해 npm 명령어를 사용했는데 다음과 같은 오류가 발생하였다.

[명령어]
jeongcheol-ui-MacBook-Pro:gridstack jeongcheol$ npm install gridstack --save

[에러내용]
npm ERR! code ENOSELF
npm ERR! Refusing to install package with name "gridstack" under a package
npm ERR! also called "gridstack". Did you name your project the same
npm ERR! as the dependency you're installing?
npm ERR!
npm ERR! For more information, see:
npm ERR!     <https://docs.npmjs.com/cli/install#limitations-of-npms-install-algorithm>
 
npm ERR! A complete log of this run can be found in:
npm ERR!     /Users/jeongcheol/.npm/_logs/2018-05-25T14_26_24_708Z-debug.log


이 에러는 저 위에 내용을 읽어보면 알겠지만 현재 설치하려는 라이브러리와 프로젝트의 이름이 동일할 때 발생한다.

[조치방법]
1. 폴더명을 변경
2. package.json의 프로젝트 이름을 변경한다.

-> gridstack => gridstack_test




[결과화면]
변경 후에는 잘된다.







댓글()