9월에 Java9이 공식 배포되었군요;;; 9이라서 9월에 했나;;


Jigsaw Project가 SDK를 모듈별로 관리해서 rt.jar의 용량을 줄이는 것 같은데, 대충 봐서 정확히 확인해봐야 겠네요;;;


https://docs.oracle.com/javase/9/whatsnew/toc.htm#JSNEW-GUID-C23AFD78-C777-460B-8ACE-58BE5EA681F6

저에게 눈에 띄는 주요한 변화는 아래와 같습니다.

- JEP 248: Make G1 the Default Garbage Collector 

- JEP 291: Deprecate the Concurrent Mark Sweep (CMS) Garbage Collector
- JEP 254: Compact Strings

Adopts a more space-efficient internal representation for strings. Previously, the String class stored characters in a char array, using two bytes (16 bits) for each character. The new internal representation of the String class is a byte array plus an encoding-flag field.

- JEP 269: Convenience Factory Methods for Collections

Makes it easier to create instances of collections and maps with small numbers of elements. New static factory methods on the List, Set, and Map interfaces make it simpler to create immutable instances of those collections.

For example:

Set<String> alphabet = Set.of("a", "b", "c");

- JEP 259: Stack-Walking API

Provides a stack-walking API that allows easy filtering and lazy access to the information in stack traces. 



https://docs.oracle.com/javase/specs/index.html

jvm spec for 9 도 올라 왔군요. 나중에 또 한번 훑어봐야 겠습니다;;

Posted by 제이제이랩
,

2016/03/15 - [java] - benchmarking 코드를 작성해 보자 #1

2016/06/06 - [java] - Dynamic Proxy를 꼭 써야해? #1


이 글에서는 Dynamic Proxy를 꼭 써야하나? 라는 고민을 해봅시다. 


안드로이드 대표적인 HTTP 라이브러리를 꼽자면 Retrofit2, Volley, Okhttp3 입니다. Retrofit2는 안드로이드 개발자들이 HTTP 통신을 쉽게 사용하도록 Okhttp3 기반의 상위 레이어에 불과합니다. Volley는 구글에서 만들었는데, 2013년 어떤 분이 마이크로 벤치마킹 한 결과가 별로 좋지 않아서(참조 http://instructure.github.io/blog/2013/12/09/volley-vs-retrofit/) 외국에서는 거의 Retrofit을 주로 사용하는 것으로 알고 있습니다.(오픈소스로 된 샘플앱들이 HTTP 라이브러리를 Retrofit으로 된걸로 보면요.) 티몬, 쿠팡, 위메프, 11번가등 apk를 까보면 Volley를 쓰고 있습니다. Volley의 HurlStack을 Okhttp로 사용하게 하면 그나마 성능 이슈는 해결되긴 합니다만, 굳이 사용성 측면에서나 okhttp3나 Retrofit2가 성능이나 메모리 사용에서 더 효율적이기 때문에 새로운 앱을 만들때 Volley를 선택할 필요는 없을 것입니다.


결국엔 서비스앱을 만들 때 Retrofit2를 사용할지 Okhttp3를 사용할지가 문제입니다. 앱의 가독성 측면을 보면 Retrofit2가 더 좋아 보이는데, 한가지 걸리는 것이 있습니다. 그건 바로 Dynamic Proxy를 내부적으로 런타임시 사용하고 있다는 것입니다. 개발자가 선언한 Interface를 런타임에 읽어서 그 인터페이스에서 선언한 메소드 및 annotation 등을 Reflection을 사용해서 처리하여 Proxy class를 만들어 그 구현체를 개발자가 사용하는 방식입니다. 실제로 서버에 HTTP 요청을 보내고 응답을 받는 작업은 Okhttp3가 하고있는 상황입니다. 이 사실만으로도 Reflection의 오버헤드를 피해서 조그이나마 성능 이점을 얻기 위해서는 개발자가 Retrofit2를 피하고 Okhttp3를 직접 사용하는 것이 나을것이라는 막연한 생각이 들수 있습니다. 그러면 어느 정도 성능 이점이 있을지 살펴 보도록 하겠습니다.


JMH를 이용해서 Retrofit2, Okhttp3를 이용하여 각각 동일한 GET 요청을 보내는 코드를 작성하도록 합니다. 동일한 요청을 보내는데 각 라이브러리가 얼마나 시간이 걸리는지 비교하도록 합니다. Benchmarking 코드와 결과는 이 Github[https://github.com/jayjaylab-benchmark/retrofitvsokhttp] repository에 저장하였으니 확인하세요.


하기는 벤치마킹코드입니다. 혹시나 보시고 제가 실수한 사항이 있으면 지적해 주시면 감사하겠습니다. 서버에 GET 요청을 보낸 후 응답으로 오는 json 스트링을 POJO 객체로 unmarshalling하는 지점까지를 성능 측정하는 범위로 정하였습니다. 본 벤치마킹을 실행한 환경은 MAC OS X yosemite version 10.10.5(14F27), 2.7GHz Intel core i5, 8GB 1867MHz DD3이고 JDK 1.8.0_45, VM 25.45-b02 자바환경에서 JVM warm up없이 cold start인 상황에서 측정하였습니다. JMH에서 코드 warm up한 상태에서 벤치마킹할 각 메소드를 각 iteration당 1번만 실행하는 옵션은 없어서 부득히하게 cold start인 경우에만 측정하게 되었습니다. 안드로이드의 경우 client mode로 dalvik이나 ART가 실행될 것으로 추측이 되고 warm up 과정은 없으리라 추측이 되는데, AOSP에서 런타임 까봐야 알겠죠?ㅋ...

public class AndroidHttpLibraryBenchmark {
    @org.openjdk.jmh.annotations.State(Scope.Benchmark)
    public static class State {
        public Retrofit retrofit;
        public OkHttpClient okHttpClient;
        public ObjectMapper objectMapper;

        @Setup(Level.Trial)
        public void setUp() {
            System.out.println("########## setUp() ##########");
            retrofit = new Retrofit.Builder()
                    .baseUrl("https://api.github.com/")
                    .addConverterFactory(JacksonConverterFactory.create())
                    .build();
            okHttpClient = new OkHttpClient();
            objectMapper = new ObjectMapper();
        }

        @TearDown(Level.Trial)
        public void tearDown() {
            System.out.println("########## tearDown() ##########");
            retrofit = null;
            objectMapper = null;
            okHttpClient = null;
        }
    }

    @Benchmark
    @BenchmarkMode({Mode.SingleShotTime})
    @OutputTimeUnit(TimeUnit.MILLISECONDS)
    public void testRetrofit2(State state, Blackhole blackhole) {
        GitHubService service = state.retrofit.create(GitHubService.class);
        Call<List<Repo>> repos = service.listRepos("octocat");
        List<Repo> result = null;

        try {
            retrofit2.Response<List<Repo>> response = repos.execute();
            result = response.body();
        } catch(Exception e) {
            e.printStackTrace();
        }

        blackhole.consume(result);
    }

    @Benchmark
    @BenchmarkMode({Mode.SingleShotTime})
    @OutputTimeUnit(TimeUnit.MILLISECONDS)
    public void testOkhttp3(State state, Blackhole blackhole) {
        HttpUrl url = new HttpUrl.Builder()
                .scheme("https")
                .host("api.github.com")
                .addPathSegment("users")
                .addPathSegment("octocat")
                .addPathSegment("repos")
                .build();

        Request request = new Request.Builder()
                .url(url)
                .build();
        List<Repo> result = null;

        try {
            Response response = state.okHttpClient.newCall(request).execute();
            result = state.objectMapper.readValue(response.body().charStream(),
                    new TypeReference<List<Repo>>(){});
        } catch (Exception e) {
            e.printStackTrace();
        }

        blackhole.consume(result);
    }
}


우선 성능만 보자면... 어떤걸 택하시겠나요? Retrofit2, Okhttp3를 사용하는 메소드를 10번씩 돌린 결과입니다. 네트워크 상황이 벤치마킹 돌릴때마다 일정하고 안정된 상황이라는 가정하에 아래 결과를 보면 Okhttp3를 사용하는게 성능상 이점이 있다고 볼 수 있습니다. 특히나 앱 시작할 때 몇개의 Restful API를 사용하는 경우라면 XXXms의 이점이 있을것으로 보입니다. reddit androiddev subreddit에 이 결과를 올려서 공유를 했습니다.(참고 : https://www.reddit.com/r/androiddev/comments/4rpdlv/retrofit2_vs_okhttp3/).

MicroBenchmark Result #1(2016-07-07T22:41:40+09:00)

Benchmark                                  Mode  Cnt     Score     Error  Units
AndroidHttpLibraryBenchmark.testOkhttp3      ss   10  2389.107 ± 550.925  ms/op
AndroidHttpLibraryBenchmark.testRetrofit2    ss   10  2476.903 ± 327.647  ms/op
MicroBenchmark Result #2(2016-07-08T00:47:12+09:00)

Benchmark                                  Mode  Cnt     Score     Error  Units
AndroidHttpLibraryBenchmark.testOkhttp3      ss   10  2472.586 ± 245.455  ms/op
AndroidHttpLibraryBenchmark.testRetrofit2    ss   10  2920.377 ± 760.955  ms/op
MicroBenchmark Result #3(2016-07-08T12:39:23+09:00)

Benchmark                                  Mode  Cnt     Score      Error  Units
AndroidHttpLibraryBenchmark.testOkhttp3      ss   10  2427.628 ±  556.801  ms/op
AndroidHttpLibraryBenchmark.testRetrofit2    ss   10  3110.812 ± 1659.656  ms/op

Jake wharton이 proxy class instance를 State class에서 만들어 써야 한다는 댓글을 남겼습니다. 그러면 결과는 좀 달라지겠지요. 그렇다면 Okhttp3의 Request instance 또한 State class에서 만들어서 써야하는데 그렇게 되면 제 예상으로는 수치상 차이는 없어질 가능성이 많아 보입니다. 제가 지적하고자 하는 점은 dynamic proxy 사용이 염려되어 proxy class의 인스턴스를 얻는 코드를 벤치마킹에 포함하였고 그와 동일한 행위를 하는 Okhttp3 코드 또한 벤치마킹 코드에 포함하였습니다. Retrofit2는 한번 만들어진 인스턴스를 내부적으로 캐쉬하는 것으로 보이고 Okhttp3에서 사용하는 Request 인스턴스는 개발자가 직접 캐쉬하도록 코드를 작성해야 하는 번거러움이 있습니다. 저에게는 직접 캐쉬하는 코드 작성이 별로 어렵지 않은 터라 다른 경쟁사 앱들과 조금이나마 차이를 만들고자 Okhttp3 선택하고 싶네요. 


마치기에 앞서 이 벤치마킹 수치에 지대한 영향을 미치는 통신 overhead에 관해 언급을 해야겠습니다. Retrofit2의 내부에서 Okhttp3를 사용하기에 HTTP 통신 로직의 오버헤드는 둘 다 동일할 것이고 나머지 Retrofit2 자체의 오버헤드와 네트워크 상태(?)가 벤치마크 수치를 다르게 만들 수 있습니다. 네트워크 상태가 각 벤치마크를 돌릴때마다 다르다고 해도 위 수치는 OkHttp3가 좀더 좋은 결과를 나타내고 있다고 말할 수 있는 수준이라고 보입니다. 또한 Retrofit2, Okhttp3는 안드로이드용 이기 때문에 Hotspot JVM에서 벤치마킹을 하는 JMH에서의 결과를 신뢰할 수 있을지 의문이 들긴 합니다. 그런데 Dalvik이나 ART에서 동일한 벤치마킹을 수행한다고 할 때 결과 양상이 바뀔까? 라는 질문을 했을 때, 글쎄요 수치는 바뀔 수 있을지언정 위 JMH에서 나온 결과 패턴은 바뀔 것 같아 보이진 않아 보입니다. 아무튼 안드로이드상에서 벤치마킹을 수행할 수 있는 툴이 현재로선 제가 없는걸로 알고 있어서 위 결과로 유추할 수 밖에 없겠습니다.


요약 : Retrofit2의 오버헤드가 반드시 Dynamic proxy 사용 때문이라고는 결론을 내릴 순 없습니다. 위 결과로부터 okhttp3을 직접 사용하는 것이 retrofit2를 사용하는 것보다 좀더 나은 성능을 줄 수 있다라고 볼 수 있을것 같습니다. 

Posted by 제이제이랩
,

이번 글에서는 java.lang.reflect.Proxy, java.lang.reflect.InvocationHandler에 대해 다루겠습니다. 안드로이드 개발에서 Retrofit을 사용하여 Restful API 사용 로직을 구현하려면 아래와 같이 코드를 작성합니다.


<출처 : http://square.github.io/retrofit/>

public interface GitHubService {
    @GET("users/{user}/repos")
    Call<List<Repo>> listRepos(@Path("user") String user);
}
Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com/")
    .build();

GitHubService service = retrofit.create(GitHubService.class);
Call<List<Repo>> repos = service.listRepos("octocat");

위 코드를 보면 개발자는 HTTP 요청을 보내기 위해 Interface를 정의(여기서는 GitHubService)해서 Retrofit.create() 메소드에 전달합니다. 그러면 Retrofit.create()는 GitHubService 인터페이스를 구현한 Proxy class를 만들어 반환하고 개발자는 interface에 선언한 메소드 GitHubService.listRepos() 메소드를 호출하면 "https://api.github.com/users/{user}/repos" 리소스에 GET 요청을 보내고 응답을 List<Repo>로 얻습니다.


Retrofit 사용자는 서버 URL을 설정하고, 어떤 리소스에 무슨 요청을 보낼지(GET, PUT, POST, DELETE 등의 메소드 설정), 응답은 어떤 형식으로 받을지만 정의하면 됩니다. 사용자가 상기처럼 GitHubService와 같은 Interface를 정의하면 Retrofit은 내부적으로 Dynamic Proxy란 기술을 써서 인터페이스에 대한 구현 부분(Proxy class)을 런타임에 만들어줍니다. HTTP 통신을 어떻게 해야하고 에러 처리는 또 어떻게 해야할지 등에 대한 고민은 Proxy 클래스가 처리하고 있습니다.  우선 Dynamic Proxy를 사용하는 샘플을 보면서 Proxy, InvocationHandler가 어떻게 쓰이는지 이해를 해봅시다.

public class TestDynamicProxy1 {
    public static void main(String[] args) {
        TestInterface proxyInstance = (TestInterface)
                Proxy.newProxyInstance(TestInterface.class.getClassLoader(),
                new Class[]{TestInterface.class},
                new CustomInvocationHandler());

        System.out.println(proxyInstance.padSomeSymbolInTail("aaaaa"));
    }

    public interface TestInterface {
        String padSomeSymbolInTail(String text);
    }

    public static class CustomInvocationHandler implements InvocationHandler {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            return args[0].toString() + " FaNtAsTiC TaIlS";
        }
    }
}
위의 코드를 실행 시키면 아래와 같이 표준 출력에 문자열이 출력됩니다.

aaaaa FaNtAsTiC TaIlS


Interface에 정의된 메소드를 호출하면 실제 코드 실행은 InvocationHandler.invoke()에서 수행하는 구조입니다. 사실 이러한 방식은 Spring Data 에서도 제공하는 것으로 DB에서 CRUD 연산을 간편하게 하기 위해 개발자가 아래와 같이 인터페이스를 선언하면 DBMS에 접근하여 커넥션을 맺고 CRUD 동작을 수행하여 원하는 데이터를 반환해 주는 일은 Proxy class가 합니다.


<출처 : Spring in Action 4th edition. p320>

public interface SpitterRepository extends JpaRepository<Spitter, Long> {
    Spitter findByUsername(String username);
}

위와 같이 Dynamic proxy 기술은 자바 라이브러리나 프레임워크에서 자주 사용되고 있습니다. 안드로이드 개발자로서 모바일 환경에서 Dynamic proxy를 사용하는 것에 대해 계속 괜찮은 걸까?라는 의문을 가지고 있어서 다음 포스트에서는  Dynamic proxy 사용으로 어떤 장점과 단점이 있는지 살펴보고 다른 대안이 있으면 그 대안을 구현해 보고 dynamic proxy와 비교해 보겠습니다.


<참고자료>

1. http://docs.oracle.com/javase/1.5.0/docs/guide/reflection/proxy.html

2. https://docs.oracle.com/javase/7/docs/api/java/lang/reflect/Proxy.html#getProxyClass(java.lang.ClassLoader,%20java.lang.Class...)

3. https://docs.oracle.com/javase/7/docs/api/java/lang/reflect/InvocationHandler.html

4. Spring in Action 4th edition by Walls, Craig (2014) Paperback[ISBN-13 : 978-1617291203]


Posted by 제이제이랩
,


2016/02/29 - [java] - 단순한 Injector를 구현해 써보자 #1


이전 글은 윗 링크에 있습니다. 앞으로 작성할 코드는 아래 GitHub repository에 저장되어 있으니 참고 하세요.


우선 제가 만들 Dependency Injector는 팩토리 패턴과 싱글톤 패턴을 이용한 아주 Naive한 Injector 입니다. 사실 진정한 dependency injector 아니고 무늬만 JSR 330 스펙을 조금 따르는 코드입니다. 이를 이용해 샘플 코드를 구현해 보고 문제점을 보도록 하겠습니다. 그런 후 JSR 330에 소개된  annotation을 처리하기 위해 apt(annotation processing tool)을 활용하여 실제 dependency injection이 수행되도록 구현하겠습니다. (이건 개인적으로 단순 호기심과 취미로 구현하려는 것이니 실제 자바 응용 개발하실 땐 Dagger2 또는 Spring을 사용하셔야 합니다.) 본 글은 JSR 330에 대한 지식이 있는 것으로 가정하고 모르시면 http://docs.oracle.com/javaee/6/api/javax/inject/package-summary.html 을 읽으시면 됩니다.


Car, Weather, Road, GangnamRoad, SamsungRoad 로 구성된 애플리케이션을 개발하는데 각 클래스는 다음과 같이 관계를 가지고 있습니다. 


DI는 syringe가 해야합니다. 사실 syringe가 해야하는데 syringe가 알아서 하도록 하려면 reflection을 사용하여 런타임에 DI가 수행되도록 하거나 apt(annotation processing tool)로 DI를 수행하는 코드를 생성할 수 있는데 먼저 우리가 가장 쉽게할 수 있는 DI 흉내만 내어보겠습니다. 

Car, Road, GangnamRoad, SamsungRoad, Weather 클래스는 아래와 같습니다.

public class Car {
    @Inject Weather weather;
    Road road;

    @Inject
    public Car() {
        weather = Syringe.getInstance().provideWeahter();
    }

    public void setRoad(Road road) {
        this.road = road;
    }

    public void drive() {
        if(road.canGo()) {
            System.out.println("I'm driving on " + road);
        } else {
            System.out.println("I can't drive on " + road);
        }

        weather.printWeather();
    }

    public void stop() {
        System.out.println("I stop on " + road);
        weather.printWeather();
    }
}

@Singleton
public class Weather {
    String state;

    public void setState(String state) {
        this.state = state;
    }

    public void printWeather() {
        System.out.println("current weather is " + state);
    }
}

Weather 클래스는 싱글톤인데요. JSR 330에서 싱글톤 클래스는 @Singleton annotation만 추가하면 DI 프레임워크에서 해당 클래스를 싱글톤으로 만들어줘야 한다고 되어 있으니, getInstance() 메소드를 만드는 작업은 이제부터 하지말도록 합니다. 어떤 클래스를 싱글톤으로 만들고 싶다? 그럴 땐 @singleton만 붙여주시면 만사오케이! 입니다. 

public interface Road {
    boolean canGo();
}

public class GangnamRoad implements Road {
    public boolean canGo() {
        return true;
    }

    @Override
    public String toString() {
        return GangnamRoad.class.getSimpleName();
    }
}

public class SamsungRoad implements Road {
    public boolean canGo() {
        return false;
    }

    @Override
    public String toString() {
        return SamsungRoad.class.getSimpleName();
    }
}

GangnamgRoad, SamsungRoad, Weather 클래스들은 dependency를 필요로 하지 않기 때문에 DI가 필요없고 Car 클래스는 Weather, Road 객체를 사용해야 하기 때문에 DI가 필요합니다. 그래서 "@Inject Weather weather;"로 syringe가 Weather 인스턴스를 주입하도록 구현했고 Car constructor에서 syringe를 통해 Weather 인스턴스를 주입하게 됩니다. Car 객체가 syringe에 의해 생성될 때는 인자가 없는 생성자가 호출되도록 "@Inject public Car() { ... }" 로 구현을 했습니다. DI 개념의 도입으로 프로그램의 핵심을 구성하는 클래스 내부에서 new란 키워드로 객체를 생성하는 부분이 없어졌습니다. 앱을 실행 하는 코드를 먼저 살펴보고 보겠습니다.

public class App {
    @Inject Car car;
    @Inject @Named("gangnam") Road gangnamRoad;
    @Inject @Named("samsung") Road samsungRoad;
    @Inject Weather weather;

    public App() {
        car = Syringe.getInstance().provideCar();
        gangnamRoad = Syringe.getInstance().provideGangnamRoad();
        samsungRoad = Syringe.getInstance().provideSamsungRoad();
        weather = Syringe.getInstance().provideWeahter();
    }

    public void run() {
        car.setRoad(gangnamRoad);
        weather.setState("raining");
        car.drive();
        weather.setState("cloudy");
        car.stop();

        car.setRoad(samsungRoad);
        car.drive();
    }

    public static void main(String[] args ) {
        App app = new App();
        app.run();
    }
}

위 프로그램을 실행 시키면 짜잔. 아래와 같은 결과과 나옵니다.

I'm driving on GangnamRoad
current weather is raining
I stop on GangnamRoad
current weather is cloudy
I can't drive on SamsungRoad
current weather is cloudy

위 프로그램을 보시면 음?? 객체는 어디에서 생성되서 실행되는 거야? 라는 의문이 생길것입니다. 객체 생성은 모두 DI 프레임워크가 담당하고 저는 syringe란 놈을 만들면서 syringe에서 객체 생성이 모두 이루어 지도록 할 예정입니다. App클래스의 생성자를 보면 App클래스에서 사용하는 모든 클래스의 객체 주입이 syringe에 의해 수행되고 있습니다. 현재의 Syringe 클래스는 단지 팩토리 메소드로 객체를 생성하는 것이라 개발자가 어떤 dependency를 필요로 하면 dependency를 주입까지 해줘야 하는 번거로움이 있습니다. 물론 Syringe 클래스에 해당 팩토리 메소드를 만들어야 하는 일까지 해야합니다. 아래 Syringe 클래스를 보시죠.

public class Syringe {
    static Syringe instance;
    Map<Class<?>, Object> singletonCache;

    private Syringe() {
        singletonCache = new HashMap<Class<?>, Object>(100, 0.75f);
    }

    public static Syringe getInstance() {
        if(instance == null) {
            synchronized (Syringe.class) {
                if(instance == null) {
                    instance = new Syringe();
                }
            }
        }

        return instance;
    }

    public Weather provideWeahter() {
        Object object = singletonCache.get(Weather.class);
        if(object == null) {
            synchronized (Weather.class) {
                if((object = singletonCache.get(Weather.class)) == null) {
                    object = new Weather();
                    singletonCache.put(Weather.class, object);
                }
            }
        }

        return (Weather)object;
    }

    public Car provideCar() {
        return new Car();
    }

    public GangnamRoad provideGangnamRoad() {
        return new GangnamRoad();
    }

    public SamsungRoad provideSamsungRoad() {
        return new SamsungRoad();
    }
}

각 dependency들을 주입하기 위해 객체를 리턴하는 팩토리 메소드를 한땀한땀 만들었습니다. 싱글톤을 구현하기 위해서 com.util.HashMap을 사용해 싱글톤 캐쉬를 만들었습니다. 그래서 @singleton annotation으로 annotated된 클래스들은 getInstance() 메소드를 구현하지 않고 syringe가 알아서 처리해 주도록 처리하였습니다.


이렇게 팩토리 메소드로 DI를 구현하려니 너무 문제가 많습니다. 개발자는 @Inject annotation만 사용해서 dependency를 주입하고 싶은데 syringe에 직접 객체 생성코드를 작성해야 하고 Syringe.provide~() 메소드를 명시적으로 호출해 DI를 수행해야 합니다. 예를 들면 Car 클래스 생성자에서 Syringe.provide~(), App 클래스 생성자에서 Syringe.provide~() 메소드 호출이 필요없이 Syringe가 자동으로 객체와 참조를 바인딩해줘야 합니다. 그럼 개발자는 아래와 같이 Car, App 클래스를 구현해주면 되겠죠. 그리고 이게 제가 원하는 방식이고 JSR 330에 명시된 요구사항입니다. 그런데 이렇게 하려면 이제 apt가 필요합니다. Syringe 클래스에 팩토리 메소드를 하나씩 추가하는게 아닌 apt를 이용해 객체를 주입하고 생성하는 코드를 생성할 필요가 생겼습니다. 저는 DI 프레임워크 구현할 때 리플렉션을 사용하지 않을 겁니다. Roboguice가 리플렉션으로 DI 구현하다 성능 이슈가 생겨서 개발자들에게 외면 받았다는걸 상기시키면 리플렉션 접근은 이 분야에선 피해야 겠습니다.(RoboGuice 개발자도 리플렉션을 피하고 apt를 사용하려고 작업 중인걸로 압니다..) 다음 포스트에서는 apt가지고 DI 만들기에 접근해 보겠습니다.

public class Car {
    @Inject Weather weather;
    Road road;

    @Inject
    public Car() {
    }

    public void setRoad(Road road) {
        this.road = road;
    }

    public void drive() {
        if(road.canGo()) {
            System.out.println("I'm driving on " + road);
        } else {
            System.out.println("I can't drive on " + road);
        }

        weather.printWeather();
    }

    public void stop() {
        System.out.println("I stop on " + road);
        weather.printWeather();
    }
}

public class App {
    @Inject Car car;
    @Inject @Named("gangnam") Road gangnamRoad;
    @Inject @Named("samsung") Road samsungRoad;
    @Inject Weather weather;

    public App() {
    }

    public void run() {
        car.setRoad(gangnamRoad);
        weather.setState("raining");
        car.drive();
        weather.setState("cloudy");
        car.stop();

        car.setRoad(samsungRoad);
        car.drive();
    }

    public static void main(String[] args ) {
        App app = new App();
        app.run();
    }
}


Posted by 제이제이랩
,


2016/04/30 - [java] - JAVA 8 Fuctional Inteface, Method reference and Lambda expression #1


이번 글에서는 java.util.function 패키지에 선언된 functional interface 중 primitive type을 다루는 functional interface와 그에 대응하는 람다 표현식 및 메소드 레퍼런스에 대해 알아 보겠습니다. 아래 소스를 보시면 BooleanSupplier, ObjDoubleConsumer, ToLongBiFunction 등을 첫번째 인수로 받는 메소드들을 정의하고 호출하는 로직을 구현하였습니다. Functional interface가 인수로 사용될 때 람다 표현식이 어떻게 전달되는지, 어떤 method reference가 전달되는지 확인해 보세요. 보는데 직관적이지 않다면 답글 주세요~!


Posted by 제이제이랩
,

2014년 3월 18일 Java 8가 릴리즈가 되었습니다.(참고 : https://en.wikipedia.org/wiki/Java_version_history#Java_SE_8)

자바 8에서 제가 관심있는 부분은 JVM 메모리 모델에서 Permanent generation이 없어지고 metaspace가 도입된 것과 functional programming style 지원을 위해 람다, Functional interface, Stream이 도입되고, Jodatime의 개념이 JSR 310에 녹아들었던 점입니다.


안드로이드에서는 RetroLambda로 Lambda expression을 사용할 수 있지만, Android는 N부터 Lamba expression, stream을 지원하고 있습니다. 안드로이드 개발자 태생이라 발등에 떨어진 불을 끄기 위해 늦게나마 lambda expression, stream, functional interface를 공부하여 이를 좀 정리해 보려합니다.


우선 첫번째 제가 정리하고 싶은 부분은 java.util.function에 정의된 Functional Interface와 Labmda expression, method reference간의 상호 매핑입니다. 

위 소스를 보시면 com.util.function의 대표적인 7개의 functional interface가 그에 대응하는 람다 표현식과 method reference 바인딩 하는 예제인데요. 우선 type parameter를 사용하는 인터페이스를 가지고 놀았는데, 다음에는 primitive type에 관한 functional interface와 그에 대응하는 람다 표현식, 메소드 참조를 가지고 놀아보겠습니다.


Posted by 제이제이랩
,

2016/03/06 - [java] - 추상화(Abstraction) 이해의 중요성 #2



추상화 이해의 중요성에 대한 3번째 글입니다. 2번째 글에선 자동차(Car)의 속성에 해당하는 Car.drivingDistance, Car.dateMadeAt, Car.Color, Car.Engine 등을 getter-setter method로 왜 Encapsulation(캡슐화)을 하는지 살펴보았습니다.


엔진(Engine)이라는 것도 생각해 보니 제조사가 어디인지 몇 마력인지 모델은 또 무엇인지 등 복잡한 정보를 가지고 있는 객체란 생각이 듭니다. Engine에 대한 추상화도 필요해 보입니다. 휠(Wheel), 제조사, 트렁크, 루프도 추상화가 필요해 보입니다. 쉽게 아래 테이블처럼 자동차 각 구성요소를  클래스로 추상화 할수 있습니다.


자동차 구성요소

추상(Abstraction) 

 엔진(Engine)

Engine 

 휠(Wheel)

Wheel

 제조사(Manufacturer)

Manufacturer

 트렁크(Trunk)

Trunk 

 루프(Roof)

Roof 


여기서 주의해야 할 사항이 있습니다. 추상화를 너무 과도하게 하지 않는가 생각해 보아야 합니다. primitive type, String으로 충분히 요구사항을 만족시킬 수 있고 추후 요구사항 및 변화에도 유연할 수 있다면  클래스화 하지 않을 수도 있습니다. Oracle HotSpot 32bit JVM 기준으로 한 객체는 8 바이트의 헤더를 가지고 있고 배열(Array)은  12 바이트의 헤더를 가지고 있습니다. 즉, 하나의 객체는 최소 8byte 메모리를 소모하고 하나의 배열은 최소 12byte 메모리를 소모 합니다. 메모리를 고려해야할 시스템 환경이라면 클래스 대신 byte, boolean, char, short, int, float, long, double과 같은 primitive type과 String으로 쓰는 것을 고려할 수있습니다. 원래 Car 클래스가 String 멤버 변수로 이루어진 것처럼 말이지요.


이 글에서는 윗 테이블처럼 추상화를 했다고 가정합니다. Car.color는 int로 변경하고 Car.dateMadeAt은 java.util.Date 또는 java 8의 java.time.LocalDateTime을 사용할 수 있습니다. 그러나 Car.dateMadeAt을 memory efficient하게 사용하기 위해 String으로 사용하고 java.util.DateTime 이나 java.time.LocalDateTime으로 변환하는 메소드를 제공하도록 하겠습니다.

package com.jayjaylab.app.usedcar

public class Car {
    int drivingDistance;    // in km(killo meters)
    String dateMadeAt;    // in 2007-12-03T10:15:30 format
    int color;
    Engine engine;
    Wheel wheel;
    Manufacturer manufacturer;
    String model;
    Trunk trunk;
    Roof roof;
    // and so on...

    public void setEngine(Engine engine) {
        this.engine = engine;
    }

    public void setWheel(Wheel wheel) {
        this.wheel = wheel;
    }

    public void setRoof(Roof Roof) {
        this.roof = roof;
    }
    
    public void setDrivingDistance(int drivingDistance) {
        this.drivingDistance = drivingDistance;
    }

    public java.util.Date getDateMadeAt() {
        return new java.util.Date(dateMadeAt);
    }
    
    public java.time.LocalDateTime getLocalDateTimeMadeAt() {
        return java.time.LocalDateTime.parse(dateMadeAt);
    }

    ....

    public static void main(String[] args) {
        Car car = new Car();
        // 엔진 변경
        car.setEngine(new Engine("yamato"));
        car.setEngine(new Engine("superengine"));
        // 휠 변경
        car.setWheel(new Wheel("kumho_wheel"));
        car.setWheel(new Wheel("fantastic_wheel"));
        // 루프 변경
        car.setRoof(new Roof("default_roof"));
        car.setRoof(new Roof("sun_roof"));
        // 주행 거리 변경. 불법 이죠잉!
        car.setDrivingDistance(100);  // 100 km
        car.setDrivingDistance(200); // 200 km
    }
} 

자동차 중고매매 사업장에서의 쓰일 자동차 Abstraction이긴 한데, 개발사 회사의 입장으로선 굳이 자동차 매매만 되는 애플리케이션으로 한정시킬 필요가 없을것 같습니다. 추후에 자동차 뿐만 아니라 오토바이, 자전거등의 사업장에서도 사용될 수 있는 확장성을 좀 더 고려하면 개발한 제품을 공급할 곳이 더 많아지겠지요. 그래서 저는 Vehicle이란 인터페이스를 만들어서 Car, Bike가 상속하게 해야할지 Buyable이란 인터페이스를 만들어서 Car, Bike가 상속하게 해야할지 고민이네요. Vehicle이란 인터페이스가 가질 Method는 go(), stop() 등의 자동차가 하는 행위일 것 같은데 사실 매매용 소프트웨어이기 때문에 자동차가 하는 행위 자체를 선언하는 것은 별 쓸모가 없어보이고 매매 시스템이니깐 매매되는 자동차라는 점에 중점을 두면 Buyable이란 인터페이스를 만들어 isSold(), buy()등의 매매 관련 메소드를 정의하는게 좀더 좋은 선택으로 보입니다.

그럼 한번 Buyable이란 인터페이스를 정의해 보겠습니다. 알아둬야 할 사항으로  자바에서는 대개 OOP의 Polymorphism(다형성)을 지원하기 위해 Interface을 정의하고 이를 상속하는 기법을 사용합니다. 특정 인터페이스를 상속하는 몇몇 클래스들이 인터페이스에 선언된 메소드의 정의가 동일하다면 그 인터페이스를 상속하는 Abstract 클래스를 정의하고 이 Abstract 클래스를 다시 상속하는 기법이 널리 쓰입니다. 그런데 Java 8에서는 interface에 default method와 static method란게 도입되어서 이젠 그런 기법이 서버쪽에서는 안쓰일 것 같네요. 서버 개발에 대한 실무 경험이 없어 장담하긴 힘듭니다.;; 안드로이드는 아직 Java 7만 지원이 되어서 java 8은 사실 고려 안해도 되구요.

[Java 8 Interface의 default methods, static methods 관련 글은 여기로]

package com.jayjaylab.app.usedcar

/**
 * 매매할 수 있는 객체를 나타냅니다.
 */
public interface Buyable {
    /**
     * 해당 상품이 다 팔렸는지 알기 위해 호출합니다.
     * @return true 이미 다 팔림, false 재고가 남아 있음.
     */
    boolean isSoldOut();

    /**
     * 구매할 때 호출합니다.
     * @param option 구매할 때 추가할 옵션들입니다.
     * @return true 구매 신청이 정상적으로 완료됨, false 구매 실패
     */
    boolean buy(Option option);

    ...
}
package com.jayjaylab.app.usedcar

public class Car implements Buyable {
    int drivingDistance;    // in km(killo meters)
    String dateMadeAt;    // in 2007-12-03T10:15:30 format
    int color;
    Engine engine;
    Wheel wheel;
    Manufacturer manufacturer;
    String model;
    Trunk trunk;
    Roof roof;
    // and so on...

    public boolean isSoldOut() {
        return false;
    }

    public boolean buy(Option option) {
        return true;
    }
    
    ...
}


음, 위에 것처럼 구현하니 왠지 매매할 수 있을것 같은 자동차 Abtraction이 만들어진 것 같네요. 이번 글은 우선 여기까지하고 시간될 때 다시 돌아오도록 합죠.

Posted by 제이제이랩
,

소프트웨어 엔지니어로서 자신이 만든 로직 또는 알고리즘이나 3rd party 라이브러리의 성능을 측정하고 비교 분석하고 싶을 때가 있습니다. 무심코 작성한 코드가 성능에 영향을 미칠 수 있어서 특정 동작을 하는 로직을 다른 방식으로 여러개 만들어 벤치마킹 할 수도 있고 그냥 지적 호기심에 특정 라이브러리의 성능을 다른 경쟁 라이브러리의 성능과 비교 분석하고자 할 수도 있습니다. JAVA로 구현된 코드를 측정할 때 가장 공신력있는 벤치마킹 결과를 얻고자 한다면 JMH(Java Microbenchmark Harness)를 이용하는걸 추천합니다.

  • 왜 JMH로 벤치마킹 해야하는가?

JVM을 만든 사람들이 JMH를 만들었습니다. 벤치마킹 환경 구성이나 실제 성능 측정에 영향을 미칠만한 부가적인 요소를 최소화 했겠죠? 메소드가 JVM에서 실행될 때 JIT 컴파일러는 최적화를 합니다. 어느 부분에 최적화 할곳이 있는지, inline method로 만들부분이 있는지, 중복 코드가 있는지, dead code가 있는지, 코드 reordering 할 부분이 있는지 등을 분석해 최적화를 하기 때문에 제대로된 벤치마킹을 하기가 어렵습니다. JMH를 이용하면 컴파일러의 최적화 작업을 어느정도 조정할 수 있고 코드 웜업을 한 상태에서 메소드들을 측정하기 때문에 측정 오류를 최소화 할 수 있습니다. 그리고 공식적으로 JAVA 9에 벤치마킹 툴로 배포된다고 합니다.

  • JMH 사용법

아래 참고에 있는 링크에서 학습하시면 됩니다. 그 중 Jakob Jenkov가 작성한 tutorial이 가장 읽기 편합니다.  http://tutorials.jenkov.com/java-performance/jmh.html 여길 참고하세요.

  • 간단한 벤치마킹 코드를 짜보자.

(Github[https://github.com/jayjaykim/jmh-sample]에 에 소스 올려 놓았으니 앞으로 설명할 샘플 코드를 다운 받아 참고 하시면 됩니다.)

JMH를 이용하여 벤치마킹 코드를 구현하려면 maven build system을 사용해야 합니다. https://maven.apache.org/ 에서 maven을 다운 받고 실행 파일 경로 설정을 한 후 아래와 같이 콘솔에 입력합니다. 하기 명령어의 각 옵션 사이가 라인피드로 되어 있는데요 가독성을 위해 편의상 그렇게 한것이고 실제로는 그냥 스페이스 처리 하시면 됩니다. 

$ mvn archetype:generate -DinteractiveMode=false -DarchetypeGroupId=org.openjdk.jmh -DarchetypeArtifactId=jmh-java-benchmark-archetype -DgroupId=com.jayjaylab.sample -DartifactId=jmh-sample -Dversion=1

이렇게 하면 JAVA maven project가 하나 만들어 지는데, 바로 아래와 같이 명령어 입력해 주시면 벤치마킹이 수행이 됩니다. 자세한 사항은 참고[3](http://tutorials.jenkov.com/java-performance/jmh.html)를 따라하시면 쉽게 됩니다.^^

 $ mvn clean install
 $ java -jar target/benchmarks.jar

이렇게 하면 jmh를 이용해 벤치마킹을 실행할 수 있습니다. 벤치마킹 모드를 Mode.Throughput으로 하면 한 메소드 당 기본 10번 세팅(한 세트에 20 warmup iteration, 20 실제 iteration)을 돌립니다.


재미로 그럼 벤치마킹 코드를 작성해 보겠습니다. 간단히 1부터 10,000까지 합을 구하는 로직을 for loop을 이용해 구하는 법과 JAVA 8의 IntStream을 이용해 구하는 법을 벤치마킹합니다. 아래 코드의 testSummingByForLoop(), testSummingByStream() 메소드는 각각 for loop과 IntStream으로 합계를 구하는 메소드입니다.

public class StreamBenchmark {
    @org.openjdk.jmh.annotations.State(Scope.Thread)
    public static class State {
        int startNumber;
        int endNumber;

        @Setup(Level.Trial)
        public void setUp() {
            startNumber = 0;
            endNumber = 10000;
        }

        @TearDown(Level.Trial)
        public void tearDown() {
        }
    }

    @Benchmark
    @BenchmarkMode(Mode.Throughput)
    @OutputTimeUnit(TimeUnit.SECONDS)
    public void testSummingByForLoop(State state, Blackhole blackhole) {
        long sum = 0l;
        for(int i = state.startNumber; i < state.endNumber; i++) {
            sum += i;
        }
        blackhole.consume(sum);
    }

    @Benchmark
    @BenchmarkMode(Mode.Throughput)
    @OutputTimeUnit(TimeUnit.SECONDS)
    public void testSummingByStream(State state, Blackhole blackhole) {
        blackhole.consume(
                IntStream.range(state.startNumber, state.endNumber).sum());
    }
}

위 코드를 빌드하고 벤치마킹을 실행하면 하기와 같은 결과를 얻습니다. for-loop 확실히 약 7배 빠르군요. (그런데 정확히 7배라고 말하기 어렵습니다. 원래 벤치마킹할 때는 벤치마킹만 돌리고 다른 프로그램을 실행하면 안되는데 저는 이것저것 하면서 돌린것이라서;;). 참고로 본 벤치마킹을 실행한 환경은 MAC OS X yosemite version 10.10.5(14F27), 2.7GHz Intel core i5, 8GB 1867MHz DD3 입니다. Throughput으로도 충분히 해석할 수 있지만 @BenchmarkMode를 Mode.AverageTime으로 변경하여 각 메소드가 얼마나 걸리는지 알수 있습니다. 아래 결과를 좀더 보시면 min, max, mean을 구하고 표준편차와 분포를 normal distribution을 가정했을 때 confidence interval도 계산되어 나옵니다(confidence level : 99.9%, margin of error : (41011.042, 42081.252]). 이 부분에 대한 해석은 통계에 대한 지식이 있어야겠지요? 통계도 아셔야합니다. 저는 대학교, 대학원때 공부했는데 아직도 가물가물해서 다시 들여다 보고 있네요^^;;

Result "testSummingByForLoop":
  280454.648 ±(99.9%) 2181.860 ops/s [Average]
  (min, avg, max) = (227831.822, 280454.648, 289601.359), stdev = 9238.133
  CI (99.9%): [278272.788, 282636.509] (assumes normal distribution)

Result "testSummingByStream":
  41546.147 ±(99.9%) 535.105 ops/s [Average]
  (min, avg, max) = (25794.615, 41546.147, 43398.632), stdev = 2265.668
  CI (99.9%): [41011.042, 42081.252] (assumes normal distribution)


# Run complete. Total time: 00:13:28

Benchmark                              Mode  Cnt       Score      Error  Units
StreamBenchmark.testSummingByForLoop  thrpt  200  280454.648 ± 2181.860  ops/s
StreamBenchmark.testSummingByStream   thrpt  200   41546.147 ±  535.105  ops/s


어떤가요? 벤치마킹이 참 편해졌습니다. 자 그럼 JMH를 써보시고, 저는 나중 나중에 좀더 재밌는 벤치마킹을 들고 #2를 작성해 올리겠습니다.


참고

[1] http://psy-lob-saw.blogspot.kr/2013/04/writing-java-micro-benchmarks-with-jmh.html

[2] http://java-performance.info/jmh/

[3] http://tutorials.jenkov.com/java-performance/jmh.html

Posted by 제이제이랩
,

2016/03/02 - [java] - 추상화(Abstraction) 이해의 중요성 #1


자동차는 영어로 Car이고, 자동차를 이루는 구성요소로는 엔진(Engine), 바퀴(Wheel), 제조사(Manufacturer), 모델(Model) 등이 있습니다. 중고매매에서 가장 중요한 주행거리, 연식, 자동차 색상도 놓칠순 없겠군요. 단순한 자동차(Car) 추상화는 java.lang.Object만을 상속하는 Car 클래스를 만드는 것입니다. 다른 말로 POJO(Plain Old Java Object)를 만드는 것입니다.

자동차 추상(Abstraction)은 com.jayjaylab.app.usedcar.Car 클래스 아래와 같이 만들었습니다.

package com.jayjaylab.app.usedcar

public class Car {
    public int drivingDistance;    // in km(killo meters)
    public String dateMadeAt;
    public String color;
    public String engine;
    public String wheel;
    public String manufacturer;
    public String model;
    public String trunk;
    public String roof;
    // and so on...
} 

자동차는 제조사에서 한번 만들어지면 제조사, 모델이 변경되지 않습니다. 위처럼 자동차를 단순하게 추상화했는데, 자동차에서 자주 변경이 되는 부분, 변할 수 있는 부분이 무엇이 있을까 생각해 봅시다.

  • 엔진이 바뀔 수 있다.
  • 휠이 바뀔 수 있다.
  • 주행 거리가 바뀔 수 있다. 자동차 중고매매 사업장에서 이런 요구사항이 있다면 주행 거리 조작! 불법이지요;;
  • 루프가 바뀔 수 있다.

상기 정도로 정리해 볼 수 있습니다. 위 Car 클래스가 이런 변경 사항에 충분히 유연한지 한번 점검해 보겠습니다.

package com.jayjaylab.app.usedcar

public class Car {
    public int drivingDistance;    // in km(killo meters)
    public String dateMadeAt;
    public String color;
    public String engine;
    public String wheel;
    public String manufacturer;
    public String model;
    public String trunk;
    public String roof;
    // and so on...

    public static void main(String[] args) {
        Car car = new Car();
        // 엔진 변경
        car.engine = "yamato";
        car.engine = "superengine";
        // 휠 변경
        car.wheel = "kumho_wheel";
        car.wheel = "fantastic_wheel";
        // 루프 변경
        car.roof = "default_roof";
        car.roof = "sun_roof";
        // 주행 거리 변경. 불법 이죠잉!
        car.drivingDistance = 100;  // 100 km
        car.drivingDistance = 200; // 200 km
    }
} 

위 소스 코드를 보니, 런타임 동안 자동차 엔진, 휠, 루프, 주행거리 변경을 손 쉽게 할 수 있습니다. 그런데 엔진, 휠, 루프, 주행거리를 변경하는데 Car 클래스의 멤버변수를 직접 접근해서 변경해야 합니다. Car 클래스를 사용하는 입장에서 Car 클래스의 멤버 변수가 무엇인지 알 필요가 없습니다. 엔진을 변경하기 위해 Car 클래스에서는 Car.engine이라는 멤버 변수를 외부에 노출시키는게 아닌 엔진을 변경할 수 있는 메소드를 제공해야할 것입니다. Car 클래스 사용자들(개발자들)에 Car 클래스의 속성을 변경 시키려면 메소드를 통하여 변경 시키도록 하고 해당 멤버 변수는 개발자에게 노출시키지 않도록 합니다. 위의 Car 클래스는 캡슐화(Encapsulation)이 잘못된 예의 하나입니다. 아래와 같이 Accessor를 public에서 protected로 변경하도록 하고 멤버 변수에 대한 직접 접근을 막습니다. 사실 멤버 변수를 직접 참조하는것이 메소드를 호출하는 것보다 몇 CPU 싸이클 빠를것 같지만 난독화 때문에 Proguard 사용하면 신경 안써도 됩니다. getter-setter inlining을 proguard가 해준다고 하네요. [참고 http://developer.android.com/training/articles/perf-tips.html#GettersSetters]

package com.jayjaylab.app.usedcar

public class Car {
    int drivingDistance;    // in km(killo meters)
    String dateMadeAt;
    String color;
    String engine;
    String wheel;
    String manufacturer;
    String model;
    String trunk;
    String roof;
    // and so on...

    public void setEngine(String engine) {
        this.engine = engine;
    }

    public void setWheel(String wheel) {
        this.wheel = wheel;
    }

    public void setRoof(String Roof) {
        this.roof = roof;
    }
    
    public void setDrivingDistance(int drivingDistance) {
        this.drivingDistance = drivingDistance;
    }

    ...

    public static void main(String[] args) {
        Car car = new Car();
        // 엔진 변경
        car.setEngine("yamato");
        car.setEngine("superengine");
        // 휠 변경
        car.setWheel("kumho_wheel");
        car.setWheel("fantastic_wheel");
        // 루프 변경
        car.setRoof("default_roof");
        car.setRoof("sun_roof");
        // 주행 거리 변경. 불법 이죠잉!
        car.setDrivingDistance(100);  // 100 km
        car.setDrivingDistance(200); // 200 km
    }
} 

Car 클래스 사용자가 엔진, 휠 등을 변경하기 위해서 위 처럼 set method를 사용하도록 강제하면 Car의 멤버 변수를 잘못된 값으로 변경하는 것을 막을 수 있습니다. 예를 들면, Car.setEngine(), Car.setWheel()의 메소드에 매개변수가 유효한지 체크하는 로직을 넣거나 현재 변경 가능한 상태에만 변경을 하도록 로직을 추가하여 각 변경을 안전하게 수행하도록 합니다. 또한 메소드를 통하여 변경을 할 경우 멀티 쓰레딩 환경에서 thread safety 하도록 로직을 추가할 수도 있습니다. 응용 프로그램을 개발할 때 절대 네버 멤버 변수를 public로 선언하여 직접 접근하는 방식을 피하셔야 합니다. getter-setter로 Encapsulation 하시는 것은 기본입니다. 예외인 경우는 자료 구조 만들 때인데요. 성능(메소드 사용으로 인한 스택 접근 시간)을 위해 cpu cycle 사용을 최소화하기 위한 노력을 자료 구조에서는 해줘야하는데, 자료 구조 내부에서 getter-setter를 이용하지 않고 멤버 변수에 직접 접근을 하는 경우가 있습니다. 안드로이드의 경우는 ViewHolder 패턴을 사용할 때 멤버 변수를 직접 접근합니다. ViewHolder 클래스 내부 멤버변수를 접근할 때 getter로 접근하지 않고 멤버 변수를 직접 접근해서 UI thread 사용 시간을 최소화 해줘야 합니다.


잡설이 많았는데, 그럼 다음 글에서 더욱 제대로된 Car abstraction을 만들어 봅시다.

Posted by 제이제이랩
,

최근에 Jake Warton의 Retrofit 라이브러리 발표 영상을 보고 추상화에 대해서 곰곰히 생각해 보았습니다. 아래 동영상인데요. Jake Warton의 발표를 보면 header abstraction, response abstraction, Call encapsulation 등의 용어를 사용하여 Retrofit을 설명합니다. 이 부분이 저에게는 좀 충격으로 다가왔는데요. 내가 OOP 개념에 대해 대학교 때 공부한 이후에 고민해 본적이 있는가? 나는 코드 설계할때 뭘 고려하지? 나는 저런 용어를 사용하여 내 코드를 설명할 수 있는가? 등등의 생각이 들었드랬죠.




애플리케이션 개발자는 기획에서 내려온 요구사항은 기본적으로 만족시켜야 하고 차후 변경에도 유연하고 확장성 있는 코드를 설계하고 구현 해야합니다. 이 글에서는 유지 보수 용이하고 확장성 있는 코드를 설계하고 구현하기 위해선 뭐가 중요할까 고민해 보는데 그 핵심은 추상화로 보고 고민해 볼까 합니다.


추상화라는 것을 간단하게 말하면 복잡한 것을 간추려서 간단하게 하는 것인데요. 사실 추상화라는 개념은 인간의 사고 방식과 빼놓을 수 없을 것 같습니다. 어떤  유무형의 사물을 볼 때 내 기준에 비추어서 흥미있고 필요한 부분만을 보고 그것에 대해 머리속으로 간단히 기억합니다. 제 생각엔 이미 인간의 인식, 사고, 기억하는 단계에 이미 추상화를 하고 있는것으로 보입니다. 대부분의 사물의 복잡하고 자세한 내부는 외부에 감추어져 있고 사람이란 동물은 꼭 필요하지 않으면 시간 써가면 자세한 내부사항은 알 필요가 없죠. 


만약 자신의 고용주가 어떤 개발 프로젝트를 따와서 본인에게 던져줬다고 합시다. 음, 구체적인게 좋겠군요. 자동차 중고매매 사업장에서 각 딜러가 어떤 차를 누구에게 팔았고, 어떤 차가 재고로 있으며 어떤 차가 새로 들어왔는지등을 확인할 수 있는 애플리케이션 개발을 해야한다고 합시다. 그럼 이 애플리케이션 개발하기 앞서 설계를 하고 개발을 해야하겠는데요. 시스템의 전체적인 설계는 소프트웨어 아키텍쳐 패턴에서 적절한 패턴을 이미 선택했다고 가정하고 이 시스템을 구성하는 객체가 무엇이 있는지 각 관계는 무엇인지 파악하는 단계로 들어섰다고 합시다.


그래서 자동차(Car)를 설계해야 하는데, 요구사항이 무엇인지에 따라 차후 어떤 변경이 있을지에 따라 자동차의 추상화 수준을 결정해야 합니다. 자동차 추상화(Car abstraction)작업을 그럼 해볼까요?



Posted by 제이제이랩
,