해당 책은 리액트뿐만 아니라 리액트를 제대로 이해하기 위한 자바스크립트와 관련있는 전반적인 내용을

시작으로하여 리액트, 리덕스, 테스팅등 실제 업무에서 사용할수 있는 기본적인 내용들을 담고 있습니다. 

따라서, 리엑트를 실무에 적용하거나 다시한번 제대로 내용을 보고 싶은 개발자들에게 추천을 드립니다.


* 장점

- 리엑트 뿐만 아니라 자바스크립트 ES6 문법에 대한 소개, 함수형 프로그래밍 소개등 전반적인 지식을 포함하고 있습니다.

- 책의 구성이 쉽게 되어 있습니다 순수리엑트에서 부터  프로퍼티, 데이터 흐름, 리덕스 등 리엑트를 가장 쉽게 익힐 수 있도록 단원구성이 되어 있습니다. 

- 번역서지만 이해하기 쉽게 잘 번역이 되어 있습니다. 


* 단점

- 전부 다는 아니지만 중간중간에 있는 샘플 소스 코드 중에 다소 복잡해 보이는 코드들이 있었고, 예제 코드에 대한 설명이 개인적으로는 부족하다고 생각이 들었습니다.


* 총평

서버개발과 약간의 Front-End로 업무를 진행하다가 최근에 리액트를 사용하게 되었는데, 

해당 책을 통해서 원리에 대해서 잘 알지 못하고 기계적으로 사용하던 부분들에 대해서 다시 한번 알게 되어 많은 도움이 되었습니다.






이번 포스팅에서는 Kotlin 에서 언어 자체로서의 장점으로 이야기 하는 Null-Safe에 대해서 알아보도록 하겠다.


Kotlin에서 Safe 하다고 이야기 하는 부분은 크게 두가지로 나누어진다.


첫째, Nullable Type 명시를 통한 NullPointerException을 컴파일 타임에서 방지하는 점


둘째, 컴파일러에 의한 AutoCast를 통해서 MissCasting  Error 를 줄일 수 있는 점




이중에 Nullable 에 대해서 알아보도록 하자




Nullable Type & Non-Null Type

fun test(){

var a : Int = 3
a = null // Compile Error

var b : Int? = 4
b = null // Compile Success

}

Kotlin에서는 기본적으로 "?" 는 null을 의미한다.  Default 로는 Null을 허용하지 않고 Nullable 한 경우에는 "?"를 붙여서 Null 이 가능함을 나타내야 한다.

위 문장에서 a라는 변수는 default로 Null을 할당할경우 컴파일 타임에서 에러가 발생하지만, b같은 경우에는 ? 로 Null 이 가능하다고 표현하였기 때문에 에러가 발생하지 않고 Null 할당이 가능하다.



Safe Calls


Nullable Type 응 그래 이제 뭔지 알겠다. 그래서 ?? 개발자가 코드를 작성할때 뭐가 좋아지지?  라고 묻는다면 하나의 예제를 보여주겠다.

Nullable type을 그대로 참조할 경우에 컴파일 타임에서 에러가 발생하므로 개발자로 인해 발생하는 NPE에 대해서 막을 수 있다.  물론 100% 막을 수 있는건 아니지만, 개발을 하면서 NPE 발생에 대해서 생각할 수 밖에 없는 구조로 만들어져 있다. 

fun test(){
val name: String? = null // Nullable type
println(name.length()) // Compilation error
}


아래와 같은 인스턴스에 값을 할당한다고 생각 해보자.  NPE를 발생 시키지 않기 위해서는 어떻게 해야 할까.

fun test(){
var headLength = bob.department.head.length
}


일반적인 코딩을 생각하면 아래와 같이 모든 인스턴스에 대해서 Null 체크를 해줘야 한다.

if(bob != null && department != null & haed != null){
bob.department.head.length = 3
}


하지만 Kotlin 에서는 Nullable을 직접 명시하기 때문에 아래와 같이 표현이 가능하다.


fun test(){

var size = bob?.department?.size
print(size)

}

bob, Department 가 Nullable 이기 때문에 Null을 참조하더라도 바로 NPE가 발생하지 않고 , bob 이나 deprtment가  null 이라면 size 는 null 로 셋팅이 될 것이다.

위와 같은 경우에 default 값을 선언하기 위해서 Kotlin에서는 Elvis Operator 라는 것을 재공한다.





Elvis Operator


만약에 어떤 값이 Null 일경우 특정 값을 할당하는 경우 어떻게 표현해야 할까 ? If-else 구문을 활용하면 다음과 같이 표현 할 수 있다.

fun test(){

var size = if(bob.department != null) bob.department.length else -1

}


이런 표현식의 경우에 Elvis Operator ?: 을 활용하면 훨씬 쉽게 표현이 가능하다. ?: 의 다음에 오는 값이 앞에 오는 인스턴스가 NULL 일때 Default 값이라고 보면 될거 같다.

fun test(){

var size = bob?.department?.length ?: -1

}


Elvis Operator 를 응용하면 아래와 같이 Null 값에 대한 유효값 처리를 할 수 있다.

fun foo(node: Node): String? {
val parent = node.getParent() ?: return null
val name = node.getName() ?: throw IllegalArgumentException("name expected")
}



!! Operator

non-null 을 나타내는 !! Operator가 있지만 NullPointer를 발생 시킬 가능성이 있기 때문에 최대한 해당 Operator의 사용은 피하는게 좋다.

굳이 Null Safe 한게 싫은 경우 사용하기를 바란다.(아무 생각 없이 코딩을 하다 보면 안드로이드 스튜디오 에러를 막기 위해서 !! 로 도배가 된다는 슬픈 전설이)




Collections of Nullable Type


Collection 타입에서 Null과 Non-Null Object를 필터할 수 있는 메소드가 제공된다. 사용법은 아래와 같다.

fun test(){
val nullableList: List<Int?> = listOf(1, 2, null, 4)
val intList: List<Int> = nullableList.filterNotNull()
}




'Programming' 카테고리의 다른 글

Kotlin Syntax (2) - Control Flow  (0) 2017.07.05
Kotlin Syntax (1) - Basic Type  (1) 2017.07.03
Java8 Lambda 최대값 갯수 표현식  (0) 2017.06.04
[JavaScript] Switch - Fall Through  (0) 2017.05.28
RecyclerView, Cardview Example  (0) 2017.05.28

Kotlin Control Flow




If Expression


Kotlin에서는 if 문이 값을 리턴하기 때문에 삼항연산자(condition? A:B) 를 사용할 필요가 없다. 


기존에는 아래와 같이 값을 직접 리턴할 수 없기 때문에 할당을 하는 블락이 필요했다.

val a =3 
val b =4
var max =0
if( a < b ){
max = b
}else{
max = a
}


 Kotlin 에서는 다음과 같이 if 문을 사용 할 수 있다. 생각보다 많은 양의 코드를 줄여 줄 수 있는 부분이다.

val a =3
val b =4
val max = if (a<b) b else a




When Expression


When 문은 자바나 C에 있는 Switch 문을 대체하는 용도로 사용되며, 실제로 코딩을 하다보면 단순히 switch 문의 대체문이 아니라 if else 의 늪을 어느 정도 커버 할 수 있는 제어문이라고 할 수 있다. 

기본적인 사용법은 아래와 같다. (Switch 문과는 다르게 별도의 break 는 필요 없다)


fun test(){
val x = 2
when (x) {
1 -> print("x == 1")
2 -> print("x == 2")
else -> {
print("x is neither 1 nor 2")
}
}// Result : print 'x==2'
}


여러개의 값을 같이 매칭 시키기 위해서는 콤마(',') 또는 범위표현(..)을 사용할 수 있다. (범위 표현식에서는 앞에 "in"을 붙여야 한다)

fun test(){
val x = 2
when (x) {
0,1 -> print("zero and one") // , 를 사용하여 다중값 셋팅
in 2 .. 10 -> print("1 over Under 10") // 범위연산자를 활용하여 범위 표현
else -> {
print("else")
}
}
}



또한, 특정 값이 아니라 statement가 TRUE  냐  FALSE 냐에 따라서 처리도 가능하며, 이를 활용할 경우에 IF-ELSE 문을 대체 할 수 있다. 

fun hasPrefix(x:Any) = when(x){
is String -> x.startsWith("prefix")
else -> false
}
val x = 2
when {
x.isOdd() -> print("x is odd")
x.isEven() -> print("x is even")
else -> print("x is funny")
}





For Loops


For 문은 기본 반복문과 같은 기능을 제공한다고 생각하면 되지만 기본적으로 foreach 문을 기본으로 활용한다고 생각하면 되고 기존의 인덱스 기반으로 인덱싱해서 반복 하는 기능은 다른 형태로 제공한다.


fun test(){

var collection = ArrayList<Int>()
var array = collection.toArray()

for (item in collection) print(item) // 일반적인 foreach 형태

for( i in array.indices){ // Array 순회
print(array[i])
}

for((index,value) in array.withIndex()){ // 인덱스를 가지고 Array 순회
println("the element at $index is $value")
}

}



위와 같이 for statement 를 사용할수도있지만 아래와 같이 foreach 문을 사용해서 표현도 가능합니다.

fun test(){
(1..10).forEach { i -> print(i) }
}




While Loops


while문과 do-while문은 기존의 언어에서 사용하던 문법과 달라진 점이 없습니다. (설명 생략)


fun test(){
while (x > 0) {
x--
}

do {
val y = retrieveData()
} while (y != null) // y is visible here!
}



'Programming' 카테고리의 다른 글

Kotlin Syntax (3) - Null Safe  (0) 2017.07.07
Kotlin Syntax (1) - Basic Type  (1) 2017.07.03
Java8 Lambda 최대값 갯수 표현식  (0) 2017.06.04
[JavaScript] Switch - Fall Through  (0) 2017.05.28
RecyclerView, Cardview Example  (0) 2017.05.28

Kotlin 이 2017 Google I/O 에서 Android 정식언어로 지정되기 전부터 관심을 가지고 공부를 하고 있었는데,

오늘 GDG 관련 행사에서도 Kotlin 에 대한 관심이 많이 높아 졌음을 다시 한 번 느낄 수 있었다. 


실제로 Kotlin을 사용해서 개발을 하다 보면 편리하거나 간결한 방법 보다는 기존의 Java와 같은 스타일로 코드를 작성하게 되는데

Basic Syntax를 정리하면서 새로운 문법들을 활용해서 보다 간결한 소스 코드를 작성 할 수 있도록 해야겠다. 



Kotlin을 사용함으로 인해서 얻을 수 있는 장점은 다음과 같다. 

1. Null Safe를 통한 코드 안정성 증가 

2. LOC(Line Of Coverage)  감소

3. Modern Program Language 스타일 코딩 


Kotlin의 Basic Syntax를 활용하는 것 만으로도 저 세개를 모두 만족 시킬 수 있을거 같다.



Kotlin Basic Type



Numbers


Kotlin의 built-in Type은 자바와 매우 비슷하다.

Type

Bit width

Double

64

Float

32

Long

64

Int

32

Short

16

Byte

8


Underscores in numeric literals


숫자형 상수의 경우에 가독성이 좋도록 "_" 언더바를 활용하여 표현 할 수 있다. 


fun test() {

val salary = 5_000_000
val bankAccount = 1234_5679_124L
val salaryHex = 0xFF_EE_AA

}

상수의 길이가 길어지거나 의미부여가 필요할때 활용하면 좋을거 같다.




Explicit Conversion


숫자형의 다른 표현방법 때문에 작은타입(예: Int) 가 큰 타입(예:Long)의 자식타입이 아니다. 

아래와 같이 Long 타입인 b 변수는 Int 타입의 변수 a 변수를 담을 수 없다. 


fun test() { // 컴파일 에러
val a : Int = 100
val b : Long? = a
}

fun test() { // 컴파일 성공
val a : Int = 100
var b : Long? = a.toLong() //명시적으로 호출
}



Operations


Kotlin에서는 기본적인 수학연산자들을 제공한다. 

fun test() {
// 2를 왼쪽시프트 2만큼 하고 0x00FF와 bitwise xor 연산을 해라
val x = (2 shl 2) xor 0x00FF
}


  • shl(bits) – signed shift left (Java's <<)
  • shr(bits) – signed shift right (Java's >>)
  • ushr(bits) – unsigned shift right (Java's >>>)
  • and(bits) – bitwise and
  • or(bits) – bitwise or
  • xor(bits) – bitwise xor
  • inv() – bitwise inversion



Arrays


Kotlin에서의 배열은 Array 클래스로 나타낸다. 이 클래스는 get과 set function을 가지고 있고 배열 형태로 나타낸다. 

배열을 만들때는 arrayOf() 를 이용해서 값을 가지고 생성하거나 arrayOfNulls()를 활용해서 Null Element 들을 가지고 배열을 생성할 수도 있다.

fun test() {

var numberArray : Array<Int> = arrayOf(1,2,3,5,6) // Create [1,2,3,5,6]
var nullArray= arrayOfNulls<Int>(10) // create with null element
val firstElement = numberArray.get(0)
var arryaSize = numberArray.size

}


또다른 방법은 Factory Function을 이용해서 생성을 하는 방법인데, 배열의 사이즈를 가지고 각 인덱스를 기반으로 초기 값을 셋팅 할 수 있다. 

fun test() {
// Create String Array ["0","2","4","6","8"]
val asc = Array( 5 , { index -> (index*2).toString() })
}




Strings


String은 immutable 하며  character 인덱스를 가지고 접근이 가능하다. (for-loop로 문자열 순회가 가능 : 자바와 동일)


fun test() {
//String Literals
val s = "Hello, world!\n"

//raw string delimited
val text = """
var i : Int = 0
while(true){
i++
}
"""
}



String Templates


String에는 템플릿 표현식이 들어갈 수 있다. 이는 처리된 결과 값이 문자열에 포함되어 있음을 의미 한다. 이를 표현하기 위해서 $ 를 붙여서 표현한다. 

또는 { } 를 붙여서도 표현이 가능하다. $s.length 와 ${s.length} 는 다르게 표현될 수 있다.


fun test() {
val i = 10
val s = "i = $i" // evaluates to "i = 10"

val l = "abc"
val str = "$l.length is ${l.length}" // evaluates to "abc.length is 3"
}

다음 포스팅에서는 Kotlin 의 Control Flow 에 대해서 설명하겠다.

'Programming' 카테고리의 다른 글

Kotlin Syntax (3) - Null Safe  (0) 2017.07.07
Kotlin Syntax (2) - Control Flow  (0) 2017.07.05
Java8 Lambda 최대값 갯수 표현식  (0) 2017.06.04
[JavaScript] Switch - Fall Through  (0) 2017.05.28
RecyclerView, Cardview Example  (0) 2017.05.28

System.out.println(Arrays.stream(height).filter(u -> u == Arrays.stream(height).max().getAsInt()).count());

Java8  :  배열에서 최대값을 쉽게 뽑아 낼 수 있는 Lambda  표현식

예전 블로그 보다가 나름 재미있게 만들었던 자료가 있어서 가지고 왔다.









Github에 Android 와 관련된 샘플을 업로드할 예정이다.

시리즈 중 1편으로 RecyclerView 와 CardView를 활용한 간단한 리스트 화면을  만들어 봤다.  

(https://github.com/kajsss/android-example)





Layout 작성

레이아웃은 RecyclerVIew를 포함하고 있는 메인 페이지와 각 아이템을 그리는 카드뷰 레이아웃 2개가 필요하다.


<activity_main> 에서는 적당한 위치에 RecyclerVeiw 를 위치 시켰다.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.miloapp.MainActivity">

<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="8dp"
tools:context=".MainActivity">

<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical" />
</RelativeLayout>
</LinearLayout>


<item_cardview> 에서는 RecyclerView 안에 그려질 아이템의 레이아웃을 설정한다. 예제에서는 Title과 Description 두개의 TextView로 구성된다.

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/card_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
card_view:cardCornerRadius="4dp"
card_view:cardBackgroundColor="@color/colorPrimary"
card_view:cardUseCompatPadding="true">

<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/text_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_marginLeft="20dp"
android:textStyle="bold"
android:textSize="36sp"
tools:text="M"/>

<TextView
android:id="@+id/text_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:textSize="15sp"
android:text="I"
android:layout_centerInParent="true"/>
</RelativeLayout>
</android.support.v7.widget.CardView>



코드  작성


public class MainActivity extends AppCompatActivity {

@BindView(R.id.recycler_view)
RecyclerView mRecyclerView;

@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);

ItemRepository itemRepository = new ItemRepository();

mRecyclerView.setLayoutManager(new LinearLayoutManager(getApplicationContext()));
RecyclerViewAdapter recyclerViewAdapter = new RecyclerViewAdapter(getApplicationContext(), itemRepository.getAllItems());
mRecyclerView.setAdapter(recyclerViewAdapter);

}
}

Main Activity에서는 RecyclerView를 Binding 하고 RecyclerView 에 LayoutManager 와 Adapter를 셋팅해주는 코드가 필요하다. 

RecyclerViewAdapter 에는 화면에 그려줄 ObjectList 를 같이 넘겨줘서 화면에 그리도록 한다. 

위 코드에서 itemRepository 에서는  List<Item> 타입을 리턴하게 된다.


@Data
@AllArgsConstructor
public class Item {
String title;
String description;
}

VO 클래스는 lombok annotaiton 을 이용하여 Setter나 Getter를 자동으로 생성하도록 하였다.



public class ItemRepository {

public List<Item> getAllItems(){
return dummyData();
}

private List<Item> dummyData(){
List<Item> dummyList = new ArrayList<>();
dummyList.add(new Item("A","apple"));
dummyList.add(new Item("B","Baggin"));
dummyList.add(new Item("C","Cati"));
dummyList.add(new Item("D","Dadday"));
return dummyList;
}

}
추후 확장을 위해서 구현해놨는데 ItemRepository는 단순히 List<Item> 형태를 리턴한다




public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.RecyclerViewHolders> {

List<Item> itemList;
private Context context;

public RecyclerViewAdapter(Context context, List<Item> itemList) {
this.itemList = itemList;
this.context = context;
}

@Override
public RecyclerViewHolders onCreateViewHolder(ViewGroup parent, int viewType) {
View layout = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_cardview, null);
RecyclerViewHolders rcv = new RecyclerViewHolders(layout);
return rcv;
}

@Override
public void onBindViewHolder(RecyclerViewHolders holder, int position) {
holder.title.setText(itemList.get(position).getTitle());
holder.description.setText(itemList.get(position).getDescription());
}
@Override
public int getItemCount() {
return this.itemList.size();
}

class RecyclerViewHolders extends RecyclerView.ViewHolder implements View.OnClickListener {

TextView title;
TextView description;

public RecyclerViewHolders(View itemView) {
super(itemView);
itemView.setOnClickListener(this);
title = (TextView) itemView.findViewById(R.id.text_title);
description = (TextView) itemView.findViewById(R.id.text_description);
}

@Override
public void onClick(View view) {
Toast.makeText(view.getContext(), ((TextView) view.findViewById(R.id.text_title)).getText() + "Item Clicked", Toast.LENGTH_SHORT).show();
}
}
}

가장 중요한 부분인 RecyclerViewAdapter 부분이다.  전체 소스는 위와 같고 아래에서 좀 더 자세히 설명 하도록 하겠다.

RecyclerView Adapter를 상속 받으면 onCreateViewHolder, onBindViewHolder, getItemCount 를 재정의(Override) 해야 한다.



public RecyclerViewAdapter(Context context, List<Item> itemList) {
this.itemList = itemList;
this.context = context;

}

 RecyclerView Adapter의 역할은 RecyclerView에 데이터를 그려주는 역할을 담당한다. 그 역할을 위해서 그릴 대상인 ItemList 를 파라미터로 전달 받았다.



@Override
public RecyclerViewHolders onCreateViewHolder(ViewGroup parent, int viewType) {
View layout = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_cardview, null);
RecyclerViewHolders rcv = new RecyclerViewHolders(layout);
return rcv;
}

onCreateViewHolder는 데이터를 담을 뷰 홀더를 생성하여 리턴한다. 이때 위에서만들었던 카드뷰 Layout을 inflate 한다. 



class RecyclerViewHolders extends RecyclerView.ViewHolder implements View.OnClickListener {

TextView title;
TextView description;

public RecyclerViewHolders(View itemView) {
super(itemView);
itemView.setOnClickListener(this);
title = (TextView) itemView.findViewById(R.id.text_title);
description = (TextView) itemView.findViewById(R.id.text_description);
}

@Override
public void onClick(View view) {
Toast.makeText(view.getContext(), ((TextView) view.findViewById(R.id.text_title)).getText() + "Item Clicked", Toast.LENGTH_SHORT).show();
}
}

뷰홀더는 Layout에 있는 Component와 Adapter 를 연결하기 위한 클래스라고 보면 이해가 쉬울 거 같다.

뷰홀더에서 각 아이템을 클릭 했을때 Action을 구현하였다.



@Override
public void onBindViewHolder(RecyclerViewHolders holder, int position) {
holder.title.setText(itemList.get(position).getTitle());
holder.description.setText(itemList.get(position).getDescription());
}

onBindViewHolder에서는 전달받은 아이템을 홀더에 값을 셋팅해주는 역할을 수행하게된다.

이때 홀더를 이용하여 값을 셋팅하면 리스트에 보여지게 된다. 


@Override
public int getItemCount() {
return this.itemList.size();
}

전체 아이템 갯수를 리턴하도록 한다. 




------ 끝 ----- 





문제상황 


- Android Project 의 테스트케이스가 많아지면서 어느순간 부터 빌드시 Out of memory 가 발생함

- 로컬 테스트 수행할때는 시간이 오래 걸리지 않았지만, 빌드서버와 로컬 Gradle task "debugBuildTest" Task 수행시에 해당 문제가 발생

- 특정 Activity 의 테스트케이스를 Ignore 했을 경우에는 이상없이 빌드가 수행됨. 이 Activity들에서는 Dialog를 Shadow로 띄워서 테스트를 함

- 빌드서버의 메모리를 높여서 띄워봤지만 문제는 해결 되지 않음



문제해결


- 문제 원인은 하나의 Process로 작업이 진행되면서 GC가 제대로 동작하지 않아서 순간적으로 많은 양의 메모리를 사용하는 테스트가 많이 수행될 경우 Memory leak 현상이 발생된 것으로 보였고, forkEvery 옵션을 주어서 새로 Process를 생성하도록 변경하고나니 Out of Memory 문제가 해결되었고 평화로워 졌다.


같은 문제를 앓고 있는 분들에게 도움이 되었으면 한다.


testOptions {
unitTests.returnDefaultValues = true
unitTests.all {
jvmArgs '-Xmx2g', '-XX:MaxPermSize=1024m', '-XX:+HeapDumpOnOutOfMemoryError'
jacoco {
includeNoLocationClasses = true
}
forkEvery = 10
}



'Programming' 카테고리의 다른 글

[JavaScript] Switch - Fall Through  (0) 2017.05.28
RecyclerView, Cardview Example  (0) 2017.05.28
안드로이드 MVC, MVP Pattern  (0) 2017.04.05
Android Clean Architecture  (0) 2017.04.03
다른 App으로 데이터 전달 하기  (0) 2017.03.28

Kotlin에 대해서 공부를 하면서 MVC와 MVP 패턴에 대해서 생각해 볼 수 있는 기회가 생겼고 정리하고자 글을 포스팅 한다.



MVC 


MVC 모델은 Spring MVC를 통해서 가장 많이 접했던 구조라 말할 수 있다.

Model , View, Controller 세가지로 나누어 정의 한다.

 

Model은 View나 Controller에서 독립되어 Object(Data)의 상태나 비지니스 로직을 담당하게 됩니다. Controller에서는 View에 모델을 통해서 정보를 전달하게 된다.

View는 사용자와의 인터렉션을 하는 영역으로 UI를 그리고 컨트롤러와 통신을 하는 역할을 맡습니다. View는 모델이나 컨트롤러와는 독립적으로 순전히 View 본연의 역할만을 수행하는 것이 좋습니다.

Controller는 Model과 View 를 묶어주는 역할을 합니다. View에서 컨트롤러로 어떠한 요청을 보내고 컨트롤러에서는 요청에 대한 처리를 진행합니다. 이때 모델에 변경이 생겼고 다시 View의 갱신이 필요한경우 컨트롤러는 다시 View에 변경된 모델을 내용을 알려주게 됩니다.

위 설명은 일반적인 MVC 패턴의 구성요소별 설명이라고 생각하면 된다.


반면에 안드로이드에서는 View 와 Controller 의 영역에 대한 구분히 모호합니다. 이와 관련된 역할을 하는것은 Activity 나 Fragment가 담당하게 되는데, 프로젝트를 진행을 하다보면 의식적으로 모델과 관련되어 있는 부분은 Repository로 분리해서 데이터를 다루게 되지만 View와 Controller 는 Activity나 Fragment에서 별도 구분없이 프로그래밍을 하게 된다.. 이러한 작업이 연속으로 이루어지다 보면  거대한 Activity나 Fragment를 만나게 됩니다. 거대한 Activity나 Fragment는 그만큼 거대한 테스트클래스가 필요하다는걸 말한다.  또한 View와 Controller의 역할을 하나의 클래스에서 하게되다보면 View 영역의 변경이 Controller에 미치게되고 그 반대또한 가능하게 된다. 그만큼 유지보수 비용이 많이 발생하게 된다.

이러 문제들을 해결하고자 MVP 모델을 사용한다.





MVP


MVC 모델에서 Model의 역할은 MVC 패턴과 같이 데이터를 저장하고 핸들링하는 부분을 담당한다.

다른점은 View 의 영역을 보다 명확히 하여. Activity 나 Fragment가 View의 일부로서 동작한다는 점이다. 엑티비티는 뷰 인터페이스를 구현하고 Presenter가 인터페이슬 가지고있게 만들어 특정한 뷰에 결합되지 않은 상태에서 가상의 View 를 통해서 독립된 유닛 테스트를 실행할 수 있게 된다.

Presenter는 인터페이스로서 MVC가 가지고있는 테스트의 어려움을 극복하고 모듈화/유연성 문제 또한 해결합니다. 명심해야 할 부분은 Presenter는 인터페이스로 종속성이 발생해서는 안되며 유스케이스를 구현하게 된다.


 

본내용은 https://github.com/android10/Android-CleanArchitecture 의 내용을 요약 정리 하였습니다.

"SHOULD NOT REINVENT THE WHEEL" 



Android Clean architecture principle



clean architecture란 다음과 같은 시스템을 생산하는 일련의 양식을 의미합니다.


  • Independent of Frameworks. 
  • Testable.
  • Independent of UI.
  • Independent of Database. 
  • Independent of any external agency. 


그림에 대한 설명 : 내부원의 경우에는 외부에 있는 원의 영역에 대해서는 생각할 필요가 없고 겹치는 영역도 존재하지 않는다. 

- Entities : 어플리케이션의 비지니스 오브젝트

- UseCases : 인터렉터라고 불리우며 엔티티간에 데이터흐름을 조정함

- Interface Adapters : 유즈케이스와 엔티티에 가장 편리한 형태로 데이터를 변환하는 역할을 한다. Presenter와 Controller가 여기에 속한다

- Frameworsk and Drivers : UI, Tools, fraemework 등을 포함




Android architecture


.친구 목록을 누르면 친구에 대한 상세 내용이 나오는 앱을 구성하는 시나리오로 Android architecture에 대해서 설명한다.

Android architecturing의 목적은  separation of concerns 이다. 비즈니스룰은 자신의 원 밖의 일에 대해서는 몰라도 된다. 따라서 비지니스룰은 외부적인 요인과는 

상관없이 테스트가 가능해야 한다.  이 목적을 이루기 위해서 프로젝트를  서로의 목적이 다르며 독립적으로 동작하는 3개의 다른 레이어로 구분했으면 한다. 


각각의 레이어는 독립성을 보장하기 위해서 독립된 데이터 모델을 가지고 있음을 언급할 필요가 있다. 



clean_architecture_android



Presentation Layer


이 레이어에서 뷰와 애니메이션과 관련된 동작들이 이루어 집니다.  MVC 나 MVVM 어떠한 패턴을 사용해도 상관은 없습니다. 그러나 fragments나 activity들은 view만 담당합니다.  UI로직을 제외하고는 어떠한 로직도 들어가서는 안되며 화면이 그려지는 모든 부분이 이곳에서 동작합니다.


이 레이어의 Interactors(유즈케이스)로 구성된 Presenters는  Android의 메인 UI thread의 밖에서 새로운 thread를 생성해서 동작하고 Callback 을 통해서 화면을 그려주게됩니다.  (https://github.com/pedrovgs/EffectiveAndroidUI/)


clean_architecture_mvp





Domain Layer


비즈니스 룰과 관련되 내용은 모두 이 레이어에서 일어나게 됩니다.  안드로이드프로젝트라면 모든 use case의 구현을 이 레이어에서 볼 수 있습니다.

이 레이어는 순수한 자바 모듈이고 안드로이드와 어떠한 디팬던시 없이 구현됩니다. 모든 비지니스 오브젝트와 외부 컴포넌트는 인터페이스로만 통신하게 됩니다. 



clean_architecture_domain




Data Layer


어플리케이션의 모든 데이터는 UserRepository구현체(Domain Layer 인터페이스)를 통해서만 가지고 올수 있으며, 팩토리를 통해 특정 조건에 따라 데이터소스를 선택하는 전략과 함께 레파지토리패턴을 사용한다. 

예를 들어 사용자 id 를 가지고 오는 경우 캐시에 사용자 id가 있으면 Disk 소스에서 가지고 오고, 그렇지 않으면 클라우드에서 조회한후 디스크 캐쉬에 저장하는 식으로 동작한다.

이때 클라이언트는 데이터가 어떻게 처리되는지(메모리에서 오든지, 클라우드에서오든지) 신경쓰지 않고 데이터를 가져다 쓰는 부분에만 집중하면 된다. 


clean_architecture_data





Error Handling


위에서는  콜백메소드를 사용했다. 데이터레파지토리의 경우에는 onResponse()와 onError() 같이 두가지 콜백 메소드를 사용했다. OnError() 는 ErrorBundle이라는 래퍼클래스를 캡슐화합니다. 이런방식은 렌터링할 프레젠테이션 레이어까지 콜백체인을 형성하기 때문에 여러가지 어려움이 생길수 있습니다. 또한 코드 가독성이 손상될 수 있습니다. 

반면에 무엇인가 잘못되면 이벤트를 던지는 이벤트 버스 시스템을 구현할 수 있습니다. 여러이벤트를 subscribe하는 가운데 세밀하게 다루지 않으면 에러를 놓치는 경우가 발생 할 수 있다.(?)



Testing


Testing과 관련하여서는 레이어 별로 다른 솔루션들을 선택했습니다.

.Presentation Layer : Android instrumentation 과 espresso를 통합 및 기능테스트를 위해서 사용했습니다.

.Domain Layer : 단위테스스트를 위해 JUnit 과 mockito를 사용했습니다.

.Data Layer : Android 와의 의존성때문에 Robolectric , Junit, mockito를 사용했습니다.





본 소스코드 : https://github.com/android10/Android-CleanArchitecture

  • presentation: 프레젠테이션 레이어를 구현한 안드로이드 모듈
  • domain: 안드로이드 디팬던시가 없는 자바 모듈
  • data: 모든 데이터 조회가 가능한 안드로이드모듈
  • data-test: 데이터 레이어를 위한 테스트 모듈.




Conculution


아키텍처가 모든 능사는 아니다. 그러나 위와 같은 방법을 사용한다면 다음과 같은 장점은 얻을수 있다.

  • 쉬운 유지보스
  • 쉬운 테스트
  • 높은 모듈 응집도
  • 낮은 결합성











+ Recent posts