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

https://github.com/skaengus2012/Ndroid


최근, 사내에서 개발하는 프로젝트에서 주로 다루게 되었던 요구사항 중 하나는 특정 범위에 대하여 등급을 부여하는 것이었습니다.


예를들어, [80.01~100 이면 "S", 70.01~80.00 이면 "A"] 이런 식으로 특정 구간에 대한 점수를 구합니다. 


이를 구현하는 문제는 생각보다 어려운 문제는 아니었지만, SWIFT 와 같은 최신언어에서는 범위에 관한 TYPE 을 제공하고 있는 것을 확인했던 차, JAVA 에도 이런 클래스가 있으면 좋겠다는 생각을 하게 되었습니다.


JAVA8 에서는 범위와 비슷한 문제에 대하여, [날짜 클래스 관련 Duration, Between 과 같은 클래스들이 배포]되었지만 아쉽게도 여러 형식을 호환하기 위한 구간 클래스는 아니었습니다.


그래서, N`s 오픈소스 프로젝트에서는 구간 관련 요구사항을 담을 수 있는 모듈을 제공하고자 하였습니다. 

해당 모듈의 주목적은 특정 Generic Type 에 대한 구간을 객체로써 가지는 것을 목적으로 하며, 이를 이용할 수 있는 추가 모듈을 제공하는 것입니다.


사용법은 아래와 같습니다.



1. 구간 객체 생성 및 활용.


구간을 생성하고, 특정 값이 구간에 포함되는가에 대한 표현입니다.


1
2
3
4
5
6
7
// 3 에서 7 사이의 구간 객체 생성.
Between<Integer> between3to7 = Between.Just(37);
 
System.out.println("3에서 7 사이, 4 는 구간에 포함되는가? -> " + between3to7.contains(4+ "\n");
 
// CONSOLE LOG
// 3에서 7 사이, 4 는 구간에 포함되는가? -> true
cs


객체 생성 방식은 RxJava 의 Maybe 와 비슷한 경험을 제공하고자 하였으며, 

기존 집합개념의 Set 의 Set::contains 와 개념적으로 비슷하기 때문에 같은 서명을 사용하고자 했습니다. 



2. 구간 객체 관리 Map


Between 객체를 관리할 수 있는 자료구조가 있다면, 꽤 멋진 활용을 할 수 있을 것이라 생각했었습니다. 

가장 먼저 생각한 것은 구간을 key 로 가지며, 구간에 포함되는 값을 매칭하면 해당 구간에 대한 value 를 얻을 수 있는 Map 을 구현하는 것이었습니다.


자료구조적으로 HashMap 은 아니지만, Map 과 비슷한 메소드 서명을 취할 수 있도록 Wrapping 하여 위의 요구사항을 충족하고자 했습니다. :-)


사용방법은 다음과 같습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
BetweenMap<Between<BigDecimal>String> betweenStringBetweenMap = new BetweenMap<>();
 
// 
betweenStringBetweenMap.put(Between.Just(BigDecimal.valueOf(0.0), BigDecimal.valueOf(5.0)), "A");
betweenStringBetweenMap.put(Between.Just(BigDecimal.valueOf(5.1), BigDecimal.valueOf(10.0)), "S");
betweenStringBetweenMap.put(Between.Just(BigDecimal.valueOf(6.1), BigDecimal.valueOf(13.0)), "S++");
 
// BigDecimal map getter.
// Return type -> Rx.Maybe
betweenStringBetweenMap.getFirst(BigDecimal.valueOf(3.87)).subscribe(grade -> System.out.println("Maybe Type 결과 출력 ([3.87] 에 대한 등급) : " + grade));
 
// List Type Return.
System.out.println("List Type 결과 출력 ([8.7] 에 대한 등급) : " + betweenStringBetweenMap.getToList(BigDecimal.valueOf(8.7)));
 
// CONSOLE LOG
// Maybe Type 결과 출력 ([3.87에 대한 등급) : A
// List Type 결과 출력 ([8.7] 에 대한 등급) : [S, S++]
cs



해당 모듈과 관련된 원본 주소는 아래에서 확인하실 수 있습니다.



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


반응형
Posted by N'

해당 포스팅에서 언급된 내용은 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'

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

https://github.com/skaengus2012/Ndroid


안녕하세요. 연휴는 잘 보내고 계신가요?


저는 4일간 연휴 중 조금 마음을 refresh 하고자, 하루는!! 

기존 작성하였던 코드를 리뷰해보고, 리팩토링을 하는 시간을 가지게 되었어요. 


그래서 오늘 포스팅은 이에 대한 결과물로 조금 더 구체적인 안드로이드 개발의 패턴화에 대해 생각해보는 포스팅을 해보려 합니다. (그냥 일개 초보수준 프로그래머가 작성한 것입니다. ㅡㅡ^)


일단 리팩토링 한다고 해서 아무렇게나 작성을 해보기 보단 지난 포스팅 때 다뤘던 두 가지 사례를 적극적으로 사용해보는 시간을 가졌습니다. 아래의 두 포스팅을 말이죠. :-)



작업을 했던 내용은 다음과 같습니다.


1. Activity에 조금 더 최적화된 EasyWeakReference


코드를 작성하다보면 종종 어떤 일들은 안드로이드의 메인스레드에서 실행되어야할 경우가 많이 존재합니다. 특히 병렬처리를 하려 한다면, 멀티스레드 작업의 결과를 View 에 반영해야하는데 이 작업은 꼭 메인스레드에서 이루어 져야 하는 작업이죠!!


안드로이드에서 이에 접근할 수 있는 방법은 여러 종류가 있지만 저는 그중에서 runOnUiThread 를 주로 애용하는 편입니다. 


이 개념을 저는 일전에 제작한 NxWeakReference 에 추가하고 싶었으며(보다 더 쉽게 일하기 위해 ㅡㅡ^), 이를 위해 저는 해당 클래스의 확장체를 만들게 되었습니다.


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
/**
 * Activity 전용 약한참조 객체
 *
 * Created by Doohyun on 2017. 1. 28..
 */
 
public class NxActivityWeakReference<extends Activity> extends NxWeakReference<T> {
    /**
     * 단순한 상속에 대한 생성자 제작
     * @param referent
     */
    public NxActivityWeakReference(T referent) {
        super(referent);
    }
 
    /**
     * 단순한 상속에 대한 생성자 제작
     *
     * @param referent
     * @param q
     */
    public NxActivityWeakReference(T referent, ReferenceQueue<T> q) {
        super(referent, q);
    }
 
    /**
     * Activity 에서 runOnUiThread 로 실행함
     *
     * @param weakReferenceConsumer
     */
    public void runOnUiThread(IWeakReferenceConsumer<T> weakReferenceConsumer) {
        final T activity = this.get();
 
        if (activity != null) {
            activity.runOnUiThread(() -> weakReferenceConsumer.accept(activity));
        }
    }
}
cs


간략하게 리뷰를 하자면, 생성자에서 Activity 를 상속받은 인스턴스를 강제로 입력하도록 제작하였으며, runOnUiThread 메소드를 통해 보다 더 쉽게 mainThread 에서 비지니스로직을 수행할 수 있습니다.



2. 안드로이드 MVP 규칙 정의


일전에 한번 JANDI 블로그(http://tosslab.github.io/android/2015/03/01/01.Android-mvc-mvvm-mvp.html)를 통해 protoType 화 했던 MVP 패턴에 대한 규칙을 조금 더 명세하였습니다. 규칙은 다음과 같습니다.


- View 에 해당하는 Activity는 오직 View 자체 변경 메소드만 작성할 것!


View 는 버튼 클릭 등에 의한 이벤트로 부터 해야할 비지니스 로직을 Presenter 로 위임합니다.


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
public class LoginActivity extends MitActivity {
 
    // 하위 View
    private RelativeLayout ryDialogBg;
    private LoginLoadingDialog loginLoadingDialog;
    private Button btnLogin;
 
    // Presenter
    private LoginPresenter loginPresenter;
    
    .....
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
 
        loginPresenter = new LoginPresenter(this);
       
        // 이벤트에 대한 비지니스 로직을 Presenter 로 위임! 
        btnLogin.setOnClickListener(loginPresenter::clickLoginButton);
    }
 
    /**     
     * View 자체의 변경 메소드
     *
     * 키보드 숨기기
     */    
    public void showLoginLoadingDialog() {
        loginLoadingDialog.show();
        ryDialogBg.setVisibility(View.VISIBLE);
    }
 
}
cs


- Presenter 는 View 와 Model 의 연결고리, 즉 기존의 Controller 


Presenter 는 View 로 부터의 요청 시, Modeler 로 명령을 위임합니다. 


또한 Modeler 로부터  받은 정보를 View 로 반영하기 위해, View 에 정의된 메소드를 직접 실행합니다. 이를 위해 Presenter 는 View 에 대한 약한참조를 가지고 있습니다.


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
public class LoginPresenter implements ILoginServiceObserver {
 
    private NxActivityWeakReference <LoginActivity> loginActivityEasyWeakReference;
 
    public LoginPresenter(LoginActivity loginActivity) {
  // Activity 와 약한참조 유지
        loginActivityEasyWeakReference = new NxActivityWeakReference <>(loginActivity);
    }
 
    /**
     * 로그인 시도
     * 
     * <pre>
     *  View 인 Activity에서 Click Event로 할 일을 위임한 메소드
     * </pre>
     * @param view
     */
    public void clickLoginButton(View view) {
       final UserInfo userInfo = getCurrentInputUserInfo();
 
       if (userInfo != null) {
          // View 변경 메소드 사용
          loginActivityMidasWeakReference.run(LoginActivity::showLoginLoadingDialog);
          // Modeler 로 실제 비지니스 로직 위임.
          LoginModeler.GetInstance().tryLogin(userInfo);  
       }
    }
}
cs


Presenter 는 다른 컴포넌트의 옵저버 역할을 수행합니다. 다른 컴포넌트의 알람의 결과(멀티스레드의 콜백결과 등..)로 View 가 변경되야 할 때, 이를 Presenter 로 전달을 하여 View 를 변경시킬 수 있도록 합니다.


- Model 을 담당하는 Modeler 개념 추가 


Modeler 는 스프링에 있던 Service 와 같은 역할을 수행합니다. (안드로이드에는 서비스가 있으니 서비스라는 이름은 차마 사용할 수 없었습니다. ㅜㅡㅜ)


Presenter 의 요청으로 내부DB 의 CRUD 를 수행하는 DAO 클래스를 사용하거나, network 작업을 수행합니다. 물론 요청이 콜백이었다면, 데이터를 전달하는 역할도 수행하겠죠?


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
public class LoginModeler {
    /**
     * 로그인을 시도한다
     *
     * <pre>
     *     userInfo 가 넘어오는 것에 성공했다면 정확한 정보를 보장함!
     *     유효성 검사 필요 없음
     * </pre>
     *
     * @param userInfo
     */
    public void tryLogin(@NonNull UserInfo userInfo) {
        // 실제 비지니스 로직 수행 (네트워크 접근)
        BindServiceManager.GetInstance().runRMI(iMessengerBinder -> iMessengerBinder.loginProcess(userInfo.getUserId(), userInfo.getPwd()));
    }
 
    /**
     * 유저 정보를 저장한다.
     *
     * <pre>
     *     userInfo 는 null 이 아닌 한 정확한 정보를 보장함!
     *     유효성 검사 필요 없음
     * </pre>
     * @param userInfo (null 허용)
     * @return
     */
    public boolean saveUserInfo(@Nullable UserInfo userInfo) {
        // 실제 비지니스 로직 수행 (DB CRUD)
        final boolean resultYn;
 
        if (userInfo != null) {
            resultYn = TxManager.GetInstance().runTransaction(() -> {
                UserInfoServiceManager.GetInstance().insert(userInfo.getUserId(), userInfo.getPwd());
                SettingServiceManager.GetInstance().insert();
            });
        } else {
            resultYn = false;
        }
 
        return resultYn;
    }
}
cs



계속해서 이 패턴화 를 견고하게 작업해 볼 생각입니다. 


보다 유연하고 질 좋은 코드를 꿈꾸며 말이죠. :-)


이상 설연휴 중 삽질이었습니다. ㅡㅡ^ 



반응형
Posted by N'

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

https://github.com/skaengus2012/Ndroid


안드로이드 개발을 진행하다보면 Activity 의 생명주기와 스레드 사용지점이 맞지 않아 누수가 되곤 합니다. 그러한 이유로 WeakReference 를 사용하란 이야기가 많이 나오곤 합니다.


보통 Activity 인스턴스를 약한참조 관계로 가지고 있습니다. 그리고 해당 인스턴스의 메소드를 사용하기 위해 우린 객체를 꺼내서 다음과 같이 사용합니다.


1
2
3
4
5
6
MyActivity activity = easyWeakReference.get();
        
if (activity != null) {
    // 메모리가 해제되어 더이상 존재하지 않는 객체인지 확인할 필요 존재!
    activity.func1();
}
cs


심플하게 사용할 수 있지만, 


프로그램의 덩치가 커지고 WeakReference 를 사용할 일이 많아진다면,


별 거 아닌 것 같은 저 null check 는 매우 귀찮고 중복작업일 수 있습니다.


이 문제는 아래의 WeakReference 의 상속체를 제작함으로 간단하게 해결할 수 있습니다. 


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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
public class NxWeakReference<T> extends WeakReference<T> {
 
    /**
     * 단순한 상속에 대한 생성자 제작
     * @param referent
     */
    public NxWeakReference(T referent) {
        super(referent);
    }
 
    /**
     * 단순한 상속에 대한 생성자 제작
     * @param referent
     * @param q
     */
    public NxWeakReference(T referent, ReferenceQueue<super T> q) {
        super(referent, q);
    }
 
    /**
     * 약한 참조 속 실제 객체의 사용
     * @param weakReferenceConsumer
     */
    public void run(IWeakReferenceConsumer<T> weakReferenceConsumer) {
        T t = this.get();
 
        if (t != null) {
            weakReferenceConsumer.accept(t);
        }
    }
 
    /**
     * 약한 참조 속 실제 객체의 사용
     *
     * <pre>
     *     call back 의 개념 사용
     * </pre>
     *
     * @param weakReferenceFunction
     */
    public Object call(IWeakReferenceFunction<T> weakReferenceFunction) {
        Object result = null;
 
        T t = this.get();
 
        if (t != null) {
            result = weakReferenceFunction.accept(t);
        }
 
        return result;
    }
 
    /**
     * 약한 참조 속 실제 객체의 사용
     *
     * <pre>
     *      call back 개념 사용
     *      defaultValue 사용
     * </pre>
     *
     * @param weakReferenceFunction
     * @param defaultValue
     * @return
     */
    public Object call(IWeakReferenceFunction<T> weakReferenceFunction, Object defaultValue) {
        final Object result = call(weakReferenceFunction);
 
        if (result == null) {
            return defaultValue;
        } else {
            return result;
        }
    }
 
    /**
     * WeakReference 의 내부객체를 사용하기 위한 Consumer 인터페이스
     * @param <T>
     */
    @FunctionalInterface
    public interface IWeakReferenceConsumer<T> {
        void accept(T t);
    }
 
    /**
     * WeakReference 의 내부객체를 사용하기 위한 Function 인터페이스
     * @param <T>
     */
    @FunctionalInterface
    public interface IWeakReferenceFunction<T> {
        Object accept(T t);
    }
}
 
cs


상속 후 새로 제작된 accept 는 위의 null 체크를 수행하도록 하며, 실제 비지니스 로직은 함수형 인터페이스로 동작파라미터화 시켰습니다. 


동작 파라미터화가 무엇인지 궁금하다면, 이곳으로 이동!!



이제 비지니스 로직은 다음과 같이 단순화하여 개발할 수 있습니다.


1
easyWeakReference.run(MyActivity::func1);
cs

함수형 프로그래밍을 조금만 알면 쉽게 작업은 단순화시킬 수 있습니다. 




반응형
Posted by N'