해당 포스팅에서 언급된 내용은 Ndroid 에서 제공합니다.

https://github.com/skaengus2012/Ndroid


매일 코딩을 하던 중 같은 것을 반복한다고 느끼는 경우가 있습니다.


그 중 하나가 아마 예외처리를 하는 것이라 생각합니다.


예외처리는 개발자가 의도적으로 어떤상황이 일어났을 때의 상황을 처리 못하니, 위의 stack 에서 알아서 하라고 던지는(throw) 행위입니다. (사실 이 행위는 프로그램코드가 아닌 실세계에서도 많이 일어납니다. ㅡㅡ^)


일단 예외처리에 대한 제 입장은 이러한 예외처리를 꼼꼼하게 계속해야 한다고 생각합니다. 


조금 회사에서 일을 해보니, 프로그램 작성 시에는 일부로 어떠한 경우들을 일부로 체크안해서 UnCheckException (꼭 처리를 안해도 되는 예외) 을 내보내도 되지 않을까(쉽게는 NullPointerException)생각하여 작성해보았는데 디버깅 시, 유지보수가 더 쉽지가 않음을 깨달았습니다. 


로직의 스탭(모듈 등)간의 관계에 대하여 강결함(?) 보장이 의도적으로 되야 하는데, 의도적으로 예외를 내지 않아버리면 갑자기 어디선가 죽어버리는 녀석을 계속 찾아야하는 것 같습니다. (같은 NullPointer 에 대한 예외를 처리한다 하더라도, 아무것도 안하고 Runtime 중 일으키는 것보다는 의도적으로 RuntimeException 이라도 넘기는 것이 좋다고 생각합니다. 메시지도 같이 말이죠....)


어쨌든 이러한 처리를 아마 아래와 같이 코드를 작성하곤 합니다.


1
2
3
if (a == null) {
    throw new MessageException("잘못된 접근입니다.");
}
cs


비지니스로직에 흔히 작성될 수 있는 코드입니다. 

간단해보이지만 아래와 같이 체크해야할 경우가 많다면 어떨까요?


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
if (a == null) {
    throw new MessageException("잘못된 접근입니다.");
}
 
if (list == null || list.isEmpty()) {
    throw new MessageException("잘못된 접근입니다.");
}
 
if (string == null || string.toString() == null || string.toString().isEmpty()) {
    throw new RuntimeException("잘못된 접근입니다.");
}
 
if (map == null || map.isEmpty()) {
    throw new MessageException("잘못된 접근입니다.");
}
 
if (!map.containsKey("a")){
   throw new RuntimeException("잘못된 접근입니다.");
}
cs


뭔가 꼼꼼하긴 하지만, if 블럭 안에 예외 한개씩 입력하고 있는 공통점이 있어보입니다. 이러한 단순 작업을 요즘은 툴(인텔리J)이나 어노테이션(NonNull)등으로 어느정도 커버할 수 있지만, 안드로이드나 웹프로젝트에서 같이 작업하는 입장에서 생각할 때는 호스트코드에 작성하는 방식을 아에 무시할 수는 없어보입니다. 

(잘 모르는 것일 수도 있습니다. ~ @.@ ~)


일단은 같이 일하는 선배님께서 이러한 부분을 간결하게 해보자는 것을 제안하셔서, 아래와 같은 코드를 한번 배포해 보았습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
 * 단순한 NullCheck 매크로
 *
 * @param object
 * @param message
 */
public void NullCheck(Object object, String message) throws MessageException {
   if (object == null) {
      throw new MessageException(message);
   }
}
 
 
NullCheck(vo, "잘못된 접근입니다.");
cs


MessageException은 CheckedException 종류로 컨트롤러로 이 예외가 던져진다면, 프론트에서 사용자가 메시지를 볼 수 있도록 합니다.


이 코드로 인하여, 예외를 처리하는 것은 매우 단순하게 간결 해졌습니다. 


복잡한 방식을 간결하게 바꾸는 시도는 여러가지로 시도 되고 있고(인터페이스를 람다로 치환한다는 등..), 이러한 방식을 제안한 선배님께는 감사하고 있습니다.


단순하게 Null 뿐 아니라, string 빈 값, Container 의 빈 상태 등 자주 사용하는 여러 상황을 체크하는 메소드를 만들었고, 잘 사용하고 있었지만 이 코드에는 문제가 있음을 깨닫게 되었습니다.


간결 예외처리 코드는 아쉽게도 MessageException 밖에 출력을 못합니다. 

경우에 따라서는 RuntimeException 을 내보내야할 때도 있으며, 안드로이드와 라이브러리를 공유한다고 했을 때 웹에서만 사용하는 MessageException 을 안고 갈 수는 없었습니다. 


이를 해결하기 위해 예외의 형태를 제네릭으로 받아 동적으로 예외를 만들자는 생각이 들었습니다. 아래와 같은 초안 메소드를 작성하게 되었고, 오버로딩으로 간략하게 사용할 수 있는 메소드도 같이 제공하였습니다.


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
/**
 * Check with CustomException.
 *
 * @param check
 * @param message
 * @param exceptionClass
 * @param <T>
 * @throws T
 */
public final static <extends Exception> void Check(
            @NonNull Boolean check
            , @NonNull String message
            , @NonNull Class<T> exceptionClass) throws T{
    if (!check) {
        final T exception;
 
        try {
            exception = exceptionClass.getConstructor(String.class).newInstance(message);
        } catch (Exception e) {
            throw new RuntimeException(e);
         }
 
        throw exception;
    }
}
 
/**
 * Check.
 *
 * @param check
 */
public final static void Check(@NonNull Boolean check) {
    Check(check, ERROR_BAD_ASSESS, RuntimeException.class);
}
 
 
Check(object != null"[에러] object 는 Null 일 수 없음", RuntimeException.class);
Check(object != null);
cs


이러한 형태로 자주 사용하는 예외체크 타입을 간략화한 유틸클래스를 릴리즈하였으며, 사용은 아래와 같습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Check
CheckUtil.Check(object != null"Occurred Error! Please ask administrator!", RuntimeException.class);
 
// Null check
CheckUtil.NullCheck(object, "Occurred Error! Please ask administrator!", MessageException.class);
 
// String empty check.
CheckUtil.EmptyToStringCheck(object);
 
// Maybe Empty check.
CheckUtil.EmptyMaybeCheck(MaybeUtil.JustNullable(object));
 
// Container Check.
CheckUtil.EmptyContainerCheck(Collections.emptyList());   // Collection
CheckUtil.EmptyContainerCheck("Test""Test2");           // Array
CheckUtil.EmptyContainerCheck(Collections.emptyMap());    // map
cs


RxJava2 에서 제공하는 Maybe 개념까지 CheckUtil 에 넣게 되었습니다.


보다 자세한 내용은 아래 url 에서 확인하실 수 있습니다.



이 포스팅이 보다 간결한 코딩을 하는 것에 도움이 되길 바래요 ~ @.@ ~

반응형
Posted by N'

해당 포스팅에서 언급된 내용은 Ndroid 에서 제공합니다.

https://github.com/skaengus2012/Ndroid


Java 에서 시간 관련 클래스 (Calendar, Date) 들은 꽤 많이 사용됩니다. 


하지만 언제나 사용할 때마다 헷갈리는 부분이 존재하며, 간단한 기능도 직관적이지 않은 패턴을 사용해야합니다. (예를들면, Calendar 에서 달을 출력하려면? 현재 년도에 특정 값을 더하고 싶다면?)


1
2
3
4
5
Calendar calendar = Calendar.getInstance();
        
calendar.get(Calendar.MONTH); // 출력되는 달은 0-11 입니다. (1-12 가 안나온다는 것을 기억해야하죠.
        
calendar.add(Calendar.YEAR, 2); // 년도를 더하고 싶습니다. 덧셈을 하고 싶다면, Calendar 의 상수를 이용해야하죠.
cs


제 생각에 직관적이지 못하다고 생각하는 이유는 메소드명을 보고 행위를 하는 것이 아닌, 상수를 넣고 처리를 해야하기 때문입니다. 물론 Calendar 클래스에 익숙하다면 능숙하게 사용하겠지만, 날짜 연산에 대해 조금만 복잡하게 계산을 한다고 하면 저 상수를 이용한 연산을 줄줄이 입력해야할 수도 있습니다. 


Ndroid 프로젝트에서는 조금 더 이를 편하게 사용하고 싶었습니다. 


Calendar 의 사용목적이 결국 날짜 데이터를 핸들링하는 것이고, 일련의 연산과정 중 최종으로 핸들링된 날짜만 알고 싶습니다. 이를 마치 질의를 하는 것과 같은 선언형식으로 할 수 있다면 매우 코드가 아름다워지지 않을까라는 기대가 있었습니다.


Ndroid 의 TimeBuilder 모듈은 위와 같은 요구사항을 담도록 하였습니다. 


예를 들어 이러한 요구사항이 있다고 합시다.


- String 으로 된 시간을 파싱할 것.

- 파싱한 날짜에 년도를 1 덧셈

- 파싱한 날짜에 달을 5 덧셈.

- 파싱한 날짜에 일을 1 뺄셈

- 시,분,초는 생략할 것.

- 날짜는 영어권으로 보여줄 것.


기존 자바 코드로 작성을 한다면, 이 정도 되겠네요.


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
// simple java code.
 String dateString = "2017-3-26 16:40";
 
try {
    // parse.
    Date formatData = new SimpleDateFormat("yyyy-MM-dd hh:mm").parse(dateString);
 
    // calculating
    Calendar calendar = Calendar.getInstance();
    calendar.setTime(formatData);
    calendar.add(Calendar.YEAR, 1);
    calendar.add(Calendar.MONTH, 5);
    calendar.add(Calendar.DAY_OF_MONTH, -1);
 
    // to yyMMdd
    calendar.set(Calendar.HOUR_OF_DAY, 0);
    calendar.set(Calendar.MINUTE, 0);
    calendar.set(Calendar.SECOND, 0);
    calendar.set(Calendar.MILLISECOND, 0);
 
    System.out.println(new SimpleDateFormat("yyyy.MM.dd (hh,mm,ss a)", Locale.ENGLISH).format(calendar.getTime()));
 
catch (ParseException e) {
    e.printStackTrace();
}
cs


간단하지는 않네요. 사용하려면 상수의 기능부터 알아야하고 (DAY_OF_MONTH, HOUR_OF_DAY 등은 한 번 찾아봐야겠죠 .ㅡㅡ^), SimpleDateFormat 클래스의 기능도 알아야합니다.


위의 기능을 TimeBuilder 를 통해 작업해보도록 하겠습니다.


1
2
3
4
5
6
7
8
TimeBuilder.Create(dateString, "yyyy-MM-dd hh:mm").
                addYear(1).
                addMonth(5).
                addDay(-1).
                setLocale(Locale.ENGLISH).
                to_yyMMdd().
                getStringFormat("yyyy.MM.dd (hh,mm,ss a)").
                subscribe(System.out::println);
cs


Builder 클래스에서 제공하는 메소드를 사용함으로써, 상수들을 알 필요가 없어졌으며 파싱 및 포맷을 위해서 사용할 SimpleDateFormat 역시 몰라도 됩니다.


파이프라인 메소드 중 getStringFormat 의 출력 타입은 RxJava 의 Maybe 입니다. 

포맷의 형식이 잘못되어 포맷에 실패할 수도 있으며, 이런 결과를 이전 MaybeUtil 에서 제공하던 기능들과 같이 값이 있거나 없거나의 문제로 보았습니다.


MaybeUtil 의 기능은 아래에서 참고 



TimeBuilder 의 인스턴스를 만드는 방법은 아래와 같이 다양합니다.


1
2
3
4
5
6
7
8
9
10
11
 // non-param : current time.
TimeBuilder currentTimeBuilder = TimeBuilder.Create();
 
// param : Calendar.
TimeBuilder calendarBuilder = TimeBuilder.Create(TimeUtil.GetCalendar());
 
// param : date
TimeBuilder dateBuilder = TimeBuilder.Create(new Date());
 
// param : string, format
TimeBuilder stringBuilder = TimeBuilder.Create("2017-3-26""yyyy-MM-dd");
cs


보다 자세한 내용은 아래 url 에서 확인하실 수 있습니다.



이 기능을 제작하게 된 첫 번째 배경은 JAVA8 에서 Date 관련 API 의 개편이 있던 것으로 알았고 (아직 공부해보지는 않았습니다. 아마 다음 올릴 포스팅이 해당 부분일 것입니다.), 안드로이드 진영에서는 아직 JAVA8 을 온전히 지원하지 않기 때문에 추가하고자 하는 배경이 되었습니다.


JAVA8 의 기능을 보고 TimeBuilder 의 기능이 더 추가될 여지가 있길 바랍니다. :-)


반응형
Posted by N'

해당 포스팅에서 언급된 내용은 Ndroid 에서 제공합니다.

https://github.com/skaengus2012/Ndroid


기존 JAVA 에서는 변수에 값의 존재 여부를 명확하게 표현할 수 없었기 때문에 Null 검사를 해줬어야 했으며, 그럼에도 불구하고 NullPointerException 을 발생시키지 않기가 어려웠습니다.


그러나 JAVA8 에서는 Optional 이라는 Null 을 피하고자하는 새로운 개념이 생겼으며, 이 개념을 통해 어느정도 위의 문제를 해결하거나 비지니스 로직을 더 간소화 시킬 수 있었습니다. 


자세한 내용은 아래 포스팅을 참고! 



그러나, 아쉽게도 안드로이드에서는 Optional 을 API>=24 에서 사용할 수 있습니다.

하지만 언제나 JAVA8 을 대체하는 듯한 라이브러리가 있으니, RxJava 진영에서는 Maybe 를 지원합니다. Maybe 라는 이름이 생소해보일 수 있지만, 다른 언어에서는 Optional 대신 Maybe 라고 표기하는 곳도 있습니다.


Maybe 의 튜토리얼은 아래와 같습니다.


1
2
3
4
5
6
7
8
9
10
// 빈 Maybe 생성.
Maybe<String> emptyMaybe = Maybe.empty();
// 유효 값 Maybe 생성.
Maybe<String> validMaybe = Maybe.just("Test1");
 
// 값이 유효할 때 로그 출력.
validMaybe.subscribe(System.out::println);
 
// 실제 데이터 출력.
String returnValue = validMaybe.blockingGet("Default Value");
cs


그러나 아직 RxJava2 의 버전이 높지 않아, 편의 메소드들이 많이 추가되지 않은 것으로 보입니다. 

그래서 Ndroid 프로젝트에서는 Maybe 를 조금 더 적극적으로 사용해보자는 취지로 MaybeUtil 을 제작하였습니다. 


편의의 기준은 현재 공부하고 있는 [JAVA8 in Action] 의 실무에서 많이 사용하는 예를 참고하여 작성하였습니다.


1. Null 을 포함하는 Just


Maybe 의 사용목적 자체가 값의 존재여부를 확신할 수 없기 때문에 사용한다고 볼 수 있지만, 정작 Maybe 를 생성하기 위해서는 null check 를 수행해야합니다. ㅡㅡ^


Maybe 에 있는 just 는 Null 을 포함할 수 없습니다.


MaybeUtil 에서는 이 이슈 해결을 위해 아래와 같은 튜토리얼을 제공합니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
String test = "Test", nullValue = null;
 
// Null 을 포함할 수 있는 just!
MaybeUtil.JustNullable(test).subscribe(System.out::println);
 
Maybe<String> nullValueMaybe = MaybeUtil.JustNullable(nullValue);
nullValueMaybe.subscribe(System.out::println);
        
// 오직 비어있는 Maybe 일 때만 실행!
MaybeUtil.SubscribeEmpty(nullValueMaybe, () -> System.out.println("That value is null!!"));
 
// Maybe 가 유효하다면, 두번째 param, 아니라면 세번째 param 사용
MaybeUtil.Subscribe(nullValueMaybe, System.out::println, () -> System.out.println("That value is null!!"));
cs


2. Map 의 안정적 사용을 위한 Maybe 지원.


Map 에 데이터를 삽입하고, 이를 사용하기 위해서는 containsKey 메소드를 통해, Map 에 실제 데이터가 존재하는지 확인하여야 합니다. 이 문제 역시도 Maybe 의 철학과 같이 값의 존재여부 문제를 따져볼 수 있습니다.


아래의 튜토리얼을 참고하여, Map 사용에 대한 비지니스 로직을 줄여볼 수 있습니다.


1
2
3
4
5
6
7
8
9
10
11
HashMap<String, Integer> testMap = new HashMap<>();
testMap.put("key1"1);
testMap.put("key2"1);
testMap.put("key3"1);
 
// 맵에서 데이터 출력 시, Maybe 형태로 반환
ContainerUtil.JustInMap(testMap, "key1").subscribe(System.out::println);
 
// 맵에 데이터가 존재할 때 해야할 Consumer 를 정의할 수 있음.
ContainerUtil.RunMaybeInMap(testMap, "key1"System.out::println);
ContainerUtil.RunMaybeInMap(testMap, "key4"System.out::println);
cs



3. Box Object 의 parse 를 Maybe 지원.


String 클래스를 Integer 나 Double 등, Boxed Object 로 변환하는 parseXXX 메소드는 언제나 XXXFormatException 을 발생시킬 우려가 있습니다. 이 문제 역시도 String 을 각 Boxed Object 으로 변환하여 값의 존재를 따질 수 있냐는 문제로 볼 수 있습니다.


아래 튜토리얼은 안전한 변환을 지원합니다.


1
2
3
4
5
6
7
8
9
// Parse Integer
StringUtil.ParseInteger("1").subscribe(System.out::println);
StringUtil.ParseInteger("String Text").subscribe(System.out::println);
 
// Parse Boolean
StringUtil.ParseBoolean("false").subscribe(System.out::println);
 
// Parse Double
StringUtil.ParseDouble("0.0111111").subscribe(System.out::println);
cs


 2,3 번의 경우 MaybeUtil 에 모든 기능을 추가할 수 있었으나, 


MaybeUtil 의 추 후 확장성


이미 Ndroid 프로젝트에 존재하는 StringUtil 과 ContainerUtil 의 성격과 더 맞다는 점


에서 기능을 나누게 되었습니다.


자세한 내용은 Ndroid ReadMe 에서 확인하실 수 있습니다.

[https://github.com/skaengus2012/N-java/wiki/N-java-v0.2-wiki#maybeutil]


Maybe 를 통해 우리는 안드로이드의 비지니스 로직을 단순화시킬 수 있을 것이라 생각합니다.

Java8 의 새 라이브러리와 RxJava 는 거의 비슷하며, 어느곳에서도 익숙한 개발자가 되는 것은 조금의 노력으로 할 수 있을 것이라 생각합니다.


여러분들의 코드가 새 패러다임을 입고, 뷰티풀해지길 바랍니다. :-)




반응형
Posted by N'

해당 포스팅에서 언급된 내용은 Ndroid 에서 제공합니다.

https://github.com/skaengus2012/Ndroid


RxJava2 와 더불어 안드로이드에서 더욱 Functional Programming 을 할 수 방법이 늘어났습니다. 

점점 JAVA8 에서 언급된 내용들이 지원이 되고 있죠.


특히 RxJava 진영에서 JAVA8 의 기본 함수형 인터페이스를 제공해주기 때문에, 더욱 개발은 편해질 수 있을 것이라 생각합니다. (없었다면 람다를 인스턴스로 가지고 있기 위해, 함수형인터페이스를 만들어주었어야 할 것입니다.)


하지만 RxJava 의 함수형 인터페이스를 사용하는데 있어서 불편한 점이 두 가지가 있었습니다.


1. 함수형 인터페이스의 함수들이 모두 throws Exception


RxJava 에서 지원하는 함수형 인터페이스들은 모두 예외를 필수적으로 출력하도록 되어 있습니다. 기존 JAVA8 에서는 그렇지 않았으며,  RxJava 의 Observerable 이 더이상 NULL 을 허용하지 않겠다는 정책이 만들어짐에 따라 생긴 특성일 것이라 생각합니다.


물론 어떤면에서는 좋겠지만 이 행위는 람다를 쓸 때마다 Exception 에 대한 처리를 해야함을 말합니다. Exception 이 생길 일이 없는데 무조건 처리해야한다는 것은 안타까운 일입니다.


1
2
3
4
5
6
7
8
9
10
11
@FunctionalInterface
public interface BiFunction<T, U, R> {
    R apply(T t, U u) throws Exception
}
 
try {
      BiFunction<Integer, Integer, Integer> biFunction = (Integer a, Integer b) -> a + b;
     biFunction.apply(25);
catch (Exception e) {
    // Exception Required. ㅡㅡ^
}
cs


Ndroid 이를 위해 JAVA8 에서 지원하는 기본 함수형 인터페이스를 다시 만들었습니다. 


Ndroid 에서 지원하는 기본형들은 모두 인터페이스임을 명시하기 위해 Ixxxx 형식을 사용합니다.

(ex. IPredicate, IFunction)


상황에 따라 RxJava 의 람다와 섞어 사용할 수 있을 것이라 생각합니다.


1
2
3
4
5
6
7
8
9
@FunctionalInterface
public interface IBiFunction<T, U, R> {
    R apply(T t, U u) throws Exception
}
 
// Too simple. None Exception
IBiFunction<Integer, Integer, Integer> biFunction = (Integer a, Integer b) -> a + b;
biFunction.apply(25);
 
cs



2. 람다 조합 불가 


람다를 조합할 수 있다는 것은 매우 편리한 기능입니다. 


각종 조건들에 대해서, 더이상 논리연산자를 복잡하게 추가하지 않아도 되며, 이는 한결 유지보수에 도움이 될 수 있습니다.



그러나 JAVA8 의 디폴트, 정적 메소드들이 안드로이드 24버전부터 지원함에 따라 해당 기능을 사용할 수 없었습니다. 


RxJava 역시 해당 기능을 지원하지 않기 때문에 조합을 할 수 없는 방법이 없을까 고민하던 찰나, 해당 기능을 지원하는 Builder 를 만들기로 했습니다.


Ndroid 의 LambdaUtil 은 람다에 대한 조합을 할 수 있으며, Java8 에서 사용가능한 모든 기능을 지원합니다.


1
2
3
4
IPredicate<Integer> predicate = LambdaUtil.PredicateBuilder((Integer a) -> a >= 5).
                                    and(a -> a < 10).
                                    or(a -> a == 0).
                                    get();
cs


LambdaUtil 은 기본적으로 Ndroid 에 정의된 기본형 인터페이스들을 지원합니다. 


하지만 조합된 람다는 RxJava 의 Observable 과도 호환되야 하기 때문에, Rx 스타일의 람다 역시 지원하도록 하였습니다.


1
2
3
4
5
6
7
// a % 5 == 0 && a > 40 || a < 20
Observable.range(060).filter(
                LambdaUtil.PredicateBuilder((Integer a) -> a % 5 == 0).
                        and(a -> a > 40).
                        or(a -> a < 20).
                        getRx()).
                subscribe(System.out::println);
cs


자세한 내용은 Ndroid 의 ReadMe 에서 확인할 수 있습니다.

[https://github.com/skaengus2012/N-java/wiki/N-java-v0.2-wiki#lambda-combination]


비록 안드로이드에서 JAVA8 의 모든 기능을 사용할 수 없지만, 조금씩 그 격차는 줄어들고 있습니다. 

우리의 코드는 계속해서 자리를 잡을 것입니다. 언제나 그랬던 것 처럼 말이죠. :-)


반응형
Posted by N'