소프트웨어에서 이름은 어디에서든 사용합니다.

변수에도 이름을 붙이고, 함수, 인수, 클래스, 패키지 등에 이름을 붙입니다. 또한, 파일, 디렉터리, war, jar 등 여기저기 이름을 많이 붙이네요. 


이렇듯 이름을 짓는 일은 많으며, 좋은 이름을 지으려면 시간이 걸리지만 좋은 이름으로 절약하는 시간이 더 많습니다. 

좋은 이름을 지으면, 자신을 포함해 코드를 읽는 사람이 행복해 집니다.


이번 포스팅에서는 이름 잘 짓는 몇 개의 규칙에 대해 다뤄보려 합니다.


1. 의도를 분명히 밝혀라.


많은 개발자들이 "의도가 분명하게 이름을 지으라" 는 이야기를 많이 합니다.

의도가 분명한 이름은 정말로 중요합니다.


변수나 함수 클래스 이름은 다음과 같은 굵직한 질문에 모두 답해야 합니다.


- 왜 존재해야 하나?


- 수행 기능은?


- 사용 방법은?


따로 주석이 필요하다면, 의도를 제대로 나타내지 못함을 의미합니다.



1
int d;  // 경과 시간 (단위: 날짜)
cs


이름 d 는 아무 의미도 드러나지 않습니다. 해당 변수가 표현하는 논리적인 이름이 필요합니다.

아래처럼 말이죠.


1
int daysInAppraisalDeadline; 
cs



의도가 드러나는 이름을 사용하면, 코드 이해와 변경이 쉬워집니다.

한번, 다음 함수를 주목해주세요.


1
2
3
4
5
6
7
8
9
10
11
public List<int[]> getThem() {
    ArrayList<int[]> list = new ArrayList<>();
        
    for (int[] n : theList) {
        if (n[0== 4) {
            list.add(n);
        }
    }
 
    return list;
}
cs


함수 function 은 복잡한 문장은 없어 보입니다. 하지만, 무슨 일을 하는지 짐작하기 어렵습니다. 


문제는 코드의 단순함이 아닌 함축성에 있습니다. 코드 맥락이 명시적으로 드러나지 않습니다.

위에서 제공하는 코드는 아래와 같은 질문을 남깁니다.


1. theList 에 무엇이 들어있는가?


2. theList 에서 0번째 값이 어째서 중요한가?


3. 값 4는 무엇인가?


4. 함수가 반환하는 list 는 어떻게 사용되는가?


코드만 보고는 위 질문에 대한 답을 알 수 없습니다. 

하지만, 다음과 같이 코드를 변경하면 어떨까요? 아래 코드는 단순함에 변화를 주지 않고, 이름만 변경했습니다.


1
2
3
4
5
6
7
8
9
10
11
public List<int[]> getFlaggedCellS() {
    ArrayList<int[]> flaggedCellS = new ArrayList<>();
 
    for (int[] cell : gameBoard) {
        if (cell[STATUS_VALUE] == FLAGGED) {
            flaggedCellS.add(cell);
        }
    }
 
    return flaggedCellS;
}
cs


이 코드를 보고, 위의 질문에 답을 해 볼 수 있습니다.


1. theList 에 무엇이 들어있는가?

    -> gameBoard 로 변경되면서, 게임판임을 알 수 있습니다.


2. theList 에서 0번째 값이 어째서 중요한가?

    -> 0 은 STATUS_VALUE 라는 값을 통해, 셀의 상태를 의미함을 알 수 있습니다.


3. 값 4는 무엇인가?

-> 4라는 상수에 FLAGGED 라는 이름을 붙임으로, 플래그가 된 상태임을 알 수 있습니다.


4. 함수가 반환하는 list 는 어떻게 사용되는가?

-> 반환하는 list 에 flaggedCellS 라는 이름을 붙임으로, 플래그 된 셀 목록을 구한다는 것을 알 수 있습니다.


각 개념에 이와 같이 이름만 붙여도 충분히 나아진 것을 알 수 있습니다. 

단순함은 변하지 않았지만, 코드는 더욱 명확해졌습니다.


조금 더 리팩토링을 해볼 수 있을 것 같습니다. 

int 배열 사용 대신 Cell 이라는 클래스를 만들고, Cell::isFlagged 라는 메소드를 사용하여 FLAGGED 라는 상수를 감춰도 좋을 것 같습니다.


1
2
3
4
5
6
7
8
9
10
11
public List<Cell> getFlaggedCellS() {
    ArrayList<Cell> flaggedCellS = new ArrayList<>();
 
    for (Cell cell : gameBoard) {
        if (cell.isFlagged) {
            flaggedCellS.add(cell);
        }
    }
 
    return flaggedCellS;
}
cs



2. 그릇된 정보를 피하라.


프로그래머는 코드에 그릇된 정보를 남기면 안됩니다.

그릇된 단서는 코드의 의미를 흐리기 때문이죠.


예를들어, 우리는 resumeList, appraisalList 와 같이 목록을 나타내는 변수명을 짓습니다.

프로그래머에게 있어 List 는 자료구조를 나타내는 특수한 의미이며, 해당 변수들이 List가 아니라면, 그릇된 정보를 제공하는 셈입니다.

간단하게 resumeS(s가 눈에 안뛸 수도 있기 때문에 대문자로 강조..), appraisalGroup 등으로 명명하는 것을 책에서는 추천하고 있습니다.


또한, 서로 흡사한 이름을 쓰지 않도록 주의할 것을 말합니다.

한 모듈에서는 getStringForHandlingOfResume 라 하고, 다른 곳에서 getStringForStorageOfResume  라 하면 두 모듈의 차이를 알 수 없습니다.


조금은 유머같지만, 알파벳 l, o 들의 문자들은 지양하는 것이 좋습니다.


1
2
3
4
5
int i = l;
int x = 1;
        
int f = o;
int fx = 0;
cs


폰트나 IDE 에 따라, 위의 코드는 헷갈릴 수 있습니다.



3. 의미 있게 구분하라.


종종, 어떤 코드들은 이름에 연속된 숫자나 불용어(의미없는 글자)를 추가하는 경우가 있습니다.

이런 코드들은 모듈의 가독성을 꽤 나쁘게 만듭니다.


예를들어, 한번 아래 메소드 서명을 볼 수 있습니다. 

연속적인 숫자를 덧붙인 이름(a1,a2... aN) 은 그릇된 정보를 제공하지도, 어떤 의도도 주지 못하는 이름입니다.


1
public void copyPropertise(Object a1, Object a2)
cs


위의 서명을 source, destination 등 의미있는 이름을 사용하는 것으로도, 충분히 어떻게 사용하면 되는지 나타낼 수 있습니다. 


1
public void copyPropertise(Object source, Object destination);
cs


불용어를 사용하는 경우 역시, 어떤 정보를 주지 못하며 혼란을 줄 수 있습니다.

Product 라는 클래스가 있고 ProuductInfo, ProductData 라 부른다면, info 나 data 는 의미없는 정보입니다.

a, an, the 등의 접두어 역시 불용어입니다.

그렇다고, 접두어나 접미어의 사용이 나쁘다는 것이 아닙니다. product 라는 이름이 있다고, theProduct 라는 이름을 짓지 말자는 것입니다.


변수 이름에 variable 을 쓰거나, 테이블에 table 이라는 단어 역시 마찬가지로 불용어 입니다.

name 과 nameString 은 다를 것이 없습니다. name 이 숫자가 될 일은 없을테니까요...



4. 인터페이스와 구현 클래스


예를들어, 커피를 제작하는 Abstract Factory를 구현한다고 가정해보죠.


이 절과 관련은 없지만, Abstract Factory 가 무엇인지 궁금하다면, 이 곳을 참고! :-)



이 패턴에 따라 클래스를 그려보면, 인터페이스와 구현체로 나눠 제작하게 될 것입니다.

이 때, 인터페이스의 이름을 어떻게 할까요? ICoffeeFactory? CoffeeFactory? 


책의 저자는 옛날 코드에서 많이 보이는 인터페이스에 붙이는 접두어(I)에 대하여, (잘해봤자) 주의를 흐트리고, (나쁘게는) 과도한 정보를 제공한다고 주장합니다.

굳이 다루는 클래스가 인터페이스라는 사실을 알리고 싶지 않고, 사용자는 CoffeeFactory 라고만 생각하면 좋겠다고 합니다.

책의 저자는 차라리 CoffeeFactoryImp 가 ICoffeeFactory 보단 좋다고 말합니다.



5. 클래스 이름


클래스 이름과 객체 이름은 명사나 명사구가 적합합니다.

Resume, Account 등이 좋은 예입니다. Data, Info 등과 같은 단어는 피하고 동사는 사용하지 않습니다.



6. 메소드 이름


메소드 이름은 동사나 동사구가 적합합니다. 

postPayment, save, deletePage 등이 적합하며, javabean 규칙에 따라 접근자(getXXX), 변경자(setXXX), 조건자(isXXX) 를 사용하길 권장합니다.


생성자 같은 경우는 중복정의를 하기 보단 팩토리 메소드를 사용하길 권장합니다.

팩토리 메소드는 직접적으로 생성자를 이용하지 않고, 어떤 상태를 가진 객체를 생성하는 것을 의미합니다.


예를들어, 중복정의된 생성자 보단,


1
Complex fulcrumPoint = new Complex(23.0);
cs


아래와 같이, 의미를 가진 이름으로 표현된 팩토리 메소드를 이용하는 것이 좋습니다.


1
Complex fulcrumPoint = Complex.FromRealNumber(23.0);
cs



7. 한 개념에 한 단어를 사용하라.


추상적인 개념 하나에 단어 하나를 선택하고 이를 고수하기를 권장합니다.


예를들어, 똑같은 일을 수행하는 접근자 메소드를 클래스마다 get, fetch 등 제작기 부르면 혼란스러워 집니다.

메소드 이름은 독자적이고 일관적이어야 합니다. 그래야 주석을 뒤져보지 않고 프로그래머가 올바른 메소드를 선택할 수 있습니다.


마찬가지로 동일 코드 기반에서 controller, manager, driver 를 섞어쓰는 것을 지양합니다.

DeviceManager 와 DeviceController 는 근본적으로 다를까요? 둘 다 Controller 가 아닐지? 혹은 둘 다 Manager 가 아닐까요? 

이름이 다르면 독자는 당연히 클래스도 다르고 타입도 다르다고 생각할 것입니다.



8. 해법 영역에서 가져온 이름을 사용하라.


코드를 읽는 사람 역시도 프로그래머입니다. 그렇기 때문에 전산용어, 패턴명, 알고리즘 명 등은 써도 무방합니다.

Factory 패턴에 익숙한 개발자는 CoffeeFactory 를 금방 이해하며, JobQueue 역시 특정 일을 하는 큐임을 인지할 수 있습니다.



9. 문제 영역에서 가져온 이름을 사용하라.


문제 영역에서 가져온 이름을 사용하면, 해당 분야의 전문가에게 의미를 물어 문제를 이해할 수 있습니다.

문제 영역의 개념과 깊은 문제를 해결하는 코드라면, 문제 영역에서 이름을 가져와야 합니다.



10. 의미있는 맥락을 추가하라.


스스로 의미가 분명한 이름이 없진 않습니다. 

하지만 대다수 이름은 분명하지 못하며, 클래스, 함수 등에 공간을 넣어 맥락을 부여합니다.

모든 방법이 실패한다면, 마지막 수단으로 접두어를 사용합니다.


예를들어, 아래 코드를 확인해봅시다.


1
2
3
4
5
6
7
8
public class TestClass {
 
    String firstName, lastName, streetName, city, state, zipCode;
 
    public static void main(String[] args) {
 
    }
}
cs


변수 명을 본다면, 해당 변수들이 주소를 나타낸다는 것을 알 수 있습니다.

하지만 어떤 메소드가 오직 state 하나만 사용한다면, 그 메소드만 보고 주소라는 사실을 바로 알긴 쉽지 않을 것 같아 보입니다.


그래서 다음과 같이 addr 이라는 접두어를 사용하여, addrState 라 하면 조금 더 의미가 명백해집니다. 


1
2
3
4
5
6
7
8
9
10
11
12
public class TestClass {
 
    String addrFirstName, addrLastName, addrStreetName, addrCity, addrState, addrZipCode;
 
    public static void main(String[] args) {
 
    }
 
    public String getAddrState() {
        return addrState;
    }
}
cs


하지만, Address 라는 클래스를 제작한다면 변수가 조금 더 큰 개념에 속한다는 것이 컴파일러에게도 명백해집니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
 * 주소 클래스
 *
 * Created by Doohyun on 2018. 1. 7..
 */
public class Address {
    String firstName;
    String lastName;
    String streetName;
    String city;
    String state;
    String zipCode;
}
 
cs



11. 불필요한 맥락을 없애라.


사내에서 개발한 InJob 이라는 어플리케이션이 있었습니다.

저는 이 프로젝트에서 모든 유틸 클래스 및 기본이 되는 클래스들에 INJOB 이라는 접두어를 붙인 적이 있었습니다.


아래와 같이 말이죠...


1
2
3
4
5
6
7
8
/**
 * inJob 에서 구현된 리소스 유틸
 *
 * Created by Doohyun on 2018. 1. 7..
 */
public class InJobResourceUtil {
    private InJobResourceUtil() {}
}
cs


어차피, InJob 이라는 안드로이드 프로젝트에서만 독릭접으로 사용될 코드이며, 이 클래스의 명은 ResourceUtil 로 변경하는 것이 옳다고 생각합니다.


비슷하지만, 조금은 다른 케이스로는 다음과 같은 사례를 볼 수 있습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
/**
 * inJob 에서 사용하는 코드 정의
 *
 * Created by Doohyun on 2018. 1. 7..
 */
public class InJobCodeDefinition {
 
    public static class APPLICATION_TYPE {
        public static final String APP = "INJOB_APPLICATION_TYPE_APP";
        public static final String WEB = "INJOB_APPLICATION_TYPE_WEB";
    }
}
 
cs


위와 같은 경우 코드 클래스 자체가 다른 응용에서 사용할 수 있기 때문에, InJobCodeDefinition 이라는 이름을 사용하고 있습니다.

또한 코드의 value 역시 다른 솔루션과 구분을 짓기 위해 "INJOB_APPLICATION_TYPE_APP" 을 사용하지만, 클래스와 상수명에는 접두어가 없고 계층적인 구조를 사용하고 있습니다.

개념적으로 불필요한 맥락과 필요한 맥락을 나누고, 개념에 속하도록 제작이 된 좋은 사례인 듯 합니다. :-)



책의 저자는 위와 같은 규칙을 소개하며, 마치면서 다음과 같은 말을 했습니다.


사람들은 이름을 바꾸지 않으려는 이유는 다른 개발자가 반대할까 두려워서다. 

여느 코드 개선 노력과 마찬가지로 이름 역시 나름대로 바꿨다가는 누군가 지책할지도 모른다. 

그렇다고 코드를 개선하려는 노력을 중단해서는 안 된다.


이 문구를 읽었을 때, 3년차가 되면서 협업이라는 그늘 안에서 보수적이라는 두려움을 외면 시 했던 된 저를 보게 되었습니다.

남들이 안좋게 작성했기 때문에, 어쩔 수 없다는 핑계로 안좋은 코드를 이어갔었던 듯 합니다.


더불어 제가 코드를 작성하는 안좋은 버릇이 충분히 이 절에서 꽤 많이 보였습니다. 하하...

충분히 반성을 하게 된 포스팅인 듯 합니다.


이 글이 저처럼 조금 더 여러분들이 나아지는 것에 도움이 되었으면 좋겠습니다.


Clean Code 클린 코드
국내도서
저자 : 로버트 C. 마틴(Robert C. Martin) / 박재호,이해영역
출판 : 인사이트 2013.12.24
상세보기



반응형

'개발이야기 > Clean Code' 카테고리의 다른 글

깨끗한 코드  (1) 2018.01.07
[프롤로그] Clean Code  (0) 2018.01.05
Posted by N'

Clean Code 의 첫 도입부는 책 이름답게 "깨끗한 코드" 라는 주제를 다루고 있습니다.

첫번째 장에서는 깨끗한 코드의 중요성과 유명하고 노련한 프로그래머들이 생각하는 깨끗한 코드에 대한 생각들이 담겨 있었습니다.

이번 포스팅에선, 위 내용에 대한 주관적인 요약을 다뤄보려 합니다.



1. 코드가 존재하리라.


최근 프로그램을 제작하기 위한 많은 기술들이 등장하고 있으며, 아마 프로그램을 제작하는 방법은 점점 편리해졌습니다.

제작하는 방법이 쉬워지는 만큼, 개발자들은 코드보단 요구사항에 조금 더 집중해야한다고 생각하는 의견들도 많이 있습니다.

생각해보면, 새로 등장하는 많은 언어들은 어렵거나 귀찮은 내용을 점점 추상화하고 있습니다.

(어느덧 C++ 이후 부터의 언어는 포인터를 찾아볼 수 없고, 병렬 처리같은 복잡한 수준의 구현은 함수형 등으로 쉽게 나타낼 수도 있습니다.)


이러한 구현과정의 추상화는 계속될 것이며, 우리는 요구사항에 조금 더 집중할 수 있을 것입니다. 

그렇지만 요구사항을 나타내는 구체적인 방법은 결국 코드이며, 코드는 계속 우리 곁에 남아있을 것입니다.



2. 나쁜 코드


협업을 하게 되면, 동료들의 많은 코드를 볼 수 있습니다. 

또한, 이 코드를 사용하거나 고쳐야할 상황도 많으며 종종 누군가가 작성했던 나쁜 코드들에게 시달렸을 것입니다.

(아마 종종 누구가는 자신일 확률이 높을 수도 있습니다. ㅡㅡ^)


나쁜 코드는 당연한 이야기이지만 개발 속도를 크게 떨어트립니다. 

간단한 변경이란 없고, 코드를 변경할 시 여기저기서 문제가 발생할 것입니다. 

이러한 일들은 팀 생산력을 크게 저하시킬 것입니다.


생산력을 올리고자, 새로 인원을 충원해도 그들은 설계의도를 모르기 때문에 더 나쁜 코드가 생산될 수 있죠.

덕분에 생산력은 0를 향해 내려갈 것입니다.



 

이 상황이 계속되면, 개발자들은 재설계를 요구할 수 밖에 없습니다. 

모듈 하나 추가할 때마다 너무 많은 시간이 들며, 이곳 저곳 버그가 많다면 계속 헝겊을 짜집는 것보다는 새로 만들기를 희망합니다.

관리자들 역시 재설계에 자원을 쏟기 싫지만, 생산성이 0이기에 어쩔 수 없이 허락을 할 것입니다.


이제, 개발자들은 두 팀으로 나눠집니다. 

기존 시스템을 유지할 인력과 새로 재개발을 할 타이거 팀으로 말이죠. 

타이거 팀은 기존 시스템을 대체할 새 시스템을 내놓아야 합니다. 또한 기존 시스템에 추가되는 변경점도 따라잡아야 하죠.

이 상태는 오랫동안 이어질 수 있습니다. 

오랫동안 이어진다면 기존 타이거팀은 모두 떠났고, 또 다른 인력이 새 시스템을 설계 하겠다고 할 것입니다. 

(현재 시스템 역시 엉망이기 때문이겠죠..)


위의 이야기는 책에 등장하는 경험담이며, 현재 제가 재직하고 있는 회사에서도 겪고 있습니다...  ㅜㅡㅜ

깨끗한 코드를 만드는 노력은 비용 절감뿐 아니라, 전문가로써 살아남는 방법이라 주장하고 있습니다.



3. 태도


나쁜 코드가 심각한 장애물이라는 것은 개발자라면 많이 공감을 할 것입니다.

왜 우리들의 코드는 이렇게 되었을까요?


요구사항이 원래 설계를 뒤집는 방향으로 변해서? 일정이 촉박해서? 멍청한 관리자와 조급한 기획자 때문에?

이 책에서는 이러한 잘못은 모두 프로그래머에게 있다고 합니다.


관리자와 기획자는 우리에게 현실성을 자문하고, 도움을 요청합니다. 

우리에게 정보를 구하지 않더라도, 우리는 적극적으로 그들에게 정보를 제공해야 합니다.

우리는 프로젝트에 가장 깊게 관여하고 있으며, 프로젝트 실패는 우리에게 커다란 책임이 있습니다. 나쁜코드가 초래하는 실패라면 더더욱 책임이 큽니다.


"아니, 잠깐만요.. 상사가 시키는 대로 하지 않으면 짤린다구요!" 우리의 의견은 보통 이럴 텐데 말이죠..

하지만, 대다수의 관리자들은 진실을 원하며, 일정에 쫓기더라도 좋은 코드를 원합니다. 

관리자들이 일정을 밀어붙이는 것은 그들의 책임이며, 좋은 코드를 사수해야하는 것은 우리들의 일입니다.


이 책에서는 한가지 비유를 했습니다.


자신이 의사라 가정하자. 

어느 환자가 수술전에 손을 씻지 말라고 요구한다. 시간이 너무 오래걸리니깐?

하지만, 의사는 단호하게 거부한다. 왜? 질병과 감염의 위험은 환자보다는 의사가 잘 아니까. 

환자 말을 그대로 따르는 행동(범죄일 뿐만 아니라)은 전문가 답지 못하다.


개발자 역시, 나쁜코드의 위험을 이해하지 못하는 관리자 말을 그대로 따르는 것은 전문가 답지 못할 것입니다.



4. 깨끗한 코드란?


나쁜 코드는 이와 같이 나쁘며, 빨리 가려면 깨끗한 코드를 유지해야 한다고 인정해야 한다고 가정합시다.

그렇다면, 깨끗한 코드는 어떻게 작성할까요?


이 책에서는 깨끗한 코드를 구현하는 행위는 그림을 그리는 것과 같다고 비유합니다.

대부분의 사람들은 그림이 잘그렸는지 엉망인지 알고 있습니다. 

하지만, 그림을 구분하는 능력이 그림을 잘 그리는 능력은 아닙니다


깨끗한 코드를 작성하려면 '청결' 이라는 어렵게 습득한 코드감각을 활용해 자잘한 기법을 이용하는 절제와 규율이 필요합니다. 나쁜 모듈을 보면 좋은 모듈로 개선할 방안을 떠올리며, 최고 방안을 선택한 후 여기서 거기까지 이동하는 경로를 세웁니다.


즉, 깨끗한 코드를 작성하려면 코드 감각을 익혀야 하며 그 전에 깨끗함이 무엇인지 알 필요가 있습니다.


깨끗한 코드의 정의는 매우 다양합니다. 프로그래머 수만큼 말이죠..

그래서, 이 책에서는 유명하고 노련한 프로그래머들이 말하는 깨끗한 코드에 대한 의견을 소개했습니다.


이번 절에서는 이 의견들 중 인상깊은 내용에 대한 소개를 하고자 합니다.


- 깨끗한 코드는 잘 읽혀야 한다.


많은 의견 중 공통적인 가장 첫번째 의견은 잘 읽히는 코드여야 한다고 합니다.

잘 읽히는 코드는 우아하며, 보기에 즐거워야 합니다. 

잘 쓴 문장처럼 잘 읽히며, 설계자의 의도를 숨기지 않습니다. 추측이 아닌 사실에 근거해야 하며, 필요한 내용만 담아야 합니다.

또한, 표현력 역시 명확해야 할 것입니다. (이름짓기 등..)


이와 같이 깨끗한 문장은 작성자와 구독자 모두 읽기 쉬워야 하며, 고치기도 쉬워야 합니다.



- 깨끗한 코드는 한가지 일을 제대로 한다.


수많은 소프트웨어 원칙은 이 간단한 교훈 하나로 귀결됩니다.

나쁜 코드는 너무 많은 일을 하려 애쓰다가 의도가 뒤섞이고 목적이 흐려집니다. 


깨끗한 코드는 한가지에 '집중'하며, 각 함수, 클래스, 모듈은 주변 상황에 현혹되거나 오염되지 않은 채 한 길만 걷는다고 합니다.


'메소드 추출' 과 같은 리팩토링은 이 교훈을 따르기 위한 방법입니다.




- 깨끗한 코드는 짐작했던 기능을 그대로 수행한다.


이 내용은 매우 당연하지만, 심오한 내용입니다. 

하지만, 짐작했던 그대로 수행하는 모듈은 생각보다 많지 않을 것입니다. 

(헷갈리고, 모듈끼리 복잡하게 엉켜있고, 또는 엉뚱한 기능도 수행하고.. ㅜㅡㅜ)


깨끗한 코드는 읽으면서 놀랄 일이 없어야 합니다. 

각 모듈은 다음 무대를 준비하며, 다음에 벌어질 상황이 보여야 합니다. 

즉, 코드를 읽으면서 짐작했던 기능을 각 루틴이 그대로 수행한다면 깨끗한 코드라 불러도 된다고 합니다.



실제 책의 내용에는 조금 더 공감되고 좋은 내용이 더 있었습니다.

하지만, 모든 내용은 결국 모듈을 한 가지 일만 하도록 최대한 작게 구현하며, 의도를 잘 표현할 수 있도록 세심한 주의 할 것을 말합니다.


지막으로 한가지 생각해 볼 것은 코드를 잘짜는 것이 전부가 아니라는 것입니다. 

시간이 지나도 언제나 깨끗함을 유지하는 것이 더 중요한 듯 합니다.


'소프트웨어 장인정신' 에서도 언급하는 보이스카우트 규칙은 이 내용을 말하고 있습니다.


"캠프장은 처음 왔을 때보다 더 깨끗하게 해놓고 떠나라!"


시간이 지날수록 코드가 좋아지는 프로젝트에서 작업한다면 얼마나 좋을까요? 

사실 전문가라면, 너무도 당연한 이야기입니다.


'지속적인 개선이야말로 장인 정신의 본질'이라는 저자의 말을 남기며, 이번 포스팅을 마칩니다.


Clean Code 클린 코드
국내도서
저자 : 로버트 C. 마틴(Robert C. Martin) / 박재호,이해영역
출판 : 인사이트 2013.12.24
상세보기














반응형

'개발이야기 > Clean Code' 카테고리의 다른 글

의미 있는 이름  (6) 2018.01.07
[프롤로그] Clean Code  (0) 2018.01.05
Posted by N'

안녕하세요. 블로그 주인장 N` 입니다.


개발자에게 있어서 코드를 작성한다는 것은 어떤 의미라 생각하시나요?

제가 회사에서 존경하는 선배님 중 한 분은 이런 말씀을 하셨습니다. 


"코드를 작성하는 것은 저자가 글을 작성하는 것과 같다고 볼 수 있어.."


2년전에 들었던 저 이야기는 어느덧 3년차 개발자가 된 제게 많은 의미를 되돌아 보게 하였습니다.


그동안 제가 회사 사람들과 공부했던 OOP(객체지향)의 개념이나, 무엇을 구현할지에 조금 더 집중할 수 있는 테크닉을 볼 수 있던 함수형 프로그래밍 등은 모두 코드의 품질을 올리기 위한 "기본에 조금 더 충실하자."는 철학이 있었습니다.

코드 품질을 올리겠다는 것은 유지보수에 좋은 코드를 작성 하자는 의미로도 볼 수 있을 것 같은데요.

같이 일하는 모두가 행복(?)하려면, 조금 더 잘 읽히는 코드를 작성하는 것은 정말 중요해 보입니다. 


다시 한번, 위의 이야기를 빌리면 우리는 누군가에게 코드를 제공하는 저자이며 누군가의 코드를 읽는 독자가 될 수 있습니다. 이 점은 아마 코드를 펼쳐보았을 때, 그 이야기에 잘 빠져들 수 있도록 글쓰기 연습을 할 필요성을 느껴볼 수 있습니다.


이번에는 이 연습을 위한 카테고리로 "Clean Code" 를 주제로 합니다.

누구나 처음부터 좋은 글을 쓸 수 없고, 저 역시도 좋은 글을 쓰기는 쉽지 않은 듯 합니다.

냄새나는 나쁜 버릇을 고칠 수 있도록, 다시 한번 기본으로 돌아가며 포스팅을 해보겠습니다. ^^;


많이 봐주세요 :-)


Clean Code 클린 코드
국내도서
저자 : 로버트 C. 마틴(Robert C. Martin) / 박재호,이해영역
출판 : 인사이트 2013.12.24
상세보기




반응형

'개발이야기 > Clean Code' 카테고리의 다른 글

의미 있는 이름  (6) 2018.01.07
깨끗한 코드  (1) 2018.01.07
Posted by N'

안녕하세요. N` 입니당.


이번 포스팅 에서는 드디어 길고 길었던 "패턴이야기"의 마지막을 장식합니다.


마지막으로 다루는 패턴은 "컴파운드 패턴 (Compound Pattern)" 으로, 이 패턴은 여태까지 배웠던 패턴들의 종합이라 볼 수 있습니다.

 

컴파운드 패턴은 


일반적으로 자주 생길 수 있는 문제를 해결하기 위한 용도로, 

2개 이상의 패턴을 결합해서 사용하는 것


의미합니다.


즉, 이 패턴의 사용한다는 것은 여태까지 배웠던 여러 패턴들을 결합해서 특정 문제를 해결하는 것을 의미하며, 대표적으로 꽤 유명한 패턴 중 하나인 MVC(Model-View-Controller) 패턴은 이러한 컴파운드 패턴에 속합니다.


오늘 리뷰는 요구사항을 살펴보며 그에 따라 여러 패턴을 사용하게 되는 예제와 함께 MVC 패턴 내부의 컴파운드 원리를 간략하게 알아보려 합니다.

(사용하는 예제 및 자료는 제게 OOP 를 가르쳐 주셨던 한국기술교육대 김상진 교수님의 자료를 참고하였습니다.)



1. 오리꽥꽥 시뮬레이터!


요구사항은 간단합니다.

여러가지 특징을 가진 오리들을 제작하고, 오리들이 꽥꽥하며 우는 시뮬레이터 하는 것이죠.


여러 오리를 제작하기 위해, 첫 번째로 설계한 UML 은 아래와 같습니다. (코드 대신 UML 로 대치...)

 

 

 

이 UML 에 따라 제작된 클래스를 시뮬레이션 하는 프로그램을 저는 다음과 같이 제작하였습니다.


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
/**
 * 오리 시뮬레이터 정의.
 *
 * Created by Doohyun on 2017. 11. 19..
 */
public class DuckSimulator {
 
    /**
     * 테스트 클래스 제작.
     *
     * @param args
     */
    public static void main(String[] args) {
        DuckSimulator duckSimulator = new DuckSimulator();
        duckSimulator.simulate();
    }
 
    /**
     * 시뮬레이터 작성.
     *
     * <pre>
     *     이 곳에 비지니스 시뮬레이션 기능을 넣을 것!
     * </pre>
     */
    public void simulate(){
        Duck rubberDuck = new RubberDuck();
        simulate(rubberDuck);
    }
 
    /**
     * 오리를 넣어 시뮬레이션.
     *
     * @param duck
     */
    private void simulate(Duck duck){
        duck.quack();
    }
}
 
// CONSOLE RESULT
// 삐이익~~ 
cs



2. 오리가 아닌 다른 동물을 테스트!


테스트 클래스는 현재 잘 작동하고 있습니다만, 오리와 비슷한 성격을 가진 거위 클래스를 이 테스트에 포함하고 싶습니다.


거위 클래스 명세는 다음과 같습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
 * 거위 클래스
 *
 * Created by Doohyun on 2017. 11. 19..
 */
public class Goose {
 
    /**
     * 거위의 울음.
     */
    public void honk() {
        System.out.println("Honk");
    }
}
cs



해당 테스트 클래스에서 거위를 사용하려면, 오리와 호환을 맞춰야할 것으로 보입니다. 

좋은 방법이 있을까요? 여러분은 알고 있을 것이라 생각합니다. 


아래 패턴을 기억해 보죠.



제작한 거위 어댑터는 아래와 같습니다.


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
/**
 * 거위를 오리로 호환시켜주는 클래스
 *
 * Created by Doohyun on 2017. 11. 19..
 */
public class GooseAdapter extends Duck {
 
    private Goose goose;
 
    public GooseAdapter(Goose goose) {
        this.goose = goose;
    }
 
    @Override
    public void quack() {
        goose.honk();
    }
}
 
// TEST CLASS
public class DuckSimulator {
    /**
     * 시뮬레이터 작성.
     *
     * <pre>
     *     이 곳에 비지니스 시뮬레이션 기능을 넣을 것!
     * </pre>
     */
    public void simulate(){
        // 거위를 오리로 호환하여, 테스트
        Duck goose = new GooseAdapter(new Goose());
        simulate(goose);
    }
}
 
// CONSOLE RESULT
// Honk
cs



3. 오리들의 꽥꽥한 횟수 구하기!


추가된 요구사항은 시뮬레이션을 한번 할 때, 전체 오리들이 꽥꽥한 횟수를 구해야하는 것입니다. 

이를 구현하기 위한 방법으로 static 변수를 이용하고자 합니다.


구현 알고리즘으로 떠오르는 것은 quack 메소드가 호출될 때마다 해당 변수를 증가시키면 되지만, 이를 위해서는 Duck 을 구현한 모든 클래스를 손봐야하는 것으로 보입니다. (많이 고쳐야 함.. 짜증...)

(Duck::quack 자체를 추상 메소드에서 일반 메소드로 변경하고 카운트를 증가시켜도 되지만, 이를 위해서는 상속받은 모든 오리클래스들이 quack 을 재정의할 때 super.quack 을 반드시 사용하도록 모두 고쳐야 합니다.)


이를 위해서 제안하는 방법은 꽥꽥한 횟수만을 관리하는 클래스를 제공하는 것을 목표로 합니다.

요구사항인 Duck::quack 을 요청했을 때 카운트가 증가해야하는 재정의가 필요하며, 이는 편의에 맞게 상속을 우회하는 패턴을 생각해 볼 수 있습니다.


우리는 이런 방법론을 배운적이 있습니다.



그렇습니다. 횟수를 관리하는 장식자를 만들 것입니다.

저는 아래와 같이 장식자를 제작하였고, 그에 따라 시뮬레이터 역시 변경하였습니다.


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
/**
 * 꽥꽥한 횟수를 관리하는 장식자
 *
 * Created by Doohyun on 2017. 11. 19..
 */
public class QuackCountDecorator extends Duck{
    private Duck duck;
 
    private static int QUACK_COUNT = 0;
 
    public QuackCountDecorator(Duck duck) {
        this.duck = duck;
    }
 
    @Override
    public void quack() {
        // 오리가 울 때마다, 카운트를 관리하도록 처리.
        ++QUACK_COUNT;
 
        duck.quack();
    }
 
    /**
     * 오리가 꽥꽥한 횟수 출력.
     *
     * @return
     */
    public static int GetQuackCount() {
        return QUACK_COUNT;
    }
}
 
/**
 * 오리 시뮬레이터 정의.
 *
 * Created by Doohyun on 2017. 11. 19..
 */
public class DuckSimulator {
 
     /**
     * 시뮬레이터 작성.
     *
     * <pre>
     *     이 곳에 비지니스 시뮬레이션 기능을 넣을 것!
     * </pre>
     */
    public void simulate(){
 
        // 고무오리 테스트.
        {
            Duck rubberDuck = new QuackCountDecorator(new RubberDuck());
            simulate(rubberDuck);
        }
 
        // 거위를 오리로 호환하여, 테스트
        {
            // 장식자로 치장.
            Duck goose = new QuackCountDecorator(new GooseAdapter(new Goose()));
            simulate(goose);
        }
 
        System.out.println(String.format("꽥꽥 횟수 출력 : %d회", QuackCountDecorator.GetQuackCount()));
    }
}
 
// CONSOLE RESULT
// 삐이익~~
// Honk
// 꽥꽥 횟수 출력 : 2회
 
cs



4. 장식자 생성의 불편함, 이를 위한 객체 생성의 캡슐화!


장식자를 이용하여, 꽥꽥 횟수에 대한 관심을 한 곳으로 몰아 버린 것은 적절한 선택인 듯 합니다.


하지만, 그에 대한 비용으로 불편한 API 사용이란 대가를 치뤄야 합니다.

대표적으로 주목할 코드는 아래의 어댑터와 장식자를 같이 사용한 사례입니다.


1
 Duck goose = new QuackCountDecorator(new GooseAdapter(new Goose()));
cs


카운트를 모든 오리에 대해서 관리해야 한다면, 매번 위와 같이 장식자를 붙여줘야 할 것으로 보이는데요. 

차라리, 해당 방식으로 객체를 생성해야할 일이 많다면 객체생성 방법 자체를 캡슐화하는 것도 방법으로 보입니다.


특정 목적에 맞게 객체를 생산해주는 클래스를 팩토리라 하며, 카운트에 대한 관심을 지원할 수 있는 팩토리 클래스를 제작해 봅시다.

(이에 대한 자세한 내용은 아래에서 참고!)




이 예제에서는 여러 타입의 객체를 목적에 맞게 생산해주는 추상 팩토리(Abstract Factory) 를 사용하였으며,

그에 대한 코드는 아래와 같습니다.


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
/**
 * 카운트 뿐만이 아닌 특정 관심사에 따라 팩토리를 생성할 수 있음으로, 추상 팩토리를 제작.
 *
 * Created by Doohyun on 2017. 11. 19..
 */
public abstract class AbstractDuckFactory {
 
    /**
     * 고무오리 생산.
     *
     * @return
     */
    public abstract Duck createRubberDuck();
 
    /**
     * 거위 생산.
     *
     * @return
     */
    public abstract Duck createGoose();
}
 
/**
 * 꽥꽥 카운팅 데코레이터를 장식한 오리 객체를 생산하는 팩토리.
 *
 * Created by Doohyun on 2017. 11. 19..
 */
public class CountingDuckFactory extends AbstractDuckFactory{
 
    private static CountingDuckFactory INSTANCE = new CountingDuckFactory();
 
    private CountingDuckFactory(){}
 
    /**
     * 싱글톤으로 제작.
     *
     * <pre>
     *     팩토리는 의미적으로 한개여도 충분해 보임.
     * </pre>
     *
     * @return
     */
    public static CountingDuckFactory GetInstance() {
        return INSTANCE;
    }
 
    /**
     * 고무오리 생산.
     *
     * @return
     */
    @Override
    public Duck createRubberDuck() {
        // QuackCountDecorator 으로 장식하였음!
        return new QuackCountDecorator(new RubberDuck());
    }
 
    /**
     * 거위 생산.
     *
     * @return
     */
    @Override
    public Duck createGoose() {
        return new QuackCountDecorator(new GooseAdapter(new Goose()));
    }
}
 
cs


현재까지의 요구사항에서는 AbstractDuckFactory 를 굳이 제작할 필요는 없어보이지만,

추 후 오리들에 대한 새로운 관심사가 생길 경우 그에 맞는 적절한 객체를 생성할 수 있도록 추상 클래스를 제작했습니다.


또한 구현된 CountingDuckFactory 는 현재 의미적으로 한개여도 충분해 보인다는 생각이 들며, 그에 따라 싱글톤 클래스로 제작하였습니다.



팩토리를 제작하였으니, 그에 따라 테스트 클래스는 다음과 같이 편리하게 고칠 수 있을 것 같네요..


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
/**
 * 오리 시뮬레이터 정의.
 *
 * Created by Doohyun on 2017. 11. 19..
 */
public class DuckSimulator {
 
    /**
     * 시뮬레이터 작성.
     *
     * <pre>
     *     이 곳에 비지니스 시뮬레이션 기능을 넣을 것!
     * </pre>
     */
    public void simulate(){
 
        // 고무오리 테스트.
        {
            // 팩토리로 객체 생성을 캡슐화
            Duck rubberDuck = CountingDuckFactory.GetInstance().createRubberDuck();
            simulate(rubberDuck);
        }
 
        // 거위를 오리로 호환하여, 테스트
        {
            // 팩토리로 객체 생성을 캡슐화
            Duck goose = CountingDuckFactory.GetInstance().createGoose();
            simulate(goose);
        }
 
        System.out.println(String.format("꽥꽥 횟수 출력 : %d회", QuackCountDecorator.GetQuackCount()));
    }
}
cs



5. 오리떼 관찰!


현재 제작된 오리 시뮬레이터에서는 각각의 오리객체가 꽥꽥하도록 정의되어 있습니다.

하지만 오리 객체를 더 많이 테스트 하고 싶다면, 팩토리에서 객체를 생성하고 DuckSimulator::simulate 메소드를 매번 호출해야 합니다.

(즉 여러 오리 객체에 대한 관리가 불편합니다. ㅡㅡ^)


만약 테스트의 요구사항이 지금과 같이 여러 오리들을 순회하며 Duck::quack 을 실행하는 것이 목적이라면, 아래 패턴을 이용해 볼 수 있을 것 같습니다.



지금부터 오리떼(Flock)를 나타낼 수 있는 클래스의 제작을 목표로 하며, DuckSimulator::simulate 를 사용할 수 있도록 하려 합니다.

이 때 오리들을 순회하면서 동작하고, 기존 Duck 의 인터페이스를 그대로 계승하도록 해야할 것 입니다.


즉, 오리들을 그룹으로 묶은 복합계층(Composite)을 만들고 단일계층(RubberDuck..)과 동일한 인터페이스를 제공할 생각입니다.


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
/**
 * 오리떼 클래스.
 *
 * <pre>
 *     오리떼라는 복합계층을 제작하며, 단일계층과 동일한 취급을 할 수 있게 해줌.
 *     Composite Pattern
 * </pre>
 *
 * Created by Doohyun on 2017. 11. 19..
 */
public class Flock extends Duck{
 
    private List<Duck> duckList = new ArrayList<>();
 
    public void addDuck(Duck duck) {
        duckList.add(duck);
    }
 
    @Override
    public void quack() {
        // 순회하면서, Duck::quack 기능을 수행.
        // 반복자 패턴(Iterator)
        duckList.forEach(Duck::quack);
    }
}
 
 
/**
 * 오리 시뮬레이터 정의.
 *
 * Created by Doohyun on 2017. 11. 19..
 */
public class DuckSimulator {
 
     /**
     * 시뮬레이터 작성.
     *
     * <pre>
     *     이 곳에 비지니스 시뮬레이션 기능을 넣을 것!
     * </pre>
     */
    public void simulate(){
 
        // 오리떼 생성.
        Flock flock = new Flock();
 
        // 고무오리 추가.
        flock.addDuck(CountingDuckFactory.GetInstance().createRubberDuck());
 
        // 거위 추가
        flock.addDuck(CountingDuckFactory.GetInstance().createGoose());
 
        // 오리떼 테스트.
        simulate(flock);
    }
}
 
cs



여기까지, 오리 시뮬레이터의 요구사항을 일단 마치려고 합니다.

이 곳에서 사용된 패턴은 Adapter, Decorator, Factory, Composite, Iterator 패턴으로 오리 시뮬레이터를 제작하기 위해 연속적으로 패턴이 사용되었습니다.

위의 언급된 예제에서는 일련의 패턴들이 어떻게 연속적으로 사용될 수 있는지 볼 수 있습니다. 


하지만, 이 것이 컴파운드-패턴은 아닙니다. 

여러 패턴을 동시에 사용하였다고 컴파운드-패턴은 아니고, 여러 패턴이 결합되어 특정 용도로 사용되는 것을 말하기 때문이죠.


지금부터는 대표적인 컴파운드-패턴인 MVC 를 살짝 보며, MVC 가 어떤 용도로 여러가지 패턴들을 사용하고 있는지 볼 것입니다.



6. MVC


MVC 는 Model-View-Controller 의 약자로 Model(데이터 및 비지니스 문제)과 View(화면) 사이에 제어하는 층(Controller)를 두는 것을 목표로 합니다.

 


세 가지의 각 컴포넌트는 다음과 같은 역할을 하고 있습니다.


- Model 


모든 데이터 및 비지니스 로직들을 포함합니다. 또한 View 와 Controller 에게 본인의 상태(데이터)를 제공할 수 있는 인터페이스 및 상태 변경 이벤트를 제공하지만, 보통은 다른 두 컴포넌트들에게 별 관심은 없습니다.


- View 


Model 을 표현할 수 있는 기능을 담당합니다. 

데이터를 표현하기 위해 Model 로 부터 상태를 가져오도록 하며, 사용자의 입력을 Controller 로 전달하곤 합니다.


- Controller


View 로부터 사용자의 입력 이벤트를 인지하며, Model 의 상태변경을 요청하거나 View 에게 표시변경을 요청합니다.



이 세 가지 컴포넌트를 패턴 관점에서 보면, 보편적으로 다음과 같이 나누곤 합니다.

 


MVC 의 목적은 Model 과 View 를 분리하는 것으로 이는 꽤 중요한 이슈입니다. 

이 원칙을 지킬 시, 이를테면 아래와 같은 이득을 볼 수 있습니다.


비지니스적인 문제는 일관될 수 있지만, 이를 표현하는 방법은 다양해질 수 있습니다. 

  CUI -> GUI 로 변경 시, 비지니스 문제를 변경할 필요가 있을지 고민해봐야 합니다. 

  혹은 일관된 데이터로 여러 뷰를 표현해야 할 수도 있죠.


표현하기 위한 방법이 분리되어 있다면, 

  비지니스 문제가 변경되도 표현방법만 알맞게 호환해주면 원활하게 개발할 수 있습니다.



이 것으로, Effective OOP 카테고리도 마무리입니다. 

올해부터 시작한 OOP 에 대한 정리가 끝나게 되니, 정말 감회가 새로운 것 같네요. :-) 


여기까지 정리를 할 수 있었고, 공부를 할 수 있게 해준 것은 꾸준히 제 블로그를 봐주고 스터디에 적극적으로 참여해주신 구성원분들 덕이라고 생각을 합니다.

(잊지 못할 것입니다. ㅠㅡㅠ)


또한, 이러한 기반 지식 및 자료를 제게 알려주신 교수님께도 감사합니다. ㅎㅎ


여기까지 공부를 마쳤다면, 저를 포함한 이 글을 읽는 모든 분들은 이 책을 한 권 마스터한 것입니다.

나름, 수준있는 예제를 들고왔기 때문에 이 책을 보며, 블로그를 다시 읽어주신다면 정말로 도움이 되지 않을까 생각이 드네요. 

(여러분들도 이제 패턴병에 걸릴 시간입니다. 히히히)


Head First Design Patterns 헤드 퍼스트 디자인 패턴
국내도서
저자 : 에릭 프리먼(Eric Freeman) / 서환수역
출판 : 한빛미디어 2005.09.04
상세보기



조금 부족한 면이 없는 글들이긴 하지만, 

이 글들을 읽은 모든 분들이 정말 멋있고 훌륭한 개발자가 되는 것에 조금이라도 도움이 되길 바랍니다. ^^







반응형
Posted by N'

안녕하세요. 블로그 주인장 N` 입니다.


10월 한 달을 그대로 태워버린 ESC 행사가 종료되었고, 프롤로그를 썼으니 에필로그 역시 작성하고자 다시 한번 펜을 들었습니다.


한번 살펴보니, 그래도 꽤 많은 글이 쓰여진 것을 확인했습니다.



나름, 이정도면 처음 써본 것 치고는 괜찮은 일지가 아니었을까 하는 생각이 드네요. ^^;



이번 ESC 를 준비하면서 오랜만에 즐겁게 개발해본 것 같았습니다.

만들고 싶은 것을 정하고, 좋은 사람과 개발을 한다는 것은 정말 행복했었습니다.


이번에 준비를 하면서 가장 중요하게 생각했던 것은 [완벽한 작품]이나 [반드시 1등]같은 일회성 보상은 아니었습니다.


정말 하고자 했던 것은 주니어 후배(필자도 주니어 level 이지만...)에게 가장 필요한 "나도 노력하면 무엇이든 할 수 있다는 자신감" 을 주는 것이었습니다.

(나름, 2017년 버킷 리스트 중 하나였습니다.)

 

 


어차피 실무에서는 열심히 구르고 욕을 먹으며 쓰라린 사회의 맛을 보겠지만, 일회성에 불과한 이 프로젝트의 경험 속에서 느꼈던 그 무엇이든지 늘보다 더 나은 자신이 될 수 있도록 도와주면 좋겠네요.. :-)

사실 이 것을 준다고 줄 수 있는 것은 아니고 팔로워들이 잘 따라와줘야 실현되는 이야기인데, 다행히 사람들은 잘 만났습니다. (하하.)

 

아 그리고 조금 자랑을 하자면 다행히 뭐라도 건져서, 상처뿐인 영광 아닌 성공경험이었습니다.

 

 

 

하지만, 단순히 주기만 할 것이라 생각했던 이번 프로젝트에서 저 역시도 받은 것이 있었습니다.

특히 행사 당일 발표 직전 단지 후배로만 봤던 동료에게 "할 수 있다" 라는 용기를 받았고, 이는 큰 무대에서 끊지 않고 발표를 마칠 수 있는 계기가 되었습니다.


여러가지로 2017년 중 가장 뜻깊은 경험이었습니다. 

마지막으로 우리 앱의 로고를 올리며, 이번 카테고리를 마무리 합니다. :-)

 

 

 


















반응형
Posted by N'

안녕하세요! miTHON의 위치 정보 및 안드로이드 서비스 단 담당 강현지입니다.


구글 지도 API 활용하기 두번째 포스팅으로 찾아왔습니다!!!


이번 포스팅은 지난번 포스팅에 말씀드린대로 현재 위치 받아오기실시간 트래킹에 대해 살펴보려고 합니다.



[ 4단계: 현재 위치 받아오기 ]


현재 위치를 실시간으로 받아와서 지도에 마커를 표시하는 코드를 아래와 같이 작성하였습니다. 


/**
* 현재 위치정보 조회.
*
* @return
*/
public Maybe<Location> get_현재위치정보() {
try {
// 권한 체크.
CheckUtil.Check(ContextCompat.checkSelfPermission(mContext, Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED, "[ACCESS_FINE_LOCATION] 권한체크 에러", Exception.class);
CheckUtil.Check(ContextCompat.checkSelfPermission(mContext, Manifest.permission.ACCESS_COARSE_LOCATION)
== PackageManager.PERMISSION_GRANTED, "[ACCESS_COARSE_LOCATION] 권한체크 에러", Exception.class);

// 위치정보 조회.
LocationManager locationManager = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);

// 위치 정보 사용여부 조회.
{
CheckUtil.Check(get_위치정보_요청가능_여부(locationManager), "위치정보 사용불가능 상태", Exception.class);
}

// 네트워크 정보로부터 데이터 조회.
{
// 네트워크 정보로 부터 위치값 가져오기
if (isNetworkEnabled) {
locationManager.requestLocationUpdates(
LocationManager.NETWORK_PROVIDER,
MIN_TIME_BW_UPDATES,
MIN_DISTANCE_CHANGE_FOR_UPDATES
, this
);

if (locationManager != null) {
location = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
}
}
}

// GPS 정보로부터 데이터 조회.
{
if (isGPSEnabled) {
if (location == null) {
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
MIN_TIME_BW_UPDATES,
MIN_DISTANCE_CHANGE_FOR_UPDATES,
this
);

if (locationManager != null) {
location = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
}
}
}
}

} catch (Exception e) {
Log.e(MiRunConstDefinition.LOG_NAME, "위치정보 획득 실패", e);
}

return MaybeUtil.JustNullable(location);
}
/**
* 마커 그리기
*
* @param myLocationMaybe
*/
public void setDrawMaker(Maybe<Location> myLocationMaybe) {

MaybeUtil.Subscribe(myLocationMaybe,

location -> {

// Creating a LatLng object for the current location
LatLng latLng = new LatLng(location.getLatitude(), location.getLongitude());

CameraPosition cp = new CameraPosition.Builder().target((latLng)).
zoom(17).
build();

// Showing the current location in Google Map
mGoogleMap.moveCamera(CameraUpdateFactory.newCameraPosition(cp));

// 기존 마커 삭제.
if (marker != null) {
marker.remove();
}

// 마커 설정.
MarkerOptions optFirst = new MarkerOptions().
position(latLng).
icon(BitmapDescriptorFactory.fromResource(R.mipmap.ic_marker_pink)).
title(MiRunResourceUtil.GetString(R.string.label_record_current_position));

marker = mGoogleMap.addMarker(optFirst);
},
() -> MiRunViewUtil.ShowToast(R.string.label_emergency_request_location_fail));
}






[ 5단계: 실시간 트래킹 ]


실시간 위치정보를 받아 트래킹 라인을 그리는 코드는 아래와 같습니다.


/**
* 지역 정보 목록 세팅.
*
* @param locationList
* @param myLocationMaybe
*/
public void setLocationList(@NonNull List<LocationConcreteVO> locationList, Maybe<Location> myLocationMaybe) {
// TODO khj1219

// 위도경도 목록 제작.
List<LatLng> latLngList = new ArrayList<>();
{

Observable.fromIterable(locationList).
forEach(vo -> {
// 위도 경도 처리.
latLngList.add(new LatLng(vo.getLatitude(), vo.getLongitude()));
Log.d(MiRunConstDefinition.LOG_NAME, String.format("초기화용 데이터 -> 위도 : %f, 경도 : %f, 시간 : %d",
vo.getLatitude(), vo.getLongitude(), vo.getTime()));
});

myLocationMaybe.
map(location -> new LatLng(location.getLatitude(), location.getLongitude())).
subscribe(latLngList::add);
}

// 마지막 라인 마커찍기.
{
if (!ContainerUtil.IsEmpty(latLngList)) {
setDrawMaker(myLocationMaybe);
}
}

AsyncBuilder.Create((FlowableEmitter<Double> emitter) -> {
if(latLngList.size() > 1){
Double result = getRecordDistance(latLngList);
emitter.onNext(result);
emitter.onComplete();
}
}).doOnNext(result -> {
// 거리 업데이트.
if(!Double.isNaN(result)){
setTextByDistance(result);
setTextByDonation(result * MiRunConstDefinition.CONST_DONATION.RUN_PER_REWARD);
}
}).run();

// 달리는 경로 polyline 생성.
drawRunningPolyline(latLngList);
}
/**
* 달리는 경로 polyline 생성(pink)
*
* @param runningPoints
*/
public void drawRunningPolyline(List<LatLng> runningPoints){
PolylineOptions polylineOptions = new PolylineOptions();
polylineOptions.color(MiRunResourceUtil.GetColor(R.color.color_path_pink));
polylineOptions.width(7);
polylineOptions.addAll(runningPoints);
mGoogleMap.addPolyline(polylineOptions);
}




이것으로 구글 지도 API 활용하기 5단계에 대해 모두 살펴보았습니다.


트래킹의 경우 테스트가 매우 힘들었지만 결과적으로 앱이 잘 나와서 매우 행복합니다.


드디어 발표가 내일인데 좋은 결과 있었으면 좋겠네요.


아직까지 열심히 디비 작업중이신 우리 유감독님 더불어 피티엥 혼신의 힘을 다하고 계신 남배우님 정말 존경하고 조금만 힘내세요!!!


우리 모두 화이팅 입니다~~


반응형
Posted by N'

안녕하세요! miTHON의 위치 정보 및 안드로이드 서비스 단 담당 강현지입니다.


MIDAS Research Festival의 해커톤인 ESC 주제로 저희는 마라톤 앱인 miTHON에 지도 관련 기능이 많습니다.


아래 기획서를 받고 구현을 위해 구글 지도 API를 활용하였고, 그 내용을 공유하고자 게시글을 올리게 되었습니다~


그럼 우선 기획서부터 살펴볼까요?



1) [그룹] 이벤트 생성하기 (경로 생성, 식수대 처리)





2) [개인][메인] 기록 시작




위 기획서를 바탕으로 구글 지도 API를 활용하였고, 그 기능을 5단계로 설명하려고 합니다.


해당 게시글에서는 3단계까지 소개하겠습니다.


[ 1단계: 맵뷰 띄우기 ]


1. 사이트에 가서 API Key를 발급 받습니다.

(레퍼런스: https://developers.google.com/maps/documentation/android-api/signup?hl=ko)

 


2. app 폴더 밑에 있는 build.gradle에 가서 구글 맵 API 컴파일 설정을 해줍니다. Google Play Services 전체 라이브러리(com.google.android.gms:play-services)를 추가하는 대신에 Google Maps Android API를 사용하기 위해 필요한 라이브러리만 추가해주는 것이 좋습니다.

그리고 SDK Manger에서 Google Play services를 설치한다. (설치 안하고 빌드시 에러와 동시에 설치하는 링크를 제공할 것입니다.)

// Google Maps Android API
compile 'com.google.android.gms:play-services-maps:11.0.2'
compile 'com.google.android.gms:play-services-location:11.0.2'

구글 플레이 서비스를 이용하려는 해당 뷰에 아래 코드를 넣으면 구글 플레이 서비스를 이용할 수 있습니다.

GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(this);

 

3. AndoridManifest.xml에 가서 아래와 같이 meta-data 설정을 해줍니다.

<!-- 구글 API KEY 설정 -->
<meta-data
   
android:name="com.google.android.geo.API_KEY"
   
android:value=" 발급받은 API key'" />

 


4. 맵뷰를 구현할 화면의 onCreate 메소드에 SupportMapFragment를 이용해 지도 객체를 생성합니다. Fragment 객체를 지도를 처리할 Activity)에 추가한다. 프래그먼트에서 getMapAsync()를 호출하여 콜백을 등록합니다.

(레퍼런스: https://developers.google.com/maps/documentation/android-api/map?hl=ko)

@Override
protected void onCreate(Bundle savedInstanceState) {
   
super.onCreate(savedInstanceState);

   
// BitmapDescriptorFactory 생성하기 위한 소스
   
MapsInitializer.initialize(getApplicationContext());

   
SupportMapFragment mapFragment = ((SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map));
   
mapFragment.getMapAsync(this);
}

 

5. 맵뷰를 구현할 화면에 OnMapReadyCallback 인터페이스를 구현하여 onMapReady 메소드를 오바라이딩 합니다. onMapReady은 콜백 메소드로서 GoogleMap 객체의 핸들을 가져옵니다.

(레퍼런스: https://developers.google.com/android/reference/com/google/android/gms/maps/OnMapReadyCallback )

public interface OnMapReadyCallback: Callback interface for when the map is ready to be used.

@Override
public void onMapReady(final GoogleMap googleMap) {

   
this.mGoogleMap = googleMap;

   
String coordinates[] = {"37.397446", "127.105714"};
    double
lat = Double.parseDouble(coordinates[0]);
    double
lng = Double.parseDouble(coordinates[1]);

   
LatLng position = new LatLng(lat, lng);
   
GooglePlayServicesUtil.isGooglePlayServicesAvailable(MapActivity.this);

   
// 맵 위치이동.
   
this.mGoogleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(position, 15));

}

 

위의 코드 실행결과 아래와 같이 지정한 위경도 좌표에 포커스가 된 맵뷰를 나타낼 수 있습니다.






[ 2단계: 화면에 마커 표시 및 마커 클릭 이벤트 ]


1. 맵뷰가 구현된 화면에 OnMapClickListener, OnMapLongClickListener, OnInfoWindowClickListener 인터페이스를 구현하여 OnMapClick, OnMapLongClick, OnInfoWindowClick 메소드를 오버라이딩합니다.

(레퍼런스: https://developers.google.com/android/reference/com/google/android/gms/maps/GoogleMap.OnMapClickListener)

 

2. 1단계에서 오버라이드한 OnMapReady 메소드의 구글 맵 지도에 클릭 이벤트를 세팅합니다.

@Override
public void onMapReady(final GoogleMap googleMap) {
    mGoogleMap = googleMap
;

    
mGoogleMap.setOnMapClickListener(this);
   
mGoogleMap.setOnMapLongClickListener(this);
   
mGoogleMap.setOnInfoWindowClickListener(this); //정보창 클릭 리스너(마커 삭제 이벤트)
}

 

3. 오버라이딩한 OnMapClick, OnMapLongClick, OnInfoWindowClick 메소드에 아래와 같이 클릭했을 때 나타날 이벤트를 설정합니다. 이때 MarkerOptions를 이용해 마커를 커스터마이징 할 수 있습니다. (마커가 표시될 위치, 아이콘, 타이틀 등 추가 가능)

 

- 지도를 클릭했을 때 "출발" 마커 생성

@Override
public void onMapClick(LatLng latLng) {
    MarkerOptions markerOptions =
new MarkerOptions();

       
//add marker
       
markerOptions.position(latLng);
       
markerOptions.icon(BitmapDescriptorFactory.fromResource(R.mipmap.ic_marker_pink));
       
markerOptions.title("출발");
       
mGoogleMap.addMarker(markerOptions).showInfoWindow();

       
// 맵셋팅
       
arrayPoints.add(latLng);
       
arrayMarkerOptions.add(markerOptions);
}

 

- 지도를 길게 클릭했을 때 polyLine 생성

@Override
public void onMapLongClick(LatLng latLng) {
   
// TODO
    //
마커를 길게 누를 경우 발생할 이벤트 작성
}

 

- 마커 info창을 클릭했을 때 해당 마커 삭제 이벤트

@Override
public void onInfoWindowClick(Marker marker) {
   
// step2에서만 삭제 가능 && 시작 위치는 삭제 못함
   
if((getViewPager().getCurrentItem() == 1) && !arrayPoints.get(0).equals(marker.getPosition())){
       
// 지도 초기화
       
mGoogleMap.clear();

       
// 리스트에서 마커 삭제
       
arrayPoints.remove(marker.getPosition());
       
arrayMarkerOptions.removeIf(v -> v.getPosition().equals(marker.getPosition()));
   
}
}


        




[ 3단계: 마커를 연결하는 Polyline 생성 ]


1. 마커를 연결할 polyLine 생성은 PolylineOptions를 사용합니다. (라인 색, 두께 등 지정 가능)

// polyline 생성
public void drawPolyline(){
    polylineOptions =
new PolylineOptions();
   
polylineOptions.color(Color.RED);
   
polylineOptions.width(5);
   
polylineOptions.addAll(arrayPoints);
   
mGoogleMap.addPolyline(polylineOptions);
}

 

2. [2단계] OnMapLongClick 메소드에 Polyline을 그리는 로직을 작성하여 마커를 길게 누를 경우 마커 리스트를 연결하는 polyLine을 만들 수 있습니다.

@Override
public void onMapLongClick(LatLng latLng) {
   
if(getViewPager().getCurrentItem() == 1) { // 2page
       
polylineOptions.addAll(arrayPoints);
       
mGoogleMap.addPolyline(polylineOptions);
   
}
}




우선 여기까지 해서 구글 지도 API 활용하기 포스팅을 마치겠습니다.


구글 지도 API는 처음 사용하시는 분들도 쉽게 사용할 수 있을 만큼 API 문서도 잘 정리되어있고, 예제 소스도 많이 있습니다.


생각보다 지도 관련 결과물이 잘 나와서 매우 뿌듯하네요~ 다음번 포스팅은 현재 위치 받아오기실시간 트래킹에 대해 소개해 드리겠습니다.


그럼 다음 포스팅에서 봐요~~


4-GRAM 화이팅입니다!!! 우리 조원 너무 좋아 ^______^!!


반응형
Posted by N'

안녕하세요 :) 저희 앱(miTHON)의 서버운영/DB/백엔드 담당 유덕형입니다.


MIDAS Research Festival 의 강력한 우승후보답게 저희는 상당한 수준의 개발 완성도를 자랑하는데요.


제가 담당한 부분을 이제부터 차근차근 설명을 드리는 시간을 가져보겠습니다.


먼저 저희 서버 아키텍처에 대해 설명을 드려볼까 합니다.


저희 서버는 AWS (Amazon Web Service) 에 올라가있습니다.

AWS 의 장점은 이루 말할 수 없을 정도로 많은데요. (자세한 내용은 저보다 구글을 참고해주세요.)


1. AWS 간략 설계 아키텍처



AWS 에는 각각의 기능에 최적화된 서버를 구성할 수 있도록 다양한 모듈들을 지원하는데요.

저희는 DB를 MySQL 을 사용하므로 Relation Database 를 구축할 수 있는 RDS 를 선택했습니다.

또한 파일 업/다운로드에 용이하며 정적 서버 호스팅도 지원하는 S3 를 사용했습니다. 

S3 의 장점은 바로 다운로드 URL 을 만들어 제공한다는 것인데요. 저희는 파일서버에 구성원 사진과 SNS 사진들을 저장하고 있습니다.


마지막으로 WAS 를 띄워놓는 EC2 인데요. 역시 서버는 우분투가 진리죠.


2. 백엔드 모듈 아키텍처



서버 백엔드 개발은 웹솔루션에서 사용하고 있는 Spring framework 기반 위에서 진행되었습니다.

보시는건 저희 Class Diagram 입니다. 생각보다 클래스가 많네요? 허허 이것이 저의 클라쓰일까요..

이 수많은 클래스들에 제 영혼이 4Gram 정도 들어간 것 같네요. 노동 착취의 향기가 느껴지지 않나요?


3. DB E-R Diagram


1) 논리모델




2) 물리모델


이번 긴 추서 연휴를 이용하여 저희팀의 DBA 이신 남궁용락 차장님에게 전수받은 기술로...! 

DB 설계를 진행해봤습니다. 저희 앱의 큰 틀이 되는 그룹, 그 하위의 이벤트 등 기획자님이 기획서를 잘 써주신 덕에

편안하게 설계를 진행할 수 있었습니다.

여기서 주목할 점은 member 정보를 독립적으로 가져감에 따라 다른 모듈과의 호환성을 조금 더 편리하게 만들었다는 것입니다.


4. 그리고 개발



착즙 세팅이 완료되었다면 그 기반 위에서 이제 저를 갈아넣기만 하면 됩니다.

코드가 알록달록한게 예쁘네요.


5. 마치며...


기술적인 내용은 전혀 없네요. 결과물 위주의 블로그를 써버림.


총 개발기간은 약 1개월 정도 되었네요. 추석기간동안 좀 더 열심히 할걸... 

ESC (Eat, Sleep, Coding) 의 취지에 맞게 1개월동안

퇴근하면 자기 전까지 밥먹고 개발하고 자고 의 for loop 속에서 살았습니다 ㅋㅋㅋㅋ


그럼에도 항상 웃음을 잃지 않고 정말 열정적으로 착즙한 강현지 사원과 남두현 사원님,

그리고 우리 앱의 기획과 디자인을 담당하신 성지윤 사원님! (군대간 유형주 사원도...)

감사하고 즐거운 시간이었다고 말씀드리고 싶습니다.


2017년이 마무리 되어가는 이 시점에, 그래도 한달 정도는 최고로 열심히 살았구나 하는

춥지만 뜨거운 시간이었습니다.


그럼 우리 마지막까지 힘내서 달려보자구요 !

반응형
Posted by N'

지난, 9월 28일 회의과정에서는 앱의 초기 기획안으로부터 Use-case 및 핵심 개념을 도출하게 되는 계기가 되었습니다.


이후, Rom(다른 팀원)님이 이슈관리 시스템에 올려주신 기획내용에 대한 추가 정리가 있었고, S님(기획자)께서는 액슈어라는 기획툴을 이용하여 스토리보드를 작성해주시고 계십니다.


어느정도 마무리가 되어가는 중, 고민거리가 생겼습니다.

그것은 바로 "디자인" 입니다.


현재, 저희 팀원 중에 디자이너는 없으며 또한 포토샵을 다룰 줄 아는 분이 없습니다.

(다룰 줄 모르면, 지금부터 배우면 되는 것이 아닌가? ㅡㅡ^)


이 사실은 추 후 앱 개발에 있어서, 아이콘 하나하나를 모두 구글링해서 찾아야함을 의미합니다.

구글링을 하더라도 배경이나 색감이 안맞으면 그림의 떡입니다. ㅜㅡㅜ


"하지만 이가 없으면 잇몸으로 음식을 씹고, 꿩이 없으면 닭을 사용하는 법"


오늘 살펴 볼 주제는 무려, "포알못(포토샵을 지 못하는 )이 아이콘 만드는 법" 입니다. 


제 개인적인 기준에서는 포토샵은 사실 디자이너나 퍼블리셔의 전유물이라는 편견이 조금 있습니다.

물론 다재다능한 분들은 잘 사용할 것이고, 시간만 충분하다면 저희들 중 누구라도 포토샵을 조금만 공부해서 할 수는 있겠지만 귀찮고 개발시간도 부족할 것 같습니다.


이럴 때 우리는 많은 직군들이 다룰 줄 아는 국민 툴인 PPT(파워포인트)로 아이콘을 만들 수 없을 까라는 고민을 하게 되었고, 이를 통해 아이콘을 제작할 수 있음을 깨달았습니다.


PPT 의 장점은 아래와 같이 많은 도형들을 제공해준다는 것입니다.

대부분 저희가 보는 아이콘들은 이 도형들로 제작이 가능할 것이란 생각이 듭니다.

 

 

 

또한 한가지 더 살펴볼 것은 구글의 디자인 색 가이드입니다.

구글 색 - 디자인 가이드


이 두 가지를 이용하여, 우리는 멋진 아이콘들을 제작할 수 있을 것 같습니다.

이번 포스팅에서 샘플로 만들어 볼 것은 게시판을 나타는 햄버거 목록 아이콘을 제작해 보려 합니다.


일단, PPT 에서 제공하는 도형들을 이용하여 아래와 같이 배치를 해줍니다.

요즘 좋아진 PPT 는 정말 좋아져서, 간격까지도 다 재줍니다.

 

 

 

아이콘의 배치가 다 끝났다면, 구글 색 가이드에서 원하는 색을 스크린 샷으로 찍습니다.

꼭 구글 색 가이드가 아니더라도, 원하는 색이 있다면 캡처를 시도합니다.

 

 

 

이렇게 찍은 스크린샷을 PPT 에 붙여넣고, 도형의 색을 우리가 원하는 색으로 맞춰줍시다.

PPT 의 색 피커를 이용하면, 쉽게 색을 맞춰 줄 수 있습니다.

 

 

 

이렇게 색을 맞춰줬다면, 모든 도형을 선택하고 그룹핑을 시켜줍니다.

 

 


이제 그룹핑된 아이콘을 [선택 -> 마우스 오른쪽버튼]을 누르고, 그림으로 저장을 눌러 줍시다.

 

 


그럼, 짜잔!

배경이 없는 우리가 원하는 PNG 아이콘이 생성되었습니다. 

 


사실, 전문적인 간격, 크기, 해상도에 따른 아이콘 배치 등의 모든 가이드가 맞춰지면 가장 베스트하겠지만, 열악한 환경이고 당장 급하다면 이 방법이 도움이 될 것이란 생각이 드네요. ㅎㅎ


이상, 오늘의 기획일지도 마무리입니다. ^^








반응형
Posted by N'

2017년 9월 28일 기획일지 


지난, 9월 26일 회의[아이디어 재점검]에서는 앱에 필요한 아이디어들은 대부분 확정이 되었습니다.

이 날의 회의가 궁금하다면, 아래 포스팅을 참고 :-)



이와같이 아이디어 재점검이 마무리될 때 쯤 새로운 동료가 생겼습니다.

Developer 로만 구성되었던 팀에 전문적으로 기획을 해주시는 분이 참여를 하게 되었습니다.

(나름, 팀의 밸런스가 맞춰지는 느낌이 듭니다. @.@)


이에따라, S님(편의 상, 계속 기획자님이라고 하기가... ^^; 이 분의 이니셜을 인용...)에게 현재의 기획 상태 및 정리 상황을 브리핑하게 되었습니다. 

"이미 확정이 되었으니, 이렇게 갑시다." 보다는 우리의 요구사항과 현재 프로젝트의 진행도는 이 정도임을 알리고, 전문적인 S님의 의견을 듣고 싶었습니다.


이에 따른 피드백은 간단하지만 묵직했습니다.


"앱 제작을 위한 리소스는 많이 준비가 되었지만, 정리가 조금 안 된 느낌? 정리가 조금 필요합니다."


이에 따라 업무시간을 마치고, 리소스에 대한 정리 및 스토리 보드를 작성하는 회의를 가지게 되었습니다.

이 날 회의에서는 앱 개발을 위한 리소스의 정리와 개념 정의 및 앱 사용 시나리오를 구상했습니다. 


1. 리소스 정리와 개념 정의


S님의 피드백 내용에 따라 가장 먼저 해야할 것은 기획의 정리였고, 앱의 목표를 먼저 다시 그려보는 계기를 가지게 되었습니다.

기존 기획안에는 개발목표라는 항목이 존재했고, 이를 정리해보았습니다.


기존의 개발목표는 아래와 같았으며, 

 

 



이에 대한 구체적인 세부기능들을 회의 전까지 아래와 같이 준비를 했었습니다.




이 안에서, S님의 주도하에 리소스들을 특정 카데고리로 나눠보는 작업을 하였습니다.

앱의 요구사항에 따라 사용자 측면(개인과 관리자)으로 분리하고, 각 사용자 관점에 따라 위에서 도출한 세부기능들을 추가해봤습니다.

 


이 과정을 통해, 관리자와 개인 사용자라는 개념 도출관점에 따른 기능 명세분류에 맞게 정의할 수 있었습니다.

이제는 앱 사용 시나리오를 살펴보면 좋을 것 같네요. ㅎㅎ



2. 앱 사용 시나리오에 따른 스토리보드


다음, 살펴볼 내용은 앱 사용 시나리오에 따른 스토리보드 입니다.

(원래는 S님께서는 혼자해도 된다고 했지만, 블로그 포스팅 및 혼자 끌어가는 것을 방지하고자 같이 참여했습니다. ^^;)


스토리보드 작성은 다같이, 종이 몇장 깔아두고 아래와 같이 진행을 했었습니다.

(빈약한 것 같지만, 원래 이런작업은 아날로그가 최고입니다. ㅋㅋㅋ)




이 과정에서는 개인과 관리자의 앱 사용 시나리오를 구상하였으며, 조금 더 상세 개념을 도출했습니다.


세부적인 기능에 대한 고민 중, 그룹이라는 개념이 한 개 더 생겼으며 그룹에는 마라톤을 진행 이벤트를 등록한다는 유즈케이스(use-case) 를 뽑아냈습니다.

중요한 것은 사용자에 따라 어떤 유즈케이스가 생기는 지를 파악하는게 중요합니다.


이에 따라 마라톤 행사 및 아젠다 등 관리적인 측면에 대하여 관리자가 그룹에 이벤트를 등록을 하며, 사용자가 기록이나 기타 게시물을 이벤트에 따라 기록할 수 있다는 시나리오와 함께 아래와 같은 스토리보드를 작성하게 되었습니다.

 

 

큰 뼈대는 제작이 되었고, 그에 따라 공지 사항 앨범 공유 등 부가적인 기능을 넣기가 더 쉬워진 듯합니다.

이 때 주의할 점은 부가기능을 넣기가 쉽다고 하더라도, 너무 많이 넣으면 프로젝트가 비대화가 될 수 있다는 점입니다.



오늘 회의에서는 요구사항에 대한 정리와 구체적인 기능 명세가 나온 듯 합니다.


일정과 자원 등 현실적인 면에서 기능과 우선순위를 고려해보았고, 이에 따라 S님께서는 플로우 차트를 먼저 작성해주시기로 하였습니다.

플로우 차트는 아래 주소에서 확인이 가능합니다.

[마이톤 기획노트 : http://xbu1aq.axshare.com/#g=1&p=4gram_기획노트]


아직까지는 프로젝트가 잘 진행이 되는 것 같습니다. 느낌상... ㅎㅎ

꼭 성공적으로 마무리가 되었으면 좋겠네요. @.@


다 같이 파이팅 합니닷!!






반응형
Posted by N'