스토리 홈

인터뷰

피드

뉴스

조회수 3841

iOS에서 간결한 API 클라이언트 구현하기 (like Retrofit+GSON)

이 글은 안드로이드 개발에서 웹 서버 API 클라이언트를 간결하게 구현할 수 있도록 도와주는 강력한 오픈소스 라이브러리인 Retrofit과 GSON의 조합을 iOS 개발에서도 따라해보고 싶은 분들을 위해 작성되었습니다. Retrofit+GSON를 실제로 사용하는 좋은 예제는 다른 블로그 글에서도 찾아볼 수 있습니다.배경리디북스 서비스가 발전하면서 점점 복잡해지고, 자연히 앱의 기능도 다양해지기 시작했습니다. 기능이 다양해지면서 웹 서버와의 연동을 위한 API 종류도 늘어났고 앱 내에서 API 호출이 필요한 부분도 다양해지면서 관련된 중복 코드가 이곳 저곳에 산재하게 되었고 전체적인 코드 퀄리티 향상을 위해 이를 최소화하고 모듈화 할 필요성이 생겼습니다.안드로이드에서는 Pure Java로 작성되어 어노테이션을 통한 간결한 코드를 사용할 수 있게 해주는 Retrofit을 GSON과 연동하여 JSON 응답을 손쉽게 객체에 맵핑 하여 사용함으로써 이러한 문제를 성공적으로 해결할 수 있었습니다. 이후 iOS 개발을 진행하면서 비슷한 역할을 할 수 있는 도구가 있을까 찾아봤지만 마땅하지 않아 결국 사용 가능한 도구들을 이용해 비슷하게 따라해보기로 했습니다.목표Retrofit+GSON 조합을 최대한 따라해서 iOS 앱의 코드 퀄리티를 높이기 위한 작업을 진행하기는 하지만 모방하는 것 자체가 목적이 될 수는 없으므로, 구체적인 목적은 다음과 같은 것들로 상정해보았습니다.API 통신 부분을 모듈화하여 관련 중복 코드를 최소화하기NSArray, NSDictionary를 직접 사용하여 제어 했던 JSON 처리 부분을 추상화하여 모델 클래스를 정의, JSON 응답을 자동으로 객체에 맵핑 해서 사용할 수 있도록 하기필요한 것Retrofit과 GSON의 동작에 대한 이해AFNetworking비동기 HTTP 요청 처리에 용이하므로 기존에도 이미 API 호출을 위해서도 사용하고 있었습니다.이 글의 내용은 버전 2.6.3 기준입니다.Swift 언어와 그에 대한 이해사실 Objective-C를 사용해도 무방하지만, 작업 당시 Swift가 발표된 지 얼마 되지 않은 시점 이었기 때문에 시험 삼아 선택 되었으며 실제로 Swift가 Objective-C 대비 가진 장점들이 적지 않게 활용되었습니다.이 글의 내용은 버전 2.0 기준입니다.구조와 동작클래스 이름 앞에 붙어 있는 RB는 리디북스에서 사용하는 클래스 접두어 입니다.RBApiServiceAPI 통신을 담당하는 부분의 핵심은 중앙의 RBApiService 클래스를 포함한 상속 구조라고 할 수 있으며 상술하면 다음과 같습니다.AFNetworking에서, HTTP 요청 작업의 큐잉부터 시작과 종료까지 라이프 사이클 전반을 관리하는 역할을 하는 AFHTTPRequestOperationManager를 상속받는 RBApiService 클래스를 정의각 API들은 역할군에 따라 RBBookService(책 정보 관련 API), RBAccountService(사용자 계정/인증 관련 API) 등과 같은 RBApiService의 하위 클래스들의 메소드로 정의됨이 하위 클래스들이 AFHTTPRequestOperationManager의 역할을 그대로 이어받아 자신을 통해 이루어지는 API HTTP 요청 작업들을 관리이 설명에 따르면 웹 서버의 /api/foo/bar API를 요청하는 메소드는 RBFooService 클래스에 다음과 같이 정의될 것입니다.func bar(param1: String, param2: String, success: RBApiSuccessCallback, failure: RBApiFailureCallback) -> AFHTTPRequestOperation! { let paramters = ["param1": param1, "param2": param2] responseSerializer = RBJSONResponseSerializer(responseClass: RBFooBarResponse.class) return GET("/api/foo/bar", parameters: parameters, success: success, failure: failure) }RBApiSuccessCallback과 RBApiFailureCallback은 요청과 응답이 완료되고 각각 성공, 실패일 때 호출되는 람다 함수(Objective-C의 block에 대응되는 개념) 타입으로 다음과 같이 typealias를 통해 선언되어 있습니다. typealias RBApiSuccessCallback = ((operation: AFHTTPRequestOperation, responseObject: AnyObject) -> Void)? typealias RBApiFailureCallback = ((operation: AFHTTPRequestOperation?, error: NSError) -> Void)?GET 메소드는 AFHTTPRequestOperationManager의 메소드로 새로운 HTTP GET 요청 작업을 생성하고 큐에 넣은 뒤 그 인스턴스를 반환합니다. bar 메소드는 이렇게 반환된 인스턴스를 다시 그대로 반환하는데 API 호출을 의도한 측에서는 이 인스턴스를 통해 필요한 경우 요청 처리를 취소할 수 있습니다. API에 따라 GET 이외의 다른 방식의 요청이 필요하다면 POST, PUT, DELETE등의 메소드들 또한 사용할 수 있습니다.RBFooBarResponse 클래스는 이 API 호출의 JSON 응답을 맵핑하기 위한 모델 클래스입니다. 이 API 요청의 응답은 RBJSONResponseSerializer 클래스를 통해 사전에 정의된 규칙에 따라 적절히 RBFooBarResponse 인스턴스로 변환되고 이 모든 과정이 성공적으로 진행되면 RBApiSuccessCallback의 responseObject 인자로 전달됩니다.모델 클래스와 RBJSONResponseSerializer앞서 이야기했듯이 RBJSONResponseSerializer는 JSON 형태로 온 응답을 특정 모델 클래스의 인스턴스로 맵핑시키는 작업을 수행합니다(Retrofit+GSON 조합에서 GsonConverter의 역할에 대응한다고 볼 수 있습니다).iOS 개발에서 전통적으로 JSON을 다루는 방식은 Cocoa 프레임워크에서 기본적으로 제공하는 NSJSONSerialization 클래스를 이용하여 JSON Array->NSArray로, 그 외의 JSON Object는 NSDictionary로 변환하여 사용하는 방식입니다. 이러한 방식을 사용할 경우 별다른 가공이 필요 없다는 장점이 있는 대신 다음과 같은 문제들에 직면할 수 있습니다.데이터가 명시적으로 정의된 프로퍼티로 접근되지 않고 문자열 키 기반의 키-밸류 형태로만 접근되므로 데이터의 타입이 명시적이지 않아 타입 검사와 캐스팅이 난무하게 되어 가독성을 해침오타와 같은 개발자의 단순 실수로 인한 버그를 유발할 가능성도 커짐특히 오타로 인한 버그의 경우 명시적인 모델 클래스의 프로퍼티로 맵핑 해서 사용한다면 IDE가 에러를 검출해주거나 최소한 빌드 타임 에러가 발생할테니 미연에 방지할 수 있습니다. 이러한 문제는 사소한 실수로 인해 찾기 힘든 버그가 발생한다는 점과 코드 리뷰를 통해서도 발견하기가 힘들다는 점에서 지속적으로 개발자를 괴롭힐 수 있습니다.RBJSONResponseSerializer를 통한 인스턴스로의 변환은 이런 문제 의식에서 출발했고 Retrofit에 GSON을 연계하여 사용하기 위한 GsonConverter가 해결을 위한 힌트를 제공한 셈입니다.// AFJsonResponseSerializer는 NSJSONSerializer를 이용해 NSArray/NSDictionary로 변환하는 기본적인 작업을 해줌 class RBJSONResponseSerializer: AFJSONResponseSerializer { var responseClass: NSObject.Type! override init() { super.init() } required init(responseClass: NSObject.Type!) { self.responseClass = responseClass super.init() } required init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func responseObjectForResponse(response: NSURLResponse?, data: NSData?, error: NSErrorPointer) -> AnyObject? { // 파서를 직접 구현하는 건 노력이 많이 필요하므로 우선 AFJSONResponseSerializer를 이용해 NSArray/NSDictionary로 변환 let responseObject: AnyObject! = super.responseObjectForResponse(response, data: data, error: error) if let dictionary = responseObject as? NSDictionary where responseClass != nil { // 변환 결과가 NSDictionary이면서 responseClass가 정의되어 있다면 변환 작업 시작 return responseClass.fromDictionary(dictionary, keyTranslator: PropertyKeyTranslator) } // NSArray라면 JSON이 top level array로 이루어졌다는 뜻이므로 변환 불가로 보고 그대로 반환 // 혹은 responseClass가 정의되어 있지 않아도 그대로 반환 return responseObject } }Key translatorfromDictionary 메소드 호출 시 함께 인자로 전달되는 keyTraslator는 JSON에서 사용되는 키로부터 모델 클래스의 프로퍼티 이름으로의 변환을 나타내는 람다 함수로 개발자가 원하는 규칙에 따라 정의하면 됩니다. 위의 코드에서 사용 중인 PropertyKeyTranslator는 리디북스 API에서 사용 중인 규칙 및 Swift의 네이밍 컨벤션에 따라 다음과 같이 언더스코어(_) 케이스로 된 이름을 카멜 케이스로 바꾸는 형태로 정의되었으며 이는 GSON의 FieldNamingPolicy 중 LOWERCASE_WITH_UNDERSCORES와 유사합니다.let PropertyKeyTranslator = { (keyName: String) -> String in let words = keyName.characters.split { $0 == "_" }.map { String($0) } var translation: String = words[0] for i in 1..NSObject.fromDictionary 메소드fromDictionary 메소드는 NSDictionary로 표현된 데이터를 실제 모델 클래스의 인스턴스로 변환하는 작업을 수행하며 NSObject의 extension(Objective-C의 category 개념과 유사합니다)으로 정의하여 원하는 모델 클래스가 어떤 것이든지 간에 공통적인 방법을 사용할 수 있게끔 했습니다.extension NSObject { class func fromDictionary(dictionary: NSDictionary) -> Self { // keyTranslator가 주어지지 않으면 디폴트 translator 사용 return fromDictionary(dictionary, keyTranslator: { $0 }) } class func fromDictionary(dictionary: NSDictionary, keyTranslator: (String) -> String) -> Self { let object = self.init() (object as NSObject).loadDictionary(dictionary, keyTranslator: keyTranslator) return object } func loadDictionary(dictionary: NSDictionary, keyTranslator: (String) -> String) { // 주어진 dictionary에 포함된 모든 키-밸류 쌍에 대해 작업 수행 for (key, value) in (dictionary as? [String: AnyObject]) ?? [:] { // keyTranslator를 이용해 키를 프로퍼티 이름으로 변환 let keyName = keyTranslator(key) // 프로퍼티 이름을 사용할 수 있는지 검사 if respondsToSelector(NSSelectorFromString(keyName)) { if let dictionary = value as? NSDictionary { // 밸류가 NSDictionary면 해당 프로퍼티의 타입에 대해 fromDictionary 메소드 호출 if let ecls = object_getElementTypeOfProperty(self, propertyName: keyName) as? NSObject.Type { setValue(ecls.fromDictionary(dictionary, keyTranslator: keyTranslator), forKey: keyName) } else { NSLog("NSObject.loadDictionary error: not found element type of property. (key: \(keyName), value: \(dictionary))") } continue } else if let array = value as? NSArray { var newArray = [NSObject]() // 밸류가 배열이면 각 요소별로 작업 수행 for object in array { if let dictionary = object as? NSDictionary { // 배열 요소가 NSDictionary면 프로퍼티의 배열 요소 타입에 대해 fromDictionary 메소드 호출한 뒤 배열에 추가 if let ecls = object_getElementTypeOfProperty(self, propertyName: keyName) as? NSObject.Type { newArray.append(ecls.fromDictionary(dictionary, keyTranslator: keyTranslator)) } else { NSLog("NSObject.loadDictionary error: not found element type of property. (key: \(keyName), value: \(dictionary))") } } else if let object = object as? NSObject { // NSDictionary가 아니면 그대로 배열에 추가 newArray.append(object) } else { NSLog("NSObject.loadDictionary error: can't cast element. (key: \(keyName), value: \(object))") } } setValue(newArray, forKey: keyName) continue } else if value is NSNull { continue } // NSDictionary, NSArray가 아니면서 null도 아니면 그대로 사용 setValue(value, forKey: keyName) } } } }주어진 dictionary에 존재하는 모든 키-밸류 쌍에 대해 밸류가 가진 타입과 이에 대응하는 프로퍼티의 타입에 따라 적절히 프로퍼티에 대응될 객체를 구한 다음 Cocoa 프레임워크에서 제공하는 KVC를 이용해 채워넣습니다.프로퍼티 타입 정보 가져오기모델 클래스가 반드시 Int, String, Float과 같은 기본적인 타입들로만 이루어져 있을 필요는 없고 다른 모델 클래스의 인스턴스나 배열을 포함하고 있어도 타입 정보를 런타임에 가져와 재귀적으로 데이터를 채워나가는 것이 가능합니다. 프로퍼티의 타입을 알아내는 과정은 다음과 같이 Swift에서 제공하는 Mirror 구조체를 통해 이루어지는데 이는 마치 (이름에서도 느낄 수 있듯이) Java의 리플렉션을 떠올리게 합니다.// 타입 이름에서 특정 접두어("Optional", "Array", "Dictionary" 등)를 찾아 제거 func encodeType_getUnwrappingType(encodeType: String, keyword: String) -> String { if encodeType.hasPrefix(keyword) { let removeRange = Range(start: encodeType.startIndex.advancedBy(keyword.length + 1), end: encodeType.endIndex.advancedBy(-1)) return encodeType.substringWithRange(removeRange) } else { return encodeType } } // object의 타입에서 propertyName의 이름을 갖는 프로퍼티의 타입 이름을 반환 func object_getEncodeType(object: AnyObject, propertyName name: String) -> String? { let mirror = Mirror(reflecting: object) let mirrorChildrenCollection = AnyRandomAccessCollection(mirror.children)! // object의 타입 구조 children 중에서 propertyName을 찾음 for (label, value) in mirrorChildrenCollection { if label == name { // Optional 타입인 경우 "Optional" 접두어를 제외 return encodeType_getUnwrappingType("\(value.dynamicType)", keyword: "Optional") } } return nil } // object의 타입에서 propertyName의 이름을 갖는 프로퍼티의 타입 인스턴스를 반환 func object_getElementTypeOfProperty(object: AnyObject, propertyName name: String) -> AnyClass? { // 타입의 이름을 가져옴 if var encodeType = object_getEncodeType(object, propertyName: name) { let array = "Array" // "Array" 접두어로 시작할 경우 (배열인 경우) if encodeType.hasPrefix(array) { // "Array" 에서 "Array" 제외하고 T를 반환 return NSClassFromString(encodeType_getUnwrappingType(encodeType, keyword: array)) } let dictionary = "Dictionary" if encodeType.hasPrefix(dictionary) { // "Dictionary" 에서 "Dictionary", "K"를 제외하고 V를 반환 encodeType = encodeType_getUnwrappingType(encodeType, keyword: dictionary) encodeType = encodeType.substringWithRange(Range(start: encodeType.rangeOfString(", ")!.endIndex.advancedBy(1), end: encodeType.endIndex)) return NSClassFromString(encodeType) } // 커스텀 클래스 접두어를 가지고 있다면 그 타입 그대로 반환 if encodeType.hasPrefix(RidibooksClassPrefix) { return NSClassFromString(encodeType) } } return nil }RidibooksClassPrefix는 커스텀 클래스들의 접두어를 나타내는 상수이며(리디북스의 경우 앞서 이야기했듯 “RB”), 이 접두어가 붙어있는 경우에만 모델 클래스로 간주해 해당 타입 인스턴스가 반환됩니다.예시앞서 정의한 PropertyKeyTranslator를 사용했을 때, 위에 예시로 사용했던 /foo/bar API 요청의 JSON 응답과 모델 클래스 및 생성되는 인스턴스 형태의 예를 들면 다음과 같을 것입니다.(Int, Bool, Float과 같은 기존 NSNumber 기반의 타입을 가지는 프로퍼티들은 아직 정확한 원인은 알 수 없으나 nil 이외의 값으로 초기화 해주지 않으면 프로퍼티가 존재하는지 확인하기 위해 사용하는 respondsToSelector 메소드가 false를 뱉게 되어 사용할 수 없으므로 클래스 선언시 적절한 초기값을 주어야 합니다.{ "success": true, "int_value": 1, "string_value": "Hello!", "float_value": null, "baz_qux": { "array_value": [1, 2, 3] } }class RBFooBarResponse : NSObject { var success = false // true var intValue = 0 // 1 var stringValue: String! // "Hello!" var floatValue: Float! = 0.0 // nil var bazQux: RBBazQux! } class RBBazQux : NSObject { var arrayValue: [Int]! // [1, 2, 3] }맺음말이런 작업들을 통해 당초 목표했던 두 가지, API 통신 관련 중복 코드를 최소화 하면서 JSON 응답을 가독성이 더 좋고 실수할 확률이 적은 모델 클래스의 인스턴스로 자동 변환 하도록 하는 것 모두 달성하는 데에 성공했습니다.다만 모든 것이 뜻대로 될 수는 없었는데 Retrofit+GSON과 비교했을 때 플랫폼 혹은 언어의 특성에 기인하는 다음과 같은 한계들 또한 존재했습니다.Retrofit에서는 Java 어노테이션을 이용해 API 메소드의 인터페이스만 정의하면 됐지만 iOS 구현에서는 GET, POST 등의 실제 요청 생성 메소드를 호출 하는 것 까지는 직접 구현해줘야 함키->프로퍼티 이름 변환 규칙에 예외 사항이 필요할 때 GSON에서는 @SerializedName 어노테이션을 통해 손쉽게 지정할 수 있지만 iOS 구현에서는 예외 허용을 위한 깔끔한 방법을 찾기가 힘듬 (다만, 예외가 필요한 경우가 특별히 많지는 않기 때문에 큰 문제는 되지 않음)향후에는 HTTP 통신을 위해 사용 중인 AFNetworking(Objective-C로 작성됨)을 온전히 Swift로만 작성된 Alamofire로 교체하는 것을 검토 중이며 기존에 비해 좀 더 간결한 코드를 사용할 수 있을 것으로 기대하고 있습니다. 다만 Alamofire의 최신 버전이 iOS 8 이상을 지원하고 있어 iOS 7을 아직 지원 중인 리디북스인 관계로 언제 적용할 수 있을지는 아직 미지수입니다.#리디북스 #개발 #개발자 #iOS #iOS개발 #API #API클라이언트 #GSON #Retrofit #중복코드 #최소화 #API통신 #웹서버 
조회수 964

나른한 봄날, 회사에서 터지는 사고 BEST 20

그렇습니다. 봄이 오고 있습니다. 날이 따뜻해지고 출근 후 최소 2시간반은 나른한 정신으로 떨어지는 벚꽃잎을 떠올릴 시즌이 되었죠. 귓 속엔 끝없이 맴도는 장범준 연금 테마송과 도시의 뿌연 먼지가 서울에도 봄이 왔단 사실을 다시금 상기시켜 줍니다. 이렇게 졸리고 나른한 시즌에는 춘곤증을 날려줄 무언가가 절실하죠. 보통은 카페인 또는 봄맞이 꽃놀이 등이겠지만, 종종 극단적인 방법으로 졸음을 이겨내는 경우가 있습니다. 그렇죠. 사고를 치는 겁니다.졸림등골이 오싹해지고 손이 떨리며 옥상에서 담배 한 개비를 태우며 뛰는 심장을 느낍니다. 살아있음을 느낄 수 있는 좋은 방법이죠. 아드레날린과 코티솔이 잘 분비되는지 자가확인할 수 있는 시간입니다. 내분비계의 정상적인 움직임이 느껴지면서 다양한 감정과 지금까지 살아왔던 수많은 세월, 인간관계, 주량 등을 종합적으로 체크할 수도 있죠.오늘은 아주 사소하고 작은 사고부터, 격렬한 심장박동을 느낄 수 있는 거대한 사고까지 다양한 업무상 사고와 그 예방책 등에 대해서 알아보려고 합니다. 물론 대부분은 경험담입니다. 지금이야 웃으며 넘길 수 있지만, 당시엔 굉장했던 것들을 되새겨보며 저도 잠을 이겨낼 수 있을 것 같습니다. 벌써부터 소름돋는게 곤지암 슈비슈비 귀신보다 더 공포스러운 기억이군요.대부분의 사고는 5가지 유형으로 나뉩니다.1. 보고누락으로 인한 사고2. 뭔갈 잘못써서 터지는 기재형 사고3. 커뮤니케이션 미스로 인한 외부업체와의 이슈4. 결과물과 관련된 제작사고5. 말 잘못해서 터지는 주둥아리형 사고사옥이 무너지거나 대규모 해킹을 당했다거나, 횡령/배임 등의 쇠고랑이슈가 아닌 이상 대부분의 사고는 손과 입에서 시작됩니다. 이러한 사고는 미연에 방지하는 것이 최선이지만, 일이란 것도 사람이 하는 것이니 만큼 실수가 없을 수는 없습니다. 때문에 사고를 예방하는 것 못지않게 중요한 게 대처하는 방법이죠. 그래서 간단히 대처하는 방법에 대해서도 써보았는데, 사실 이 대처라는 것은 자기선에서 어찌어찌 마무리해선 안되는 겁니다. 회사는 그런 곳이 아니죠. 뭐든 보고와 지시에 의해 진행되기 때문에 '대처'라는 건 = 어떻게 보고하느냐. 와 같은 개념이기도 합니다. 이 점을 염두해두고 시작해볼께요~~ 다들 3D안경과 팝콘을 들고 구경해봅시다.1. 일어나보니 9시반아침의 5분은 지구상의 1시간과 같다.이건 사고라고 하기도 참 그렇지만, 신입입장에선 대형사고입니다. 일단 아침부터 강렬하게 일어날 수 있죠. 내가 이렇게 빨리 움직일 수 있었는가...에 대해서 다시 한 번 고찰해 볼 수 있는 시간입니다. 대처법은 잠이 들기 전 베게의 멱살을 잡고 7시반!! 7시반이라고!!! 알았어?!! 라며 거칠게 외치고 잠을 청해봅시다. 베게요정이 7시반이 되면 깨워줄 겁니다. 네, 그만큼 예방책이 없단 얘기죠.대처법 : 괜히 어디가 아프네, 할아버지가 꿈에 나왔네, 누가 돌아가셨네, 사고가 났네...하면 너무 뻔합니다. 그냥 늦어서 죄송합니다 다음부턴 지각하지 않겠습니다! 하고 들어가서 열심히 일하도록 합시다. 그리고 가끔 몰래..스윽 들어와서..스윽 앉는 사람들이 있던데, 그래봤자 다 알고있습니다. 상사에게 가서 늦게와서 죄송하다고 얘기하고 업무 시작하도록 합시다.2. CC안걸고 그냥 보냈을 때그럼 다시 보내면 됩니다.3. 전날 퇴근하면서 보일러/에어컨 안끄고 그냥 가기욕을 먹을 일입니다. 이건 사무실이 아니라 집이었어도 맘스터치 각입니다. 심지어 자취하는 분이라면 일 끝나고 집에 돌아와서 멀쩡히 켜진 채로 집을 태워먹을 듯한 보일러를 보고 느끼는 소름을 경험하셨을 겁니다. 돈이 타는 소리가 들리죠. 대표님도 똑같은 심정이었을 겝니다.대처법 : 이래서 IoT기술의 발전은 좋은 겁니다. 빨리 발전해서 앱으로 켜고 끄고 합시다.4. 영수증 버리기안녕? 난 니 고생이라고 해.영수증이 없어. 대부분 회계, 업무지원팀은 냉정합니다. 영수증이 없으면 처리가 안됩니다. 대천사 미카엘같은 분이 담당자라면 어찌어찌 대강 맞춰주기도 하겠지만, 결과적으론 그 분이 일이 꽤나 피곤하게 늘어나는 거니까요. 회사에 다니는 이상 껌을 하나 사도 영수증을 챙겨야 합니다. 안줘도 내놓으라고 해야합니다. 버려진 영수증이라도 주워와야 합니다. 영수증은 생명입니다.대처법 : 해당 매장에 가서 그 날짜 그 시간에 영수증을 다시 떼어달라고 합시다. 좀 시간이 지난 경우라면 꽤나 귀찮아질 수 있으므로 잘 사정사정해야합니다. 카드로 긁은 경우라면 전표를 카드사측에 요청해보도록 합시다. 대신 이 경우엔 언제 얼마를 썼었는 지 알고있어야 합니다.5. 견적서 같은 숫자써진 문서 세절 안하고 그냥 이면지로 쓰기음.. 안돼죠. 안돼요. 누가 내 주민등록등본 뒤에 메모하면 좋겠습니까... 비슷한 이치입니다. 뭐가 되었든 숫자가 1글자라도 있다면 일단 찢든 불을 태우든 반입자충돌을 시키든 해서 잘게 쪼개도록 합시다. 6. 세절기를 고장냈다.세절기는 막 SF영화에 나오는 그런 기계가 아니예요.그렇다고 20장씩 세절기에 종이를 꾸겨넣으면 고장납니다. 세절기가 체한 듯 꾸륵꾸륵 대다가 결국 멈춰버리는데 어?..하면서 손가락을 넣으면 내 살갗이 미립자가 되어 사라질 수도 있습니다. 개인적으론 플라스틱 자가 진짜 좋더군요. 쇠자면 더 좋습니다. 플라스틱자로 긁어내면서 정방향, 역방향으로 몇번 그륵그륵 해주면 풀어집니다. 대신 막혀있는 상태에서 계속 켜놓고 있으면 어디선가 탄 냄새가 나면서 세절기가 루비콘강을 건널 수도 있습니다.7. 입찰PT를 갔는데 폰트가 깨졌다.응. 입찰 망했어음... PPT의 글꼴포함 저장을 너무 믿으면 저렇게 됩니다. 글꼴저장을 믿지마세요. 주로 TTF가 저장가능하고 OTF는 일부 글꼴에 한해서만 저장이 가능합니다. 또한 TTF라고 할 지라도 상용제한이 걸려있는 폰트라면 저장이 되지 않습니다. 가장 속편하고 깔끔한 건 사용폰트까지 한꺼번에 압축파일로 가져가는 방법입니다. 혹자는 xml로 분리한 다음 소스코드를 바꾸는 방법도 쓰지만 입찰비딩이 5분전인데 그럴 시간은 없죠.대처법 : 일단 나눔고딕이라도 깔아서 적어도 맑은고딕이나 굴림은 안나오게 해줍니다. 폰트는 마스터적용해서 일괄적으로 바꿀 수 있으니 이상한 줄바꿈같은 걸 예방하려면 나눔고딕 등으로 변환 후 폰트사이즈를 하나씩 줄여줍시다.8. 미팅갔는데 파일 안열림. USB잃어버림. 인터넷안됨외부출장을 자주 다니시는 분들은 항상 에그를 지참하시던가 무제한 데이터요금제로 테더링을 각오하고 다니셔야 합니다. USB는 원래 이성을 지니고 자유의지가 있는 물체라서 자주 사라지곤 합니다. 그리고 일 끝날 때쯤 다시 책상에 돌아오곤 하죠. 녀석의 습성을 잘 파악해야 합니다. 모든 파일은 메일에 꼭 복사본으로 하나 보내놓던가 아니면 드롭박스나 구글드라이브에 공유해놓도록 합시다.보통 미팅을 갈 땐 반드시 뭔가를 하나씩 빼먹기 마련입니다. 이건 거의 불변이죠. 그러니 뭘 빼먹을 지 모르겠다면 모든 걸 하나씩 예비로 가지고 다니시는 게 좋습니다. 하다못해 펜까지도.9. 메일 잘못보냄클라이언트에게 우리 내부문서를 보냈습니다. 음? 네, 큰일났습니다. 어떤 문서인지는 모르겠으나 아마 견적서 같은 거였나봅니다. 이건...흠 큰일입니다. 메일을 보낼 때는 반드시 다시 한 번 주소를 확인해주세요. 그리고 지난 메일을 꼭 확인하고 그 메일에 답장하도록 합시다. 새로쓰기 이런거 하지말구. 이런 큰 이슈가 터지면....하아...일단. 흡연을 한 번 한 뒤, 팀장님 기분을 한 번 보도록 합시다. 사실 기분을 볼 필욘 없습니다. 왜냐면 30분전에 로또를 맞았거나 3년째 묶여있던 부동산이 500% 가격에 매매되지 않은 이상, 당신의 목숨은 경각에 달렸으니까요. 메일은 당신이 보냈으나 이런 이슈는 당신이 해결할 수 없습니다. 윗선에서 처리해야 합니다. 향후1달간의 당신의 모습10. 견적서에 0하나 더 붙임.이건 혼나야 할 일입니다. 숫자를 쓸 때는 0을 잘봐야 합니다. 그래서 3자리마다 콤마도 찍는거구요. 하지만 비교적 이 사고는 원만히 처리할 수 있습니다. 왜냐면 크로스체크가 되거든요. 받는 사람도 제대로 확인 못했고, 주는 사람도 그냥 줬다면 추후에 계산서가 좀 복잡해지겠지만 그냥 처리하면 됩니다.  그러나 계약당시의 금액에 문제가 있었다면 추후 논쟁의 여지가 있습니다. 계약법상 일방착오에 의한 계약해제는 불가합니다. 물론 그 금액이 잘못되었다는 걸 알았거나, 알 수 있는 여지가 있었다면(굉장히 모호하지만) 일방착오라고 해도 해제를 요구할 순 있습니다. 쌍방착오였다면 특정조건하에서 계약취소가 가능하긴 합니다. 그러나 만약 엄청나게 거액의 건이었거나 꽤나 복잡한 공공조달등의 과정이었다면 음... 한동안 회사를 좀 조용히 다녀야 할 듯 합니다. 11. 기한 실수택배 왔다ㅡ앙잉ㅇ!!!14일까지 배송해주세요!!~~라고 했는데 업체측에선 14일에 배송해달라는 줄 알았다고 칩시다. 근데 행사일이 내일인거예요. 이럴 땐 어떻게 해야될까요. 두 가지 경우가 있습니다. 택배발송 전이라면 약간의 금액부담을 하고 퀵으로 받으면 됩니다. 택배발송 후라면 큰일난겁니다. 일단 손해를 감수하고 행사는 치뤄야 하니 추가구매를 해서 퀵으로 받아야겠죠. 근데 그게 주문제작물품이었다면? 음...........실제로 이런 일이 발송했을 때. 택배물건을 추적해서 해당 영업소로 택시타고 뛰어간 적이 있습니다. 울면서 전화를 해야합니다. 영업소에 보관해달라고. 그리고 찾아가서 찾아와야 합니다. 이럴 땐 신속한 보고와 바로 전화하겠습니다!!! 가 필요합니다. 제발 무슨 물건이 언제까지 올 예정이라면 적어도 이틀전에 배송예정 물품체킹을 하도록 합시다.12. 이게 뭐야??파란색을 시켰는데 옥매트같은 색깔의 묘한 아이가 내 눈앞에 있습니다. 배송 실수 였을까요? 아닙니다. 깨알같은 글씨로 "상기 색은 모니터에 보이는 것과 다소 차이가 있습니다."라고 써져있더군요. 원래대로라면 소비자원에 고발을 하든 진상을 부리든 해서 반품과 재배송을 요청해야 함이 맞지만, 사실상 그럴만한 시간적 여유가 없을 겁니다. 다시 사야 합니다.13. 제작물사고다시 돌아가버려!!!안 그럴 것 같지만 제작물 사고는 엄청나게 자주납니다. 100mm로 시켰는데 10mm짜리 스티커가 온다거나..이런 경우는 은근히 흔하죠. 서로 사이즈체크가 안된겁니다. 양방책임이니 양 쪽에서 책임을 지는 것이 맞긴 합니다만, 회사입장에선 납득이 가지 않는 얘기죠. 만약 오더할 때 제대로 100mm로 들어간 경우라면 이건 제작자의 실수가 맞습니다. 물론 오더하는 측에서 전일, 제작 전에 한 번 더 확인하는 것이 옳다! 라는 얘기도 있지만 그건 이론적인 거고 어쨌든 제작을 주업으로 하시는 분들께서 사이즈실수를 하시면 안되는 거죠. 대처법 : 업체는 당연히 반반내지는 쌍방과실로 하자고 할 겁니다. 오더보낸 메일이나 과업지시서 상에 문제가 없었다면 냉정하게 좀 질러야 합니다. 14. 구두계약 상 실수'네 그럼 그 때 봬요!' / '추후 다시 연락드릴께요.'는 엄연히 다른 말입니다. 전자는 그래, 구두상으론 계약한거야?!~ 라는 뉘앙스고 후자는 아직 확정은 아니다..라는 뉘앙스가 있어요. 이 말 하나때문에 사고가 터집니다. 강사섭외를 하거나 대관 등을 요청할 때 이렇게 말 한마디를 잘못하면 상대방은 스케쥴을 비워놓거나 이미 예약을 확정지어버리죠. 시간이 지나서 '어? 저희 거기서 안할건데요?'라고 하면, 이제 난리난리가 납니다. '아니 그때 하신다고 해서 자리 다 비워놓고 거기 요청하신 분들도 다 돌려보냈는데 이러시면 안되죠.'등등 업체쪽의 볼멘소리가 폭발할 겁니다. 구두계약은 말 한마디 한마디가 진짜 중요하므로, 스크립트를 써놓고 말하도록 합시다. 특히 견적이나 예약사항을 알아보기 위해 전화할 때는 녹음필수!!!15. 뒷마다 까다가 걸렸다.이왕 이렇게 된 거 앞에서 까도록 합시다.16. 3일내내 작업했던 파일이 깨졌다..................... 컴퓨터 잘못이니 어찌 할 도리가 있겠습니까만은... 삼가 고인의 명복을 빕니다.17. 인쇄사고인쇄..당신은 대체..후우..이건 얘기를 좀 해야겠습니다. 중요합니다. 인쇄사고는 엄청나게 자주!!!! 일어납니다. 일단 집에 있는 프린터나 회사프린터로 뽑아보고 판단하지 마세요. 인쇄소 프린터는 그것과는 완전히 다릅니다. 둘째, 반드시 ai/PSD원본파일을 보낼 땐 png와 PDF파일도 함께 보내서 비교해달라고 요청을 하셔야 해요. 이렇게 잘 나오는지 꼭 확인 후 인쇄해달라고. 그리고 셋째. 인쇄직전에 인쇄용으로 다시 만들어놓은 PDF파일을 보내달라고 하셔야 합니다. 그걸 보고 문제없는 지 다시 체크후에 제작오더를 내리시는 거예요. 그럼에도 불구하고 뒷종이에 잉크묻어남, 도무송실수, 컬러오차, 후가공실수, 종이재질이 다르다던지...온갖 실수가 넘쳐납니다. 왠만하면 중요한 인쇄건이라면 반드시 실인쇄전에 감리를 가시는 걸 추천드립니다. 감리갈 여건이 안되고 시간적 여유가 있다면 일단 한 판 뽑고 샘플을 보내달라고 하세요. 퀵으로 쏴달라고. 만약 이 난리를 쳤는데도 사고가 났다면... 그건 사람이 어찌할 수 없는 암흑의 영역에 존재하는 일입니다. 분명 인쇄소측의 과실이라고 해도 프로젝트 담당자면 혼나는 걸 결국 당신이 될 겁니다. 인쇄소가 어쨌다..라고 징징대봤자 사실 결과물이 이렇게 나왔는데 그럼 어떻게 할거냐? 라는 반문만 돌아올 뿐이죠. 인쇄소측에 정식적으로 지난 오더메일 히스토리와 발주서 등등/결과물의 오류부분등을 기재해서 재인쇄를 요청하시는 게 맞습니다. 물론 그에 대한 비용은 업체측에서 부담해야죠. 당연히. 인쇄에 대해 정확하게 알고 있는 사람도 종종 실수를 하는데, 대부분의 실무자는 인쇄소직원이 아니니 크고 작은 실수가 있는 건 당연한 일입니다. 하지만, 최대한 실수의 오차를 줄이기 위해 실인쇄가 들어가기 전 샘플받기, 감리하기, PDF받기 등은 체크하셨으면 합니다.18. 뭘 엎었다.보통 책상위 오른쪽쯤에 커피같은 걸 놔두면 항상 마우스든 뭐든 선에 걸려서 자빠집니다. 오른쪽엔 뭘 놔두지 마세요. 그리고 엎었으면 빨리 치우세요. 어머 어쩌지?!.....하고 멀뚱하지 보고서있지 말구.19. 백업데이터 하드를 날려먹었다.이쯤되면 집에 가야됨..뭔가 백업을 하면서 헤헤헤, 백업이 알아서 되겠지 하고 원본파일을 지워버렸는데 백업이 제대로 안되고 다 날아가버렸고..................와우.(복구해야지 뭐.)20. 법인차량으로 사고를 냈다.안다쳤으면 다행입니다. 사람이 우선아니겠습니까. 보험처리하도록 합시다. 설마 아무리 법인차량이라지만 사고냈다고 "이런 배추김치꼬다리같은 자식아, 넌 돌아오면 시말서 깜지다!" 라고 윽박지른다면 그냥 그만두시는 게 좋을 듯 합니다. 큰 실수 작은 실수..뭔가 잘 정리해서 적어보려 했건만 지난 시간들을 되돌이켜 보며 생각나는 대로 쓴거라 순서에는 큰 의미가 없습니다. 돌이켜보니 참으로 리디큘러스하고 판타스틱한 일들을 많이 저질렀네요. 사실 사고는 반드시 나기 마련입니다. 그리고 그 사고는 대부분 '설마' 하는 부분에서 터지죠. 반드시 터집니다. 재고 수량을 확인 안하면 반드시 수량이 빕니다. 강사님이 전날 오시는 지 확인 안하면 다음 날 반드시 늦습니다. 파일 잘 갔겠지~ 라고 확인안하면 항상 안가있습니다. 백업이 되었겠지~~하고 파일을 지우면 백업이 안되어있다구요.설마란 없습니다. 세상엔 모든 일들이 벌어질 수 있어요.설마란 없습니다. 세상엔 모든 일들이 벌어질 수 있어요. 심지어 은행명이 다르고 계좌번호가 같은 곳도 있습니다. 실화였어요. 실제로 다른 은행에 쌩뚱맞은 사람에게 돈이 들어가버린 사태도 있었답니다. 우연의 일치와 살면서 한 번이나 일어날까말까한 일들이 여러분들 손에서 이루어지는 기적을 경험할 수 있는 곳이 바로 직장인 것 같아요.이렇듯 사고가 터지면 누구나 눈 앞이 아득해지고 사직서를 만지작 거리게 되지만, 사실 그렇게까지 막 모든 걸 책임지고 제가 떠나겠습니다....라고 자책할 일은 아닙니다. 그리고 떠난다고 책임을 지는 것도 아니구요. 책임은 그 자리에서 지는거죠. 모든 실수의 대처법은 머리론 기억하되 마음엔 담지말고 다음 일을 차근차근 해나가는 것입니다. 욕이야 당연히 한두번 먹으면 되고, 나 때문에 고생한 누군가에게 정중하게 감사와 죄송죄송을 표하면 될 일입니다. 따뜻한 봄 나른나른한 요즘인지라 자꾸 0이 00으로 보이고 메일주소도 헷갈리고 스케쥴도 오락가락 하실텐데, 모두 실수없이 아름다운 회사생활 되시길 바랍니다 :)어우, 이걸 그냥...에효..
조회수 2902

UX 개인화 트렌드

(UX 디자이너 Nikita Gangwal의 미디엄을 번역하였습니다. 의역 많습니다. 원문 출처: https://medium.com/@nikitagangwal.27)어떻게 하면 앱 개인화를 통해 사용자 경험을 다음 단계로 끌어올릴 수 있을까?앱을 비롯한 많은 제품들의 개인화는 사용자들에게 유용한 정보를 추가적으로 제공함으로써(그들이 요구하지 않았음에도) 사용자 경험을 한 단계 끌어올린다. 낯선 장소에 방문했을 때 그 곳에 대한 추천을 받고 싶지 않은 사람은 몇이나 될까? 매일 아침 간단한 아침 레시피를 빠르게 추천받고 싶지 않은 사람은? 만약 네비게이션이 당신의 사용 패턴을 파악한 뒤 매일 저녁 6시에 집으로 향할 것인지 조심스레 물어본다면 어떨까? 이 모든 것들이 당신이 요청하기도 전에 이뤄진다면? 이처럼 인간은 누군가가(혹은 무언가가) 자신들을 관리해주고 작업량을 덜어줄 때 편안함을 느끼는 경향이 있다.‘개인화’란 무엇일까?시스템이 정보, 기능, 콘텐츠 등을 사용자의 니즈와 기대에 따라 실시간으로 추천(curation)해줄 때, 우리는 이것을 개인화라고 부른다. 이 때 앱은 사용자에게 가장 최적화된(unique) 경험을 제공하기 위해 시스템 내에서 유저 데이터로부터 습득한 기술을 사용한다.개인화는 왜 필요한가?사람들은 앱이 개인적인 경험을 제공할 때 더 좋아한다. 자신이 좀 더 특별하고 앱과 연결된 것처럼 느낄 수 있기 때문이다. 만약 앱에 개인화된 요소가 단 하나도 없다면, 해당 앱이 실제로는 도움이 될지언정 사용자는 딱히 도움이 안된다고 느낄 수도 있다. 이해를 돕기 위해 Spotify Running의 사례를 보면, 스포티파이는 이제 단순한 음악 감상 앱이 아니다. 스포티파이는 단순 음악 감상에서 나아가, 사용자가 달리는 속도에 맞는 템포의 음악을 추천함으로써 달리기를 계속할 수 있도록 의욕을 불어넣어 준다. 핸드폰 센서를 통해 사용자가 분 당 몇 걸음이나 걷는지를 감지하고 이와 비슷한 비트의 곡을 찾아내는 것이다. 이처럼 Spotify Running이라는 고도화된 개인화 기능이 없다면, 스포티파이는 단순한 음악 앱에 불과할 것이다. 오해는 마시라. 플레이리스트에서 내가 가장 좋아하는 곡을 찾아 듣는 경우라면, 스포티파이는 여전히 최고다. 하지만 여기서 한걸음 더 나아가, 개인화된 서비스는 사용자의 페인 포인트와 니즈에 적절히 대응할 수 있다는 점에서 대단히 매력적으로 어필할 수 있다. 이는 빠르게 사용자와 앱 사이의 유대감을 만들어내고 해당 브랜드에 대한 충성(loyalty)을 이끌어낸다.앱 내에서 개인화 서비스가 효과적으로 이루어진다면, 당신은 브랜드에 대단한 충성심과 애착(affinity)을 지닌 사용자들을 확보한 셈이 되고 장기적으로 매출이 눈에 띄게 오를 것이다. 연구에 따르면 유저가 멋진 사용자 경험을 했을 경우 ‘closing the deal’ 상황에서 더 빠르게 행동하는 경향이 있다.어떻게 제대로 실행할 것인가?1) 리서치, 또 리서치: 절대 이 단계를 건너 뛰면 안된다. 당신의 브랜드에 개인화가 필요하다면 어느정도가 적당한지 반드시 이해하라. 사용자에게 무엇이 중요한지, 한계는 무엇인지, 다양한 고객과 관련되어 있는지 파악하라. 2) 단순하고 자연스럽게: 사용자가 앱을 이용할 때 단 1초라도 혼란을 느낀다면 좋은 경험이 아니다. 개인화는 물흐르듯한 사용자 경험을 위한 첫 단계이기 때문에 사용자가 거의 눈치챌 수 없을 정도로 매끄러워야 한다.3) 반복적인 테스트: 1단계 리서치 만큼이나 중요하다. 유저 테스트는 지금 하고 있는 것이 옳은 것인지 알 수 있는 유일한 방법이다. 반복된 테스트를 통해 발견하는 인사이트는 대단히 놀라울 것이다. 이를 통해 각각의 사용자에게 꼭 맞는(tailor-made) 경험을 제공할 수 있을 것이다.Luminosity사용자의 매일의 활동과 스킬 수준에 따라 훈련 활동을 추천해주는 고도로 개인화된 두뇌 훈련 앱이다. 사용자는 앱에 빠르게 적응함으로써 서비스를 대단히 흥미롭고 재미있게 이용할 수 있다.Airbnb최근 업데이트된 에어비앤비 모바일 앱은 개인화가 핵심이라고 한다. 사용자가 도착지를 설정하면 에어비앤비는 백만 개가 넘는 현지인의 여행 팁이 담긴 해당 도시의 가이드북을 제공해준다. 가이드북에는 해당 도시에 살고 있는 호스트가 직접 추천한 최고의 식당, 경험, 볼거리 등이 담겨있다.Netflix넷플릭스는 사용자가 봤던 영화들과 비슷한 종류의 시리즈를 추천해줌으로써 개인화를 제공한다. 이들은 사용자들의 영화 관람 내역과 랭킹을 모니터링하는 복잡한 자체 알고리즘을 통해 이를 성공적으로 해내는 중이다.YummlyYummly는 사용자의 음식 선호도나 제약 조건과 관련된 레시피를 찾도록 도와주는 유명한 사이트 중 하나다. 사이트에 접속했을 때 사용자들은 본인의 음식 선호를 나타내는 조건들을 세팅하게 된다. Yummly는 이 조건들과 함께 사용자의 검색 패턴을 이용해 그들의 기대에 완벽하게 부응하는 레시피를 추천한다.개인화가 잘못되었을 때...개인화 경험을 제공할 때 주의를 기울이지 않으면 사용자와의 유대감 뿐만 아니라 그동안 이들과 쌓아올린 브랜드의 신뢰도까지 망가질 수 있다. 이러한 실수를 방지하기 위해서는 당신의 사용자에 대해 잘 아는 것이 중요하다. 요약하자면, 개인화는 적절한 사람에게 적절한 타이밍에 적절한 양의 정보를 제공할 때에 효과적이다.Know your audience!완전히 잘못된 타이밍에 적절하지 않은 사용자에게 정보를 제공한다고 가정해보자. 어떤 일이 벌어질까? 아마 당신은 전반적인 브랜드 평판에는 영향을 주지 않는 작은 에러 정도로 생각할 수도 있겠다. 하지만 불행히도 마케팅이 잘못됐을 때, 이는 정말(very, very wrong) 잘못될 가능성이 있다. 이 예가 대단히 정확하진 않지만 나는 시사하는 바가 있다고 생각한다. 다음과 같은 최악의 고객 경험을 생각해보자. 이 아버지의 날 광고는 고객 타겟팅을 할 때 절대 하지 말아야 할 완벽한 예시다. 이 광고는 많은 잠재 고객들을 고려했지만, 때때로 이 가정들이 옳지 않을 수도 있음을 보여준다. 잠재 고객의 고통을 의도한 건 아니지만 이는 명백히 잘못된 타겟팅이다.몇 가지 중요한 팁들1. 개인화를 가치를 더하기 위해 사용하라. 그냥 중요하다고 느껴져서 사용하는 것은 삼가라.2. 잠재 고객에 대해 가정하지 마라. 완전하고 정확한 정보를 얻을 때까지 조사하고 테스트하라.3. 개인화가 너무 자세해지면(If it goes to far) 사용자는 소름끼쳐 한다.4. 좋은 사용자 경험은 미묘하고 눈에 띄지 않을 정도로 이루어질 때 가능함을 기억하라.사용자 경험의 전망개인화 트렌드는 계속 진화하고 있다. 가장 친숙한 개인화 방법은 사용자의 이름을 불러주거나 키워드 매칭을 통해 그들과 관련된 콘텐츠를 보여주는 것이다. 현재 앞서 살펴본 사례들과 같이 다방면에서 개인화된 앱을 볼 수 있다. 앞으로 머신러닝이 사용자의 정성적인 데이터로부터 ‘스토리’를 이해하고, 사용자들이 삶 전반의 다양한 변화를 경험함에 따라 개인화는 완전히 새로운 수준의 완성도를 갖추게 될 것이다. 이를 통해 개인화된 앱들은 사용자에게 놀라운 경험을 제공하게될 것이다.#더팀스 #THETEAMS #디자이너 #디자인 #인사이트 #성장 #마음을움직이는디자인
조회수 929

전격! 어린시절 파헤치기

안녕하세요~!오늘은 미드레이트를 함께 만드는 분들의 어린시절을 탐험해보는 시간을 가져보려고 해요!^^미드레이트를 총괄하고 계신 대표님과 이사님들의 과거를 낱낱이(?) 파헤쳐보겠습니다먼저미드레이트 이승행 대표님의 어린이 시절을 만나볼까요~짜잔!! 너무나 똘망똘망하지 않나요!!지금의 부리부리한 눈빛과 카리스마는 갑자기 생겨난 게 아니었습니다어릴 때부터 좌중의 마음을 뒤흔들었을 것 같은 강렬한 눈빛~~어이쿠 계속 보고 있으니 깊고 까만 눈 속에 빠져드는 것 같습니다정신이 혼미~~이 아이가 어떻게 컸을까요어언 20여년이 흐른 후 ........짠대표님께서 이전에 SK E&C다니시던 시절 사진인데요^^화르륵젊음과 활활 타오르는 청춘이 느껴집니다!그리고오른쪽 뒤에 글자 보이시나요?"미래로, 세계로"저 때부터 이미 미래로, 세계로 나아가겠다는 대표님의 큰 꿈이 벽에 나타나고 있었습니다.어쩌면 이미 미드레이트가 대표님 마음 속에 탄생해서 자리 잡고 있었을지도 모르겠네요~그 다음 사진으로~단체사진임에도! 딱 센터에 눈에 띄는 분이 있죵~?요리보고 저리봐도 지금의 미드레이트 대표! 이승행 대표님!+ 뒤에 한 분 더 눈에 띄는 분이 있죵~?이미 짐작하신 분들이 많이 계시겠지만 ^^ 미드레이트의 개발을 담당하고 계신 백승한 이사님이십니다.두 팔을 벌려 우주의 기운을 받고 있는 듯 합니다.... :)이 기운을 받아 미드레이트를 건실하게 개발하고 계신 거라 믿습니다!!!한 번 더 독사진으로 만나볼까요~?이사님께서 이 때의 상황을 설명해주셨는데요소련 고르비 은퇴식 때 찍은 사진입니다라며......... ^^하핫 러시아 대륙 한파에도 정말 끄떡없을 것 같아 보이네요!그리고미드레이트에는 또 한 분의 이사님이 계시죠~^^자자~ 대표님과 함께 찍은 투샷으로 먼저 만나보겠습니다어디가서 못 보는이승행 대표님과 신규식 이사님의 얼마 지나지 않은 젊은 시절 대공개!두 분다 눈이 정말 초롱초롱하세요~~미드레이트는 초롱초롱 군단만 가입가능한걸까요?저는 아닌 것 같군요......하지만 마음만은 누구 못지 않게 초롱초롱하답니다!믿어주세요~~~믿어주신다고요? 감사합니다 사랑합니다 고객님흠흠,, 다시 본론으로 돌아가여기서 다시 더 먼 과거로 돌아가 신규식 어린이를 만나보겠습니다!나는 이 다음에 커서 세계를 제패할거야야망이 깃든 표정인걸요?이 때만 해도 몰랐었겠죠미드레이트라는 새로운 업체에서 새로운 일을 하고 있을지!각자 다른 곳에서 태어났지만SK라는 공간을 통해 만났고현재 미드레이트를 함께 이끌고 계신 세 분!이승행 대표님 & 백승한 이사님 & 신규식 이사님과거 사진을 엿보았는데요즐거우셨는지요!^^저만 즐거웠던 것 아니죠?ㅎㅎㅎ다음에 더욱 재미있는 이야기 들고 오겠습니다!Bye Bye~미드레이트 자주 방문하시고 다양한 투자 소식도 받아가세요!#미드레이트 #팀원 #팀원소개 #팀원자랑 #팀자랑 #회사문화 #조직문화 #기업문화
조회수 1104

Rxjava를 이용한 안드로이드 개발

Overview브랜디는 현재 2.0 기반 Android 버전입니다. Main Thread와 Sub Thread 사이의 ANR를 방지하려고 Volley, Otto Bus Library를 사용해서 백엔드 서비스(back-end Service)를 연동하고 있습니다. 이제 3.0 개발로 더 좋은 백엔드 서비스 기능을 만들려고 합니다. (기반 작업은 이미 완료했습니다.) 다만 3년 동안 브랜디 앱을 개발하면서 느꼈던 고통과 피로를 ‘제발’ 줄여보고 싶어서 브랜디 3.0에서는 Retrofit2 와 RxJava, Lambda 표현식을 사용하기로 했습니다. RxJava(Reactive programming)는 가장 추천하고 싶은 것 중 하나입니다. 우리는 함수형 리액티브(반응형) 프로그램이라는 표현으로 자주 마주치곤 하는데요. 주로 옵저버 패턴(Observer pattern)을 대체하기 위해 사용합니다. 단순히 데이터를 넘기고 마무리하는 건 옵저버 패턴으로도 충분하지만 대부분의 문제는 이벤트들을 묶어서 사용할 때 생깁니다.1) RxJava는 이벤트에 대한 조건 처리나 실패 처리, 리소스 정리에 대비해 사용합니다. 기존 방식의 명령형 리액티브 접근 방식을 사용하면 복잡함이 지속적으로 증가하는 반면, 함수형 리액티브 프로그래밍은 효율을 크게 높일 수 있습니다. 몇 가지 예제와 함께 살펴보겠습니다. Android에 직접 사용해보기새로운 프로젝트를 생성한 후, 아래와 같이 build.gradle 파일을 수정해봅시다. (JDK 1.8 설치 필수) apply plugin: 'com.android.application' android {    compileSdkVersion 26   defaultConfig {        applicationId "kr.co.brandi.myapplication"        minSdkVersion 21        targetSdkVersion 26        versionCode 1        versionName "1.0"        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"    }    buildTypes {        release {            minifyEnabled false            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'        }    }   //추가된 부분 1      compileOptions {        sourceCompatibility JavaVersion.VERSION_1_8        targetCompatibility JavaVersion.VERSION_1_8   }  } dependencies {    implementation fileTree(dir: 'libs', include: ['*.jar'])       //추가된 부분2    implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'    implementation 'io.reactivex.rxjava2:rxjava:2.1.3'      implementation 'com.android.support:appcompat-v7:26.1.0'    implementation 'com.android.support.constraint:constraint-layout:1.0.2'    implementation 'com.android.support:design:26.1.0'    testImplementation 'junit:junit:4.12'    androidTestImplementation 'com.android.support.test:runner:1.0.1'    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' } 이제 람다 표현식과 RxJava를 사용할 준비가 되었습니다.Flowable.just("Hello World").subscribe(new Consumer() {    @Override   public void accept(String s) throws Exception {        Log.v(tag, s);   }  });   Flowable.just("Hello World !").subscribe(s -> Log.v(tag, s)); 간단한 생성자와 결과를 출력하는 부분입니다. 두 번째 subscribe는 람다 표현식으로 인터페이스를 생성하지 않더라도 첫 부분과 동일하게 결과물을 얻을 수 있습니다.2) 이제 RxJava에서 간단한 필터링으로 간편하게 데이터를 가공하는 능력을 확인해보겠습니다. 아래 코드는 기본적인 List 의 값을 출력하는 부분입니다.List valueList = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);   for (int data : valueList) {    String result = "value " + data;    Log.v(tag, result);  } Flowable.fromIterable(valueList)        .map(new Function() {            @Override            public String apply(Integer data) throws Exception {                return "value : " + data;            }        })        //.map(data -> "value : " + data)        .subscribe(data -> Log.v(tag, data)); 위의 코드에 조건을 추가해 ’짝수’만 출력하겠습니다.for (int data : valueList) {    if ((data % 2) == 0) {        String result = "value " + data;        Log.v(tag, result);    }  } Flowable.fromIterable(valueList)        //.filter(data -> {        //      return (data % 2) == 0;        //})        .filter(data -> (data % 2) == 0)        .map(data -> "value : " + data)        .subscribe(data -> Log.v(tag, data)); 위와 같이 데이터 가공은 순차적으로 진행되고, 여러 함수로 간단하게 처리할 수 있습니다. 원하는 데이터 가공을 위해 filter, map 등의 함수들을 순차적으로 이어 붙일 수도 있습니다.위에서 보여드린 RxJava는 간단한 예시이기 때문에 RxJava 의 기능을 좀 더 보여드리겠습니다.String[] data1 = {Shape.HEXAGON, Shape.OCTAGON, Shape.RECTANGLE};  String[] data2 = {Shape.TRIANGLE, Shape.DIAMOND, Shape.PENTAGON};   Flowable source =        Flowable.combineLatest(                Flowable.fromArray(data1)                        .zipWith(Flowable.interval(100L, TimeUnit.MILLISECONDS), (shape, notUsed) -> Shape.getId(shape)),                Flowable.fromArray(data2)                        .zipWith(Flowable.interval(150L, 200L, TimeUnit.MILLISECONDS), (shape, notUsed) -> Shape.getSuffix(shape)),                (id, suffix) -> id + suffix);   source.subscribe(s -> Log.d(getThreadName(), s)); CombineLatest() 함수를 이용해 두 개의 스트림을 하나로 처리하는 방법을 보여 드렸습니다. 각각의 스트림은 interval 함수를 시간 간격으로 data1과 data2 배열의 개수만큼 반복하여 처리하는 로직입니다. 서로 다른 두 스트림은 마지막 데이터를 가지고 있으며 새로운 데이터가 나올 때마다 하나의 스트림으로 출력됩니다.마블 다이어그램 3)결과Conclusion만약 RxJava를 이용하지 않고 두 개의 TimerTask를 이용해서 코딩했다면 결과는 같았을지도 모릅니다. 이제 RxJava를 알기 때문에 다시는 TimerTask를 이용해서 코딩할 일은 없을 겁니다. 알면 알수록 다양한 기능을 갖추고 있는 RxJava! 이제 브랜디 상용화 버전에 사용할 수 있게 다시 개발의 숲으로 들어가겠습니다. 그럼 이만. 1)함수나 네트워크 호출의 비동기 응답 2)Java 8 람다 표현식 자세히 살펴보기, 2018.03.09. 3)RxJava on Android 글고재성 과장 | R&D 개발1팀[email protected]브랜디, 오직 예쁜 옷만#브랜디 #개발문화 #개발팀 #업무환경 #인사이트 #경험공유
조회수 3331

열정 넘치는 디자이너, 바로 나

목차미국 HCI 석사 준비1. 필요한 정보 습득- UI/UX/Product/Interaction Design 관련 대학원 목록- 미국 대학원 지원 준비- 미국 대기업에 다니는 디자이너들에 대한 정보 수집- 미국 대기업이 원하는 디자이너의 역량- 디자이너가 알아야 할 툴들 공부2. 열정으로 닥치는 대로 공부 & 경험- Dribbble & Behance 보고 따라 해 보기- 혼자 하는 프로젝트, 포트폴리오 콘텐츠 만들기- 디자인 관련 밋업, 스터디, 컨퍼런스 참가- 남는 시간 활용 (아티클, 블로그, 유튜브 강의 등)- 기본적인 웹 코딩 배워놓기- 스타트업 또는 회사 경험대학원에서.. 그리고 인턴쉽 지원1. 학교 수업 및 프로젝트- 첫 학기의 승부- 배워보고 싶은 것들 vs. 배워야 하는 것들 vs. 아쉽지만 버려야 할 것들- 실시간 포트폴리오 업데이트- 교내 프로젝트 외 다른 도전2. 조교- 조교의 혜택과 이점- 지원할만한 연구실 목록 작성- 연구실 지원 준비- 열심히 일하기3. 인턴쉽 지원 준비- 회사들에 대한 지식 및 공부- Referral- 이력서 디자인- 포트폴리오 외 어필할 방법4. 경험담을 글로 풀어내기- 개인 블로그 시작- 디자이너에게 블로깅이란?5. 회사 지원 및 인터뷰- Google Analytics를 통한 방문자 분석- Recruiter와의 대화- Interviewer 파악하기- 실전짧은 소개현재 난 Georgia Institute of Technology에서 HCI (Human Computer Interaction) 석사를 전공하고 있다. 그 전에는 University of Michigan - Ann Arbor에서 Psychology를 전공했다. 지금 이 글을 시점에는 미국 Facebook에서 Product Design Intern으로 근무하고 있다. 많은 디자이너와 다르게 디자인 전공이 처음에는 아니었기 때문에 내가 여기까지 오기까지의 경험담을 정리한 글을 쓰고 싶었다. 최대한 다양한 정보를, 그리고 나만의 노하우를 적고 싶었고 100% 만족스럽진 않지만 그래도 누구에겐 도움이 됐으면 좋겠다. 이렇게 관심 가지고 나의 글을 읽으러 들어와 주셔서 매우 감사하다.미국 HCI 석사 준비1. 필요한 정보 습득UI/UX/Product/Interaction Design 관련 대학원 목록당연하게도 내가 제일 먼저 한 것은 미국에 있는 모든 디자인 관련 대학원 목록 정리였다. 캐나다와 미국에서 오랫동안 살아서 그런지 일단은 미국으로 돌아가서 공부하고 커리어를 쌓고 싶은 꿈이 있었다. 미국으로 돌아가 일을 하려면 미국 대학원을 나오는 게 제일 빠른 방법인 것을 알고 있었던 나는 서둘러 인터넷을 뒤지기 시작했다. 책상에 앉아 Google이나 Quora와 같은 곳에서 디자인 관련 석사 프로그램들을 마구 찾고 정리하기 시작했는데, 생각보다 리스트가 길어져서 추리는데 만해도 시간이 오래 걸렸다. 내가 정리한 종류별 학교 목록은 여기에서 볼 수 있다.찾는 도중 깨달은 점은 생각보다 UX 디자인 관련 대학원들이 너무나도 많다는 것이었고 최근 들어 많은 프로그램들이 개설된 것을 볼 수 있었다. 그리고 누구라 할 것 없이 그동안 배출한 학생들의 인턴쉽이나 취직 데이터를 보여주면서 어필하기도 했다. 나 같은 경우에는 HCI (Human Computer Interaction)라는 컴퓨터 공학, 디자인 그리고 심리학 외에 여러 학문을 융합한 학과가 너무나도 끌렸기 때문에 그쪽 관련 대학원들을 집중해서 봤다. 찾다 보니 카네기멜론, 조지아텍, 워싱턴, 미시간, 인디아나 등의 학교들이 유명하고 경쟁률도 상당히 높다는 것을 알게 되었고 하루라도 빨리 준비를 서둘러야겠다고 마음먹었다.미국 대학원 지원 준비일단 대부분의 학교들은 지원할 때 대학교 성적표, 포트폴리오 (optional인 곳도 있지만),  Statement of Purpose (자기소개서), GRE 점수, TOEFL, 교수님 또는 직장상사 추천서 등이 필요하다. 포트폴리오 같은 경우에는 웹사이트나 PDF 파일로 제출할 수 있으며 디자인 배경이 아닌 학생일 경우에는 지원하지 않아도 되는 경우도 있다. 자기소개서는 대부분 온라인 지원양식에 첨부파일로 넣거나 직접 Input field에 쓴다. 대게 왜 대학원을 지원하는 것에 대한 질문이고 나의 배경과 나의 꿈에 대해 설명하라는 경우가 많다. GRE는 학교마다 공지하는 평균 점수대가 웹사이트에 공지돼 있는 경우를 볼 수 있는데 솔직히 정말로 못 보지 않는 이상 큰 의미는 없는 것 같다. GRE는 Verbal, Quant 그리고 Essay로 나누어지는데 박사 지원을 하는 게 아닌 이상 Verbal은 155점만 넘으면 안전한 것 같고 Quant는 한국사람이라면 160점은 넘을 수 있기 때문에 오히려 조금이나마 플러스 요인이 되는 것으로 알고 있다. 마지막으로 Essay는 3.5점만 넘으면 웬만한 수준은 되는 걸 보여주는 것이기 때문에 괜찮은 것 같다 (분명 더 잘 보면 좋긴 하다). 나는 사실 GRE를 처음 공부할 때 해커스학원을 끊어보긴 했지만 한국식 문제풀이 방식에 익숙하지 않아서 그냥 학원을 취소하고 집에서 단어만 집중적으로 외웠다. 개인적인 생각으론 GRE 만점을 맞기를 목표를 하기보다는 적당한 점수를 목표로 한 다음에 포트폴리오나 자기소개서 같은 것에 집중을 하는 게 현명한 것 같다. TOEFL은 같은 경우에는 미국대학교를 졸업하면 면제를 주는 학교는 몇몇 있지만 카네기멜론 같은 경우에는 인정해주지 않기 때문에 어쩔 수 없이 시험을 봤어야 했다.여기서 자기소개서에 대해서 내 생각을 잠시 얘기를 하자면 난 그게 학교 측에서 꼭 읽어보는 중요한 서류라고 생각된다. 사람마다 각기 배경이 다르고 대학원에 지원하고 싶은 적절한 이유가 있으며 대학원을 통해 이루고자 하는 명확한 목표가 있기 때문에 학교 입장에서는 나에 대한 전반적인 정보를 한 번에 스냅샷으로 볼 수 있는 글이 된다. 나 같은 경우에는 지원서를 쓸 때 처음 기계공학으로 대학교를 들어가고 심리학으로 전과할 때까지 그리고 그 후, 디자인에 "디"자도 모른 상태로 처음 HCI나 UX 관한 이야기들을 접했을 때 모든 것을 다 내려놓고 거기에 심취하게 된 내용으로 출발했다. 그 후, 혼자 피땀 나는 노력으로 독학을 하고 여러 사람들을 만나서 영감도 얻고 정보도 얻으면서 전반적인 디자인 관련 실력과 지식을 늘려갔고 전문적인 교육을 받기 위해 대학원에 지원한다는 내용을 썼다. 스토리 사이사이에 녹아든 나의 디자인에 대한 열정과 포트폴리오를 코딩해서 직접 만들어본 것 등을 토대로 최대한 Strong Case를 만들었던 것 같다. 자기소개서에서는 자신만의 특이한 스토리가 있어야 모든 것이 비로소 흥미로워지고 "이 학생은 아직 많이는 모르지만 대학원을 통해서 정말 많이 성장할 수 있겠구나"라는 생각을 가지게 하는 것이 중요한 것 같다.다음, 포트폴리오에 대해서 좀 더 자세히 언급하자면 나 같은 경우에는 대학교를 심리학으로 졸업했기 때문에 웹사이트는커녕 Static 한 포트폴리오조차도 없었다. 아니 다시 말하자면, 포트폴리오는커녕 디자인이 관련된 것은 정말 하나라도 가진 게 없었다. 대부분의 학교들은 포트폴리오가 Optional이 었지만 뭔가 없으면 불리할 것 같았다. 결국 나에게는 8월부터 12월까지 4개월 남짓의 시간이 있었는데 크게는 두 가지의 길이 있었다. 하나는 Squarespace나 Wix 같은 웹사이트를 쓰고 디자인 결과물을 몇 개라도 만들어서 포트폴리오를 채워보려고 노력하는 것, 그리고 또 하나는 직접 포트폴리오 홈페이지를 코딩으로 만들어 보는 것이었다. 사실 그 당시에 무작정 학원을 끊어서 HTML/CSS/JS 기초를 잠시 배워봤는데 너무나도 적성에 잘 맞았다. 수업이 끝나면 혼자 집에 가서 복습도 하고 혼자 예습도 해봤는데 온라인에 나와있는 정보들이 너무나도 많아서 금방 학원에서 나가는 진도가 너무 느린 것처럼 느껴졌다. 나중에는 학원을 가지 않고 혼자 집에서 직접 여러 가지를 만들면서 최대한 빨리 배우려고 노력했다. 지금 생각해도 코딩 같은 경우에는 마냥 유튜브 강좌를 듣는 것보단 직접 간단한 것이라도 만들어보는 것이 실력 향상에 가장 많은 도움을 줬던 것 같다. UX 포트폴리오에 대해서 잠시 쓴 블로그도 있다 추가로 내가 코딩을 공부하고 포트폴리오를 직접 코딩하겠다고 마음먹은 것 중 하나는, 대학원 지원 이력서에 HTML/CSS/JS를 할 줄 안다고 쓰고 아무런 결과물을 보여주지 않는 것보다는 허접하지만 그래도 뭐라도 보여주는 것이 결과를 완전히 바꿀 수 있는 비장의 카드라고 생각했기 때문이다. 김칫국을 마시면서 부끄럽지만 혼자 침대에 누워서 학교 면접관에 빙의해서 이렇게 생각도 해보았다. "심리학 갓 졸업한 학생이 혼자 코딩을 공부해서 포트폴리오를 만들었다니! 참 열정이 대단하군!"이라고...미국 대기업에 다니는 디자이너들에 대한 정보 수집대학원 지원 준비를 밤낮으로 하는 도중, 꾸준히 한 것이 몇 개 더 있다. 하나는 좀 Creepy 하게 들릴 수 있지만 난 항상 시간이 날 때마다 LinkedIn을 통해서 내가 관심 있는 대학원에 다니는 사람들이 어느 기업에 인턴쉽이나 정직원으로 갔는지 프로파일 스토킹을 했다. 그냥 검색하면 나오는 정보이기에 찾는 것은 매우 수월했지만 흥미롭고 전율이 흘렀던 점은 몇몇 학생들은 정말로 내가 꿈에 그리는 회사에 다니고 있는 것이었다. LinkedIn에 나와있는 이력서를 보게 되면 그 사람이 다니는 학교는 어디였는지, 들었던 수업은 어떤 것들인지, 포트폴리오는 어떤 식으로 디자인돼있고 프로젝트들은 어떤 것들이 있는지 등을 관심 있게 눈여겨봤다. 몇 개의 포트폴리오들은 시간이 가는 줄 모르고 보고 북마크로 저장하기도 하면서 "언젠간 나도 저렇게 돼야지"라고 생각했다. 하지만 반대로 어떤 포트폴리오 웹사이트들은 둘러보기가 불편한 경우도 있었으며 디자인 경험이 많지 않은 내가 봐도 전체적인 느낌이 생각보다 별로 였던 것들도 있었다. 이렇듯이, 여러 사람들의 포트폴리오를 보는 것들이 나에게 많은 영감과 자극을 주었다. 닮아야 할 것들 그리고 닮지 말아야 할 것들 또한 많이 느끼고 얻어간 것 같다.미국 대기업이 원하는 디자이너의 역량내가 했던 것들 중 또 다른 하나는, 미국에 많은 사람들이 아는 구인구직 사이트를 통한 디자인 직종 분석과 회사가 포스팅하는 디자이너 모집글들을 시간 날 때마다 하나씩 읽어보는 것이었다. 미국에서는 대부분 LinkedIn Jobs, Indeed 그리고 Glassdoor와 같은 곳이 있는데 난 거기서 매일 같이 Product Designer, UX Designer 그리고 Interaction Designer 등 여러 디자이너 직업을 찾아보았고 회사마다 다른 이름의 직책과 뽑는 시기, 그리고 지원방식이 있는 것을 알게 되었다. 정직원은 물론이거니와 인턴쉽에 관한 정보도 끊임없이 노트에 쓰기도 하고 머릿속으로도 정리했다. 예를 들어 구글 UX Design Intern은 1월 초에 지원이 열리고 페이스북 Product Design Intern은 12월 전에 마감이 되는 등, 이런 꿀 정보들은 지금 생각해보면 대학원에 들어가서 다른 친구들보다 빨리 포트폴리오를 정리하고 지원할 수 있는 계기를 마련해 준 것이 아닌가 라고 생각한다. 정보가 힘이다!디자이너가 알아야 할 툴들 공부"난 디자이너예요 또는 난 디자이너가 되고 싶어요"라고 말하기 위해서 제일 중요한 것 중 하나가 바로 Skill Set이다. 흔히 디자인을 모르는 사람들이 디자인을 생각하면 스케치보다는 포토샵을 더 많이 말하는 경우가 있는데 아무래도 요즘은 스케치 시대다. 지금 내가 인턴으로 있는 페이스북에서도 전부다 스케치를 쓴다. 하지만 내가 처음 디자인 툴을 공부해야겠다고 마음먹었을 때 배운 것은 포토샵이나 일러스트레이터다. 하지만 점점 포토샵보단 스케치를 쓰는 트렌드가 왔고 당연히 학생으로선 트렌드를 따르는 게 자연스러웠다. 게다가 스케치는 포토샵으로는 할 수 없는 여러 추가 기능들이 있었으며 너무나도 편리했다. 스케치를 써본 사람이라면 아마 대충 알 것이다. 이런 디자인 툴들 말고도 와이어 프레이밍이나 프로토타이핑 또한 요즘 디자이너가 갖춰야 할 능력임을 알았기에 많이 다운로드하여보고 실제로 툴마다 몇 가지를 만들어보기도 했다. 시간이 지나다 보니 선호하는 툴들이 생겨났고 Framer나 Origami는 특히 페이스북 그룹에 가입했다. 커뮤니티가 매우 잘 돼있었고 다른 사람들이 만든 파일을 들여다보거나 모르는 게 있으면 질문도 해보았다.2. 열정으로 닥치는 대로 공부 & 경험Dribbble & Behance 보고 따라 해 보기매일매일 피와 땀을 흘리며 디자인 관련 툴들을 손에 익혔다. 단축키를 서서히 익혀가고 스케치 같은 경우에는 Symbol사용에 익숙해졌고 각종 유용한 Plug-in들도 활용해보는 등 점점 나의 실력은 향상되어갔다. 실력을 향상하는데 가장 도움이 많이 됐던 것은 Dribbble이나 Behance에 나오는 맘에 드는 디자인들을 따라 해 보는 것이었다. 가끔 대중교통을 이용할 때나 집에서 자기 직전, 정말 멋있다고 생각했던 디자인들을 스크랩이나 스크린샷으로 저장을 해놓고 최대한 똑같이 만들어보았다. 처음에는 그림자나 색깔 사용이 익숙하지 않아서 애를 먹었다. 하지만 그럴 때일수록 혼자 연구도 해보았고 각종 시행착오를 통해 최대한 비슷하게 만들 수가 있었다. 그러다 보니 제법 실력이 늘게 되었고 특히 폰트를 적절히 트렌트에 맞추어 사용하는 법도 제법 많이 늘기 시작했다. 색의 배합도 많이 개선되었고 타이포그래피 연습도 꾸준히 했다. 지금도 딱히 비주얼적으로 뛰어난 디자이너라고 할 수는 없지만 개인적으로 모든 디자이너가 꼭 그럴 필요는 없다고 생각하기 때문에 현재에 매우 만족하고 있다. 하지만 지금도 Dribbble에 업데이트를 시간이 날 때마다 하는 등 많은 노력을 하고 있다.혼자 하는 프로젝트, 포트폴리오 콘텐츠 만들기아까 대학원 지원 당시 나의 포트폴리오에 대해서 살짝 얘기를 해보았는데 이런 궁금증을 가졌으리라고 생각된다. "웹사이트의 윤곽은 만들었다 치고 도대체 그 안에 어떤 내용을 넣었을까?" 다시 말하지만 대학교를 디자인 외의 전공으로 졸업했기 때문에 디자인 관련 프로젝트는커녕 아예 넣을만한 소재가 없었다. 하지만 포트폴리오 윤곽은 다 만들었는데 내용이 없으면 그저 빈 껍데기밖에 안된다. 그래서 혼자 고민 또 고민을 했는데 아무래도 혼자라도 프로젝트를 해보는 게 최선의 방법임을 깨달았다. 처음에는 평소에 사람들이 많이 사용하는 앱들을 다운로드하여서 혼자 Audit을 해보고 "어떻게 하면 더 좋은 UX를 만들 수 있을까? 어떻게 하면 디자인을 더 이쁘게 또 실용적이게 할 수 있을까?" 란 질문들을 던져보면서 프로젝트를 진행해 보았다. 혼자 방에 앉아 인터넷으로 리서치도 해보고, 워드에 나의 생각도 적어보고, 와이어 프레이밍도 연필로 쓱싹쓱싹 해보았다. 딱히 어떤 특출 난 아이디어를 학교 측에 보여주고 싶다기보다는 UX분야에 충분한 관심이 있고 혼자 무작정 해볼 만한 열정이 있으며 그것을 실현 가능하게 끔 하는 디자인 툴 사용 능력이 있다는 것을 증명하고 싶었다.짧은 시간 동안 여러 프로젝트들을 했는데 날씨 앱을 프로토타이핑까지 해서 더 재미있게 만들어보았고 캐릭터 디자인도 해보고, 각종 아이콘 디자인, 그리고 스타벅스 같은 유명 사이트들을 코딩으로 재현해보았다. 뭐든 관련이 있으면 일단 최대한 웹사이트에 추가로 넣어보았고 나의 200%를 보여주고 싶었기 때문에 콘텐츠를 덜 넣는 것은 상상도 못 하였다. 솔직히 그 당시에 나의 포트폴리오를 보면 당장 구멍으로 숨어버리고 싶겠지만 그 당시 면접관의 눈에는 나름 심리학을 나온 디자인에 "디"자도 모르고 코딩의 "코"자로 모를만한 학생의 열정을 높이 샀으리라 생각한다.디자인 관련 밋업, 스터디, 컨퍼런스 참가여기서 알아야 할 것은, 나는 결코 방에 틀어박혀 4개월 내내 공부만 하고 대학원 준비를 한 건 아니다. 페이스북이나 온오프믹스를 통해 각종 디자인 Meetup이나 스터디 또는 HCI 컨퍼런스에 참가하기도 했다. 그중 기억나는 스터디는 이준원 님과 진행한 Framer 스터디인데 일요일 아침마다 매우 재미있게 참가하고 많이 배워갔던 시간이었던 것 같다. 처음에 Framer에 대해서 알고 배울 때에는 너무나도 재밌었는데 모르는 것도 많아서 페이스북 그룹에 스터디가 있는지 궁금했었다. 그때 운이 좋게도 준원 님께서 스터디를 개설하고 주도해 주셨고 그때 정말 많이 배웠던 것 같다. 이렇게 무작정 실무 디자이너들이나 다른 학생들과 같이 스터디도 해보고 직접 얘기도 듣고 하는 것이 디자인이나 크게는 IT필드에 대한 전반적인 지식을 많이 쌓게 해 준 것 같다. 게다가 너무나도 멋진 디자인을 하시는 디자이너분들을 보면서 주먹을 꽉 쥐고 "언젠간 나도 저렇게 될 거야!"라는 생각을 하며 집에 돌아와 더 이를 갈고 열심히 했다. 지금도 같은 생각이지만 이런 이벤트나 모임들은 나가는 것이 정말로 많은 Motivation과 Inspiration이 되는 것은 분명 사실이다. 앞으로도 기회가 된다면 많이 참가하고 싶다.남는 시간 활용 (아티클, 블로그, 유튜브 강의 등)어렸을 때부터 지겹도록 듣는 말, 자투리 시간 활용. 항상 전교 1등들은 말한다, 화장실 가는 시간도 공부에 최대한 활용했다고... 여기서 내가 말하고 싶은 건 그 정도는 아니지만, 어느 정도까지는 열정과 관심만 있다면 할 수 있다는 것이다. 지금까지도 계속 말했지만 나는 버스에서나 지하철에서나 택시에서나 아니면 집에 잠들기 전에 시간 활용을 최대한 신경 써서 했던 것 같다. 대학원 지원 마감까지는 4개월이라는 짧은 시간밖에 없었던 것도 있지만 대학원을 지원하고도 입학은 보통 8월이나 9월이었기에 시간도 많이 남은 것을 알고 있었다. 게다가 대학원에 입학해서 실제로 수업을 듣고 프로젝트를 친구들과 진행해야 하기까지에는 많은 배경지식과 디자인/코딩 지식이 많이 필요할 것만 같았다. 학교에 가서 비실비실 끌려다니기보다는 프로젝트를 주도하고 열심히 하고 툴도 적절히 활용할 줄 아는 디자이너가 되고 싶었기에 매일매일 끊임없이 배웠다.조금 여유가 된다 싶으면 UX 관련 아티클이나 블로그를 많이 찾아봤고 운동할 때나 잠들기 전에는 유튜브 강의를 통해 많은 지식을 흡수했다. 그저 Google에 UX만 쳐도 쏟아 나오는 정보에 감탄을 금치 못했고 정말로 이 분야가 Hot하다는 것에 안도감을 느끼기도 했다. 또 놀라웠던 것은 너무나도 방대한 정보가 인터넷상에 판을 쳤고 전문적인 지식, 세세한 튜토리얼부터 취업하는 방법, 인터뷰 보는 방법, 포트폴리오 만드는 방법, 디자인 툴에 대한 설명, 비교 등 봐도 봐도 끝이 없었다. 하지만 시간이 가는 줄 모르게 너무 재미있었고 어떤 것들은 나의 능력 밖임을 깨달았을 때는 속상하기도 했다. 지금도 너무나도 많은 것들을 모르는 나 자신을 보며 스스로를 자책하고 채찍질한다. 지금 이 글을 쓰는 순간에도 배우고 싶은 것들이 너무나도 많다 그리고 배워야 할 것들이 너무나도 많다.기본적인 웹 코딩 배워놓기아까 잠시 말했지만 여기서 더 자세히 말해보려고 한다. "코딩, 과연 디자이너에게 필요한 스킬인가?" 나는 솔직히 코딩 중에서도 Front-end Development, 즉 HTML/CSS/JS에 관한 기본지식은 디자이너가 배우면 좋다고 생각한다. 실제로 많은 사람들이 "Should Designers Learn How To Code?"라는 질문들을 던지면서 디자이너가 코딩을 왜 배우냐라고 비아냥거리는 경우도 있는데 솔직히 내가 지금까지 코딩을 배워서 이득을 본 것을 생각하면 진짜 이런 말들을 들으면 서운하다. 대학원을 지원할 때 허접했지만 내가 손수 코딩한 웹사이트가 좋은 점수를 따게 해주었고 대학원에 들어가서도 새롭게 만든 나의 포트폴리오가 페이스북이나 다른 회사들 인터뷰를 볼 때 면접관의 눈썹을 올리게 해주었다. 게다가 학교에서 프로젝트를 할 때는 스케치 같은 툴로 하는 디자인뿐만 아니라 프로젝트를 위해 웹사이트를 만들어야 하거나, Framer로 프로토타이핑할 때, 다른 친구들보다 눈에 띄는 Impact를 줄 수 있었다.최근 페이스북 인턴쉽에서는 사람들이 실제로 사용하는 프로덕트에 내가 코드를 써서 리뷰도 받고 그것이 패스가 돼서 결국 프로덕션 되기도 했다. 솔직히 내가 엄청난 것을 만진건 아니지만 그래도 충분히 눈에 띌만한 개선을 한 건 사실이다. 그리고 엔지니어들 중 나랑 프로젝트를 같이 진행하는 Front-end 쪽에 집중하는 엔지니어가 있는데 걔랑 내가 만든 디자인에 대해서 회의를 할 때면 조금이라도 아는 척할 수 있는 자신감도 가질 수 있었다. 그렇기 때문에 나는 만나는 디자인 공부를 하는 학생이나 친구들을 볼 때 코딩은 적어도 기본적인 HTML/CSS/JS는 배워놓으면 좋다고 목에 핏대를 세우면서 얘기한다. 어떤 곳은 디자이너가 코드를 쓰면 싫어한다는 소리가 있는데 (코드가 읽기 힘들거나 더럽게(?) 코딩돼서?) 적어도 페이스북에서는 그런 얘기는 안 하는 것 같다. 내가 만약에 좀 더 깨끗이, 짧게 쓸 수 있다면 엔지니어가 시간 날 때 가르쳐 준다. 애초에 건들지 못할 것은 무작정 덤비면 안 되지만 내 능력 안에 할 수 있는 조그마한 일들은 항상 있기 마련이다. 엔지니어가 바빠서 다른 일들을 먼저 해야 되고 굳이 지금 해결해야 할 일이 아니라면 내가 물어버리고 천천히 시행착오를 통해 해보면 된다. 그렇게 배우는 거다.스타트업 또는 회사 경험시간은 정말 눈 깜짝할 사이에 흘렀다. 결국 12월쯤인가 아마 모든 대학원에 나의 지원서를 넣었던 것 같다. 마감일 전까지 부랴부랴 모든 것을 쏟아부었고 혹시나 빠진 것들이 있는지 확인, 또 확인했다. 포트폴리오 같은 경우에는 지원서를 넣고도 결과가 대충 나오기 시작하는 2,3월까지 언제든 학교 측에서 열람할 수 있기 때문에 수시로 업데이트를 했다. 그리고 꾸준히 블로그도 읽고 디자인 연습도 하고 LinkedIn에 다른 디자이너 스토킹도 하고... 그렇게 반복적인 것을 하던 중에 번뜩 생각난 아이디어가 있었다. "혼자 공부만 하지 말고 실제로 일을 해보면 어떨까?"그 당시 지원한 대학원을 모두 떨어질 것이라고는 생각하지 않았다. 그래서 입학 전 까지는 많은 시간이 남았다고 판단을 내렸고, 때문에 실제로 일 경험을 해보고 싶었다. 어느 날, 컴퓨터 앞에 앉아 구인구직 사이트들을 막 뒤지기 시작했다. 처음에는 대기업을 지원하고 싶었지만 디자인 인턴을 뽑는 곳이 많이 없었을뿐더러 대기업에서 배우는 것보다 스타트업이나 작은 기업에서 더 많은 것을 배울 수 있을 거라는 생각을 했었다. 그래서 관심 있는 곳에 이력서를 넣어보기 시작했다. 인턴쉽이든 정직원이든 나는 그저 경험을 쌓으려고 지원했다. 최대한 오래 일해보고 싶은 마음에 무슨 일은 하는 회사인지 딱히 많이 따져보지도 않았던 것 같다. 그 정도로 너무나도 배우고자 하는 열정이 넘쳤고 디자이너라는 직함을 달고 일을 해보고 싶었다.그때 운이 좋게도 몇몇 작은 회사들에서 연락이 왔다. 전화로 대충 면접을 보고 방문 인터뷰까지 거쳤는데 그중 해외에서도 좀 알려진 회사에서 인턴쉽 기회를 주었다. 사실 회사 측에서는 인턴쉽을 거치고 정직원을 뽑고 싶었을 텐데 그 당시에는 대학원 지원을 마친 때라 결과가 나오지도 않았는데 대학원을 간다고 하면 이상하게 보일까 봐 그냥 인턴쉽 경험만 하여야겠다는 생각이 강했던 것 같다. 그 당시에 나를 가이드해주던 디자이너분이 계셨는데 일도 열심히 하시고 되게 잘하셨던 것 같다. 기본적인 디자인 과제도 매일 내주셨는데 실력 향상에 정말로 많은 도움이 되었다. 그리고 짧은 몇 개월이었지만 정말로 값진 경험을 했다. 생각보다 많은 것을 배우고 비주얼 디자인 쪽으로 많이 연습도 했으며 이력서에 한 줄이라도 쓸 수 있게 되어서 매우 뿌듯했다.대학원에서.. 그리고 인턴쉽 지원1. 학교 수업 및 프로젝트첫 학기의 승부아까 언급했듯이 나는 대학원에 들어가기 전에 엄청나게 많은 정보를 습득하고 들어갔다. 내가 원하는 회사의 입사 지원이 대충 언제 열리고 언제 닫히는지, 준비해야 할 것들은 무엇인지 그리고 어떻게 나 자신을 포지셔닝해야 하는지에 대한 것들에 대한 준비를 학교 시작과 동시에 시작했다. 석사 프로그램은 2년이라 여름방학 때 모두 다 인턴쉽을 하러 떠난다. 인턴쉽은 돈도 벌고 경험도 쌓을 수 있는 좋은 기회이거니와 졸업 전 다른 정직원, 특히 New Grad position에 지원할 때 많은 어드밴티지가 있다. 그리고 전 세계에서 알고 있는 구글, 페이스북, 아마존 등의 회사들에서의 인턴경험은 첫 출발선을 성공적으로 장식할 수 있는 목표이다. 그래서 난 좋은 회사에서 인턴쉽 오퍼를 받는 것이 제일 중요하다고 생각했다. 그리고 그러기에는 1학년 1학기 때 최대한 많은 수업을 들어서 포트폴리오에 넣을 프로젝트를 최대한 많이 넣어야겠다고 생각했다. 왜냐하면 인턴쉽 지원은 대부분 11월에 시작해서 2월 말 정도면 끝난다. 분명 4월까지도 구하는 회사들이 있는데 대부분 이름 있는 유명회사들은 인턴 구하는 데는 문제가 없기 때문에 자리가 없어지기 마련이기 때문이다. 그렇게 되면 2학기 때 하는 프로젝트들은 사실상 포트폴리오에 넣지 못하게 되고 1학기 때의 프로젝트만이 인터뷰를 볼 때 설명할 수 있는 위치에 놓이게 된다.배워보고 싶은 것들 vs. 배워야 하는 것들 vs. 아쉽지만 버려야 할 것들그 누구처럼 나도 학교에 처음 입학하고 들을 만한 수업들을 볼 대 배워보고 싶은 것들이 너무 많았다. AR/VR, IoT 등 모바일이나 웹을 벗어나서 여러 가지 프로젝트를 해보고 싶었다. 하지만 그 당시 확실히 내가 제대로 깨달았던 건 인턴쉽을 위해서는 최대한 모바일과 웹 관련 프로젝트가 있는 것이 중요했다. 대부분의 회사들은 UX Designer나 Product Designer 포지션을 구할 때 모바일이나 웹 관련 프로젝트를 제일 눈여겨보는데 포트폴리오가 대부분 AR/VR에 관련돼 있는 것들이라면 생각보다 지원할 수 있는 회사나 포지션 폭이 확 좁아진다. 몇몇 친구들은 이런 것들을 생각보다 생각해서 고르지 않아서 내 눈엔 포트폴리오가 약간 중간에 붕 뜬 느낌이 들었다. 게다가 그런 와중에 이력서에 스케치나 Framer를 할 줄 안다고 쓰는 건 회사 측에서는 물음표를 던질 수밖에 없는 계기를 줄 수밖에 없다. 그래서 나는 배워보고 싶은 것들이 많았지만 1학기만큼은 대부분의 프로젝트들을 모바일과 웹에 집중했다. 그리고 솔직히 제일 기본이 되고 사람들이 제일 많이 익숙한 그쪽에 많은 경험과 배경지식이 없었기 때문에 확실히 짚고 넘어가고 싶었다. 무엇보다 내가 갈고닦은 디자인과 프로토타이핑 실력 또한 그쪽에 집중되어있었기 때문에 더욱더 수업을 조심해서 골라 들었다. 수업이 말이 나와서 말인데 조지아텍에서는 프로젝트 관련 수업도 있지만 이론만 배우는 수업도 있다. 하지만 포트폴리오에 결과물이 있으려면 프로젝트 중심의 수업이 좋기 때문에 그런 것들도 고려해서 수강하였다.실시간 포트폴리오 업데이트내가 학교가 시작하자마자 시간이 날 때마다 신경 쓴 것 중에 하나는 바로 포트폴리오를 다시 만드는 것이었다. 대학원 지원용 포트폴리오는 디자인이 나쁘진 않았지만 새롭게, 훨씬 더 이쁘게 그리고 더 나은 경험으로 무장한 웹사이트를 만들어보고 싶었다. 아마 학교 시작 후 한 달 동안까지도 학교 공부만큼 포트폴리오 웹사이트를 코딩하는데 시간을 많이 투자했던 것 같다. 목표를 세웠을 때는 10월 중순 정도쯤까지 완성시켜서 서서히 끝나가는 학교 프로젝트들을 쉽게 집어넣을 수 있게 윤곽을 잡으려고 했지만 나도 모르게 집중해서 하다 보니 밤을 새우는 경우도 잦아졌고 조금조금씩 완성이 돼가는 나의 웹사이트를 보면서 하루라도 빨리 세상에 빛을 보게 해주고 싶었다. 수업 중간에 시간이 비면 혼자 조용한 곳을 찾아 음악을 들으면서 코딩을 했던 기억이 난다. 서서히 고치며 때로는 몇 시간째 했던 것을 확 엎기도 해서 여기까지 왔지만 아직도 개선하고 추가, 또는 빼고 싶은 것들이 몇 개 있다.석사 1학년 1학기 때는 정말로 많이 바빴다. 나에게는 삶의 여유를 즐길만한 시간이 없었고 분명 다가올 인턴쉽 지원과 인터뷰 등이 곧 다가올 것을 직감했다. 10월 중순 때만 해도 몇몇 회사들을 서서히 인턴쉽 채용공고를 내기 시작했고 그때마다 나의 프로젝트들이 성공적으로 한걸음, 한걸음 다가서길 바랬다. 그리고 한 단계씩 프로젝트들이 진화할 때마다 사진 찍고, 기록하고, 배운 점들, 개선해야 할 점들을 정리해서 포트폴리오에 집어넣기 시작했다. 친구들 중에는 포트폴리오를 만들지 않은 경우도 있었고 딱히 인턴쉽에 대해 많은 대비를 하지 않거나 정보도 많이 모르는 친구들도 있었다. 게다가 도대체 왜 이렇게 빨리 포트폴리오를 신경 쓰고 업데이트를 매주 하는지 이해를 못해하는 친구들도 있었다. 하지만 내 머릿속엔 지원시기는 11월부터였으며 학교 프로젝트는 대부분 학기말인 12월 중순에 끝나는 것을 감안하면 모든 프로젝트의 프로세스를 도큐멘팅하고 편집하는 것은 한꺼번에 하면 정말 하기 싫어질 만큼 오래 걸릴 것을 누구보다 잘 알고 있었다. 게다가 12월 말 겨울 방학에 포트폴리오를 업데이트하고 서서히 지원을 하기에는 놓치는 회사들도 몇몇 있었고 빨리 지원할수록 인터뷰를 따내는데 어느 정도 이점도 있다고 생각했기 때문에 난 내 갈 길을 걸었다.교내 프로젝트 외 다른 도전사실상 1학년 1학기 때 3개 이상의 제대로 된 프로젝트를 하는 것은 매우 힘든 일이다. 특히 내가 다니는 조지아텍에서는 1학년 1학기 때 친구들과 같이 들어야 하는 필수 과목들이 있어서 한정이 되는 경우도 있었다. 하지만 분명 적어도 한 개 정도는 더 들을 수 있는 여유가 있었기에 이론 강의보다는 프로젝트를 통해 배우는 강의를 택했다. 그렇지만 결국 포트폴리오에 집어넣을 수 있는 학교 프로젝트는 많아야 3개였다. 포트폴리오 웹사이트에 프로젝트가 3개밖에 없다고 해서 나쁜 것이 결코 아니지만 뭔가 더 어필할 수 있는 것들이 있을 거라고 믿었다. 그리고 그런 기회는 예상보다 빨리 찾아왔다. 바로 그것은 교내에서 있는 해커톤, 디자이너톤 그리고 각종 대회였다.꼭 우승을 차지해야겠다는 생각은 많이 하지 않았고 오히려 참가하는데 의미를 두고 며칠 동안의 프로젝트지만 그래도 열심히 해서 포트폴리오에 넣어볼 수 있도록 할 정도의 퀄리티까지 뽑아보고 싶었다. 그리고 만약 상을 받는다면 분명 이력서나 포트폴리오 한편에 자랑도 할 수 있으리라 기대감도 없지 않아 있었다. 몇 날 며칠 동안 밤새고 친구들과 디자인도 해보고 프로토타입도 만들어보고 여러 가지를 해보았는데 생각보다 재미있었다. 게다가 운이 좋게도 몇 번 상을 수상해서 포트폴리오에 넣을 수도 있었다. 추가로 학교에서 했던 프로젝트 중 2개는 실제로 교내 대회에서 2번 2등을 하는 영광을 얻기도 했다. 이런 것들 모두 포트폴리오 웹사이트에 사진과 함께 실었으며 모든 사람에게 나의 열정과 동시에 실력을 보여 줄 수 있는 무기가 되지 않았나 싶다.  2. 조교조교의 혜택과 이점내가 알기론 적어도 HCI 대학원들 중에서는 조교를 못하는 대학원도 있고 조교를 하더라도 금전적 혜택이 그만큼 크지 않은 대학원도 있다. 내가 다니는 조지아텍의 장점은 대학원 학생들이 GRA (Graduate Research Assistant)나 GTA (Graduate Teaching Assistant)로 연구실이나 수업에서 조교를 할 수 있는 기회가 그래도 다른 대학원보다는 많다는 것이다. 우리 학교에서는 조교가 되면 그 학기의 학비는 면제가 되고 GRA는 연구실이나, GTA는 가르치는 수업마다 시급이 다르지만 최소 매달 1,000불에서 2,000불까지 받을 수 있다. 따지고 보면 한 학기에 학비가 대충 2,000만 원을 훌쩍 넘기는 것을 생각하면 조교가 되면 거의 2,500만 원 정도는 오히려 벌게 되는 셈이다. 이런 금전적 혜택이 있기 때문에 많은 친구들이 하고 싶어 하지만 모두 다 조교를 할 수 있는 게 결코 아니다. 내 경험으로는 컴퓨터 공학 배경이 있는 학생들이 GRA를 받기가 쉬운데 코딩을 할 줄 알아서다. 그렇지만 분명 연구실마다 디자이너가 필요한 경우가 있을 수가 있고 교수님이 하시는 연구에 보조나 조금이나마 웹 개발 또는 프로토타이핑 등 도움을 줄 수 있는 프로젝트가 있을 수 있기 때문에 열심히 발 벗고 찾아봐야 한다.사실 조교가 되면 이로운 건 금전적인 것뿐만이 아니다. 많은 LinkedIn 프로파일에서 보면 알듯이 결국 이런 것도 이력서에 쓸 수가 있다. 결국에는 한 학기 또는 더 길 게의 나만의 경력이고 특히 GTA보다는 GRA가 여러 프로젝트를 할 수 있기 때문에 추가적으로 이익인 부분도 있다. 게다가 결국엔 회사 면접을 볼 때 할 말이 남들보다 한 가지는 더 있는 것일뿐더러 나의 능력이 학교에서 이미 검증되었다는 것을 증명하는 것이기도 하다. 실제로 내가 페이스북 인터뷰를 볼 때 면접관이 내가 GRA였을 때의 경험담에 대해 잠시 얘기를 해달라는 부탁을 하기도 했다.지원할만한 연구실 목록 작성다시 말하지만 다른 학교는 어떤지 잘 모른다. 하지만 조지아텍 같은 경우에는 HCI 웹사이트에서 연구실들에 관한 정보를 쉽게 찾을 수 있다. 나는 학교에 들어가기 전, 한국에 있었을 때부터 교내에 있는 연구실을 전부다 찾아보고 지원할만한 프로젝트가 있는 연구실을 대충 정리했다. 몇몇 교수님이나 연구실에 있는 박사 학생에게 여름 방학 기간에 이메일을 보내봤지만 솔직히 운이 정말 좋지 않은 이상 힘들다. 대부분의 답변은 일단 학교에 오면 다시 얘기하자는 얘기뿐이었다. 실제로 내가 알기로도 연구실마다 뽑을 수 있는 인력과 비용 등을 정하는 것이 생각보다 까다로운데 그런 것들이 100% 갖춰져 있지 않은 기간이다 보니 아무것도 모르는 사람한테 선뜻 자리를 내어주는 것은 말도 안 되는 것 같다. 하지만 시간 날 때 참여할 만한 연구실을 미리 찾아 놓는 것은 학교에 입학했을 때 분명 도움이 많이 된다.  연구실 지원 준비사실 나는 운이 좋게도 1학기 때부터 조교를 할 수 있었다. 첫 학기에는 학비 면제까지는 아니었지만 그래도 시급이 매우 쌔서 돈은 생각보다 많이 벌 수 있었다. 게다가 매우 행복했던 점은 이른 시기에 이력서에 한 줄이라도 더 세길 수 있는 것, 그리고 뭔가 첫 단추를 잘 끼운듯한 느낌이었다. 처음 지원할 때 무작정 지원서를 모든 연구실에 뿌린 것이 아니라 그전에 정확히 내가 관심 있고 나를 관심 여겨줄 만한 연구실을 찾아놨기 때문에 2,3개의 이메일만 보내면 됐다. 이메일 속에는 나의 이력서와 대학원 때 지원했던 포트폴리오 웹사이트, 내가 가지고 있는 Skill Set 그리고 내가 어느 프로젝트에 도움이 될 수 있을 만한지를 어필하는 메시지도 같이 써서 보냈다.내가 있는 연구실 이메일이 내 학교 이메일과 연동이 되어있어서 가끔가다가 연구실에 관심을 보이는 학부나 다른 대학원생들이 이메일을 이력서 첨부해서 보내는 경우가 있는데 정말 성의 없는 경우도 있고 누가 봐도 복붙을 했다시피 보낸 이메일도 간혹 눈에 뜨인다. 하지만 그중에서도 정말 성의 있게 목표가 명확한 이메일도 오는데 확실한 건 우리 연구실 매니저가 답장을 그런 사람에게는 솔직하게 잘 해주고 면접을 보러 오라는 경우도 자주 있다. 역시 사람과 사람의 관계에서는 그런 것들도 신경 써서 하는 게 좋은 것 같음을 또 한 번 느끼는 계기가 됐다.열심히 일하기나 같은 경우에는 1학년 2학기 때 GRA가 돼서 더 많은 책임감과 프로젝트를 해보고 싶었기 때문에 그 누구보다 열심히 주어진 역할에 임했다. 분명 학비 면제라는 매우 좋은 옵션도 있기 때문에 연구실에서 생각보다 많은 시간을 보냈다. 나 자신이 어느 회사의 한 명의 파트타임 직원이라고 생각하고 정직원으로 되기 위해 열심히 일하고 어필해야 한다고 생각했다. 정말 운이 좋았던 것은 뽑은 사람들 중에서 디자이너가 한 명도 없어서 대부분 디자인 관련 일들은 내가 도맡아 했다. 우리 연구실에서 주최하는 교내 대회 포스터 디자인이라던지 앱이나 웹사이트 관련 디자인들도 내가 맡아했으며 연구실 홈페이지 업데이트에 대해서도 많은 부분은 내가 관리하기 시작했다. 그래서인지 영향력이 조금씩 커지기 시작했고 정말 감사하게도 나에게 2학기 때 GRA를 할 수 있는 기회를 주었고 인턴쉽이 끝나고 돌아가면 2학년 1학기와 2학기 때도 같은 연구실에서 GRA를 할 수 있는 기회를 주었다.3. 인턴쉽 지원 준비회사들에 대한 지식 및 공부기본적으로 회사에 지원을 하려면 지원하고자 하는 회사가 무엇을 하는 회사이며 내가 지원하는 롤이 어떤 것임을 분명히 알아야 한다. 이름은 들어봤지만 정확히 무엇을 하는 회사가 어떤 Vision이 있으며 디자이너가 회사 내에서 하는 역할 같은 것들에 대한 정보는 많이 없어서 따로 지원하기 전에 시간을 투자해서 알아봐야 했다. 실제로 그 회사들을 다니는 디자이너들의 포트폴리오도 들여다보고 LinkedIn 프로파일에 나와있는 정보도 눈여겨봤다. 하지만 몇몇 회사들은 내가 정말로 일을 하고 싶은 회사였기에 이 모든 과정이 너무나도 가슴 뛰는 일이었다. 심심할 때 Glassdoor나 Quora라는 사이트에 가서 회사와 디자이너 타이틀을 치면 생각보다 많은 Review나 댓글들을 볼 수 있다. 분명 거기에 나와있는 정보가 최신이 아닐 수도 있고 정확하지 않을 수도 있지만 그래도 아예 모르는 것보다는 좋은 정보들이 있었다. 또 하나의 정말 좋은 방법은 인맥을 만들어서 직접 물어보는 것이다.Referral미국에서는 인터뷰를 따내기 위한 제일 쉽고 빠른 방법은 Referral을 받는 것이다. 수천 개 또는 수십만 개의 지원서를 리쿠르터들이 일일이 하나하나씩 읽어볼 순 없으니 어느 정도 직원 추천에 의존하는 것 같다. 나 같은 경우에는 운이 좋게도 내가 가고 싶은 회사에는 적어도 한 명씩 친구나 선배가 있어서 Referral을 받는 게 어렵진 않았다. 하지만 한두 개의 가고 싶었던 회사들에는 아는 사람이 없었기에 1학년 1학기 때 같은 학교 졸업생이나 페이스북 그룹, 또는 LinkedIn Messaging을 통해서 공통분모가 될만한 사람과 연락해서 친해지도록 노력했다. 이처럼 만약에 아는 사람이 없다면 꾸준한 온라인/오프라인 네트워킹을 통해 인맥은 미리 만드는 것이 좋은 것 같다. 실제로 몇몇 디자인 관련 페이스북 그룹에서 자신의 Dribbble, Behance, Codepen, 블로그 등을 소개하는 사람들이 있는데 그런 식으로도 자신의 영역을 넓혀 가는 것도 하나의 방법인 것 같다.이력서 디자인지금까지 포트폴리오에 대한 이야기는 많이 했지만 이력서 얘기는 자세히 하지 않은 것 같다. 개인적인 생각으로는 제일 중요한 건 역시 포트폴리오지만 이력서도 분명 엄청 중요하다. 대게 포트폴리오에는 이력서를 다운로드할 수 있게 하거나 직접 써넣는 경우가 있는데 어쨌든 회사에 지원하려면 이력서를 PDF 파일로 내야 한다. 이력서에 대부분 넣는 내용은 자신에 대한 간단한 소개, 학교, 일 경험, 쓸 줄 아는 툴들 외 수상경력 등이다. 디자인 같은 경우에는 학점은 그렇게 많이 중요하지 않다. 많은 회사에서는 심지어 학점을 물어보지도 않는다. 그만큼 포트폴리오를 많이 본다는 것인데, 포트폴리오를 들여다보기 전에 이력서를 먼저 보는 것으로 알고 있다. 이력서에 나와있는 학교, 프로젝트 또는 일했던 회사들이 흥미로우면 포트폴리오로 넘어가는 형식이라서 이력서가 어쨌든 나의 첫인상이 된다. 그래서 많은 디자이너들의 이력서를 보면 이 모든 것을 다 넣는 동시에 자신의 Creative 한 디자인을 뽐내는 경우가 많다. 내 생각에는 시간을 투자해서 이력서를 차별화시키는 것은 정말 좋다고 생각한다. 하지만 당연히 읽기 쉬워야 하며 흑백으로 프린트해서도 깔끔하게 나와야 하는 등 신경 쓰는 게 필요하다.포트폴리오 외 어필할 방법결국 회사에 지원할 때는, 이력서 첨부파일, 포트폴리오 주소, 자기소개 및 지원사유 등이 있는데 많은 회사들은 Additional Link라고 해서 다른 디자인 관련 웹사이트들이 있으면 첨부로 넣으라고 한다. 뭐든 추가로 넣는 것은 나쁠 것이 없기 때문에 나 같은 경우에는 내 Dribbble, Medium, LinkedIn주소를 넣었다. 이 외에도 Behance, Codepen, Github 같은 곳을 추가로 넣을 수가 있으며 이력서나 포트폴리오에 넣지 못한 작업물이 나와있는 웹사이트를 올려도 좋은 어필이 될 수 있다.4. 경험담을 글로 풀어내기개인 블로그 시작맨 처음에 말했듯이 내가 처음 디자인을 접했을 때 Medium이나 다른 웹사이트들에 나와있는 각종 아티클과 블로그들이 나에게 정말 많은 도움을 주었다. 회사들에서 퍼블리쉬되는 글들도 많았지만 그중에는 현업에서 종사하는 디자이너들이 쓴 글들도 상당히 많았다. 크게 봤을 때는 글의 종류는 반반이었던 것 같다. 하나는 전문적인 지식을 얘기하는 글들이고 또 하나는 자신의 경험담을 주로 쓰는 글들이었다. 난 내가 대학원에 처음 들어와서 공부를 하기 시작했을 때 나의 이야기를 글로 풀어보면 어떨까에 대한 생각을 많이 했었다. 처음에는 HCI 석사에 대한 경험담을 집중적으로 쓰고 싶었는데 그 이유 중 하나는 디자인 관련 글들은 많았지만 이 주제에 관한 글들은 많이 찾아보기 힘들었을뿐더러 지금도 그렇지만 그 당시에도 정말 많은 사람들이 HCI 석사를 지원 희망하는 것을 몸으로 느낄 수 있었기 때문에 어느 정도의 사람들이 읽어주리라 생각했기 때문이다.그래서 한 자 한 자 써 내려가고 대학원 관련 글 말고도 페이스북에서 인터뷰한 글들도 Dribbble에 관한 글들도 나의 Medium에 쓰게 되었다. 조금씩 사람들이 공감을 많이 해주고 팔로워도 생각보다 많이 늘어나다 보니 더 열심히 좋은 글들을 써야겠다고 생각했다. 그중, 심리학 배경으로 UX 하기와 포트폴리오에 대한 글을 썼는데 생각보다 반응이 엄청 뜨거워서 Muzli와 Sidebar에 소개되는 등 폭발적인 조회수를 기록하게 되었다. 이때부터는 정말로 글 쓰는 것에 대해서 큰 재미를 느끼게 되었고 글 쓰는 것에 대해서도 많이 신중해지기 시작했다. 모르는 것들은 찾아도 보고 배우게 되었고 무조건 발행을 하는 것이 아니라 어떤 것에 대해 쓸 것인지에 대해 플랜을 짜고 Structure를 짜고 계속 고쳤다. 스토리텔링에 신경을 많이 썼다. 그리고 그것은 정말로 큰 도움이 됐다.디자이너에게 블로깅이란?개인적인 생각이지만 디자이너에게 글을 잘 쓰는 능력은 가지면 매우 좋은 어드밴티지인 것 같다. 가끔 온라인에 나와있는 유명 디자이너들의 블로그를 읽을 때면 집중해서 확 읽어버리는데 내용도 내용이지만 짜임새가 매우 매끄러워서 읽다가 넘기는 적이 별로 없었다. 하나 더 최근에 계속 느낀 건 프로젝트를 포트폴리오 웹사이트에 도큐멘팅하거나 회사에서 프로젝트에 대해서 모든 것을 기록할 때 계속 글을 쓴다는 것이었다. 디자이너의 업무 중 많은 것들은 실제로 스케치로 디자인하는 것도 있고 수많은 미팅들도 있지만 어딘가에 글을 쓰는 것은 정말로 많다.  나는 모든 프로젝트를 진행할 때 Problem Statement에 대해서 정확히 짚고 넘어간다. 때문에 어떤 것이 문제인지, 왜 이게 문제인지, 누구를 위해서 내가 이 문제를 푸는지, 마지막 결과는 어떤지 등에 관한 글을 쓴다. 그리고 다른 팀원들과 소통할 때도 Structure이 잘 돼있는 글을 보여준다.5. 회사 지원 및 인터뷰Google Analytics를 통한 방문자 분석회사에 지원서를 넣고 나면 적어도 몇 주 동안은 연락이 없다. 진짜 빠른 경우에는 일주일 만에도 답장이 오는 경우가 있지만 인턴쉽 같은 경우에는 시간이 좀 더 걸리는 것 같다. 내 포트폴리오 웹사이트에는 방문을 하긴 하는지가 제일 궁금한데 제일 쉽게 알아낼 수 있는 방법은 웹사이트에 Google Analytics Tracking Code를 삽입하는 것이다. 하기가 정말 쉬워서 온라인에서 하는 방법을 찾으면 아마 쉽게 알아낼 수 있을 거다. Tracking Code를 넣고 Google Analytics툴에 들어가면 어느 지역에서 방문했는지, 네트워크는 무엇인지 (회사 이름인 경우가 많다) 그리고 어느 페이지에 얼마큼 머물렀는지 까지 분석해볼 수가 있다. 게다가 실시간 정보도 다 보이기 때문에 지금 나의 웹사이트에 들어온 사람들을 볼 수 가있다.나 같은 경우에는 내가 지원한 어느 특정 회사에서 들어오면 매우 기뻐했다. 그리고 실제로 인터뷰를 보기 한두 시간 전에 면접관들이 들어와 보는 것을 경험으로 깨달아서 나중에는 전화나 화상통화 면접을 보기 전에 그 사람들이 어떤 프로젝트를 눈여겨보는지 실시간으로 봤다. 그래서 짧은 시간이지만 면접관이 더 오래 머무른 프로젝트에 대해서 마음속으로 연습 또 연습을 하면서 전략적으로 준비했다.Recruiter와의 대화대부분 처음에 인터뷰를 보자고 회사에서 연락이 오면 찬스는 정말 높아진 것이다. 대부분의 서류들이 사실 필터가 되는 경우가 많고 인터뷰를 보자고 이메일 오는 경우는 지원자 수에 비해서 턱없이 적기 때문이다. 회사마다 진행되는 단계가 다르지만 인턴쉽 같은 경우에는 리쿠르터와 먼저 대화를 한다. 대충 리쿠르터가 적절한 시간에 전화를 해서 지원자의 관심사와 스킬을 인턴이 필요한 팀과 매칭을 하기도 하는 이유이기도 하지만 크게는 지원자에 대해서 더 알아가는 동시에 프로세스를 설명해 주는 시간이다. 어떤 경우에는 실제로 리쿠르터와 전화하고 나서 인터뷰를 더 이상 진행 못하는 경우도 있는 거로 봐서는 절대로 가볍게 해서는 안 되는 단계임은 분명하다. 페이스북 같은 경우에는 디자인 리쿠르터가 따로 있어서 디자인 관련된 전문적인 것도 물어봤었다. 하지만 인터뷰를 본 다른 회사들은 그런 것들보단 나에 대해서 General 하게 알고 싶어 했다.Interviewer 파악하기리쿠르터와 Screening 단계를 하고는 대부분 디자인 관련 직업의 면접관과 전화나 화상통화를 한다. 어떤 회사들은 직접 회사로 불러서 On-site 인터뷰를 진행하는 경우도 있지만 인턴쉽은 대부분 전화로 끝난다. 리쿠르터와 시작부터 끝까지 이메일로 연락을 주고받는데 전화연결방법, 면접관에 대한 정보 등을 전달해준다. 면접관에 대한 정보는 흔하게는 이름과 직책만 주는 경우가 대부분인데 LinkedIn이나 Facebook 같은 곳을 찾아보면 쉽게 찾을 수 있다. 특히 LinkedIn으로는 그 면접관이 어디서 일했었고 어느 프로젝트를 했으며 그 사람에 대한 전반적인 느낌을 알 수가 있다. 면접관과 처음 몇 마디를 나눌 때는 공감대가 있는 것이 분위기를 업시켜주고 시작을 산뜻하게 출발하게 도움을 주는데 여기서 몇 가지 자신과 공통된 점을 설명하 거나하면 그래도 라포르를 형성하는데 어느 정도 기여를 한다. 하지만 너무 Creepy 하게 스토킹을 한 것처럼 느껴지면 이상하기 때문에 조심해야 한다.실전실제로 면접관과 전화를 할 때 회사마다 다르지만 꼭 하는 것은 포트폴리오 리뷰이다. 내가 지금 까지 한 프로젝트 중 제일 잘 했다고 생각한 프로젝트나 면접관이 관심 있는 프로젝트를 2개에서 많게는 3개까지 설명하게 된다. 대부분 프로젝트가 어떤 것인지에 대한 설명부터 시작하게 되고 프로젝트 중 나의 역할, 힘들었던 점, 배운 점 등을 설명하는데 중간중간에 면접관이 궁금한 점들도 물어본다. 이것에 대해 Medium에 글을 쓴 것이 있는데 관심이 있으면 여기에서 찾아보길 바란다.마무리하며...많은 디자이너 분들이 재미있는 블로그들을 써주시는데 순수 디자인 백그라운드가 아닌 상태에서 지금까지 오기까지의 이야기는 흔하지 않은 것 같아서 그동안 내가 느낀 점을 써보자고 다짐했다. 쭉 써가다 보니 어느샌가 많이 길어지게 되었고 "설마 시람들이 이걸 다 읽을까..."라는 고민도 했었다. 나의 소중한 시간을 투자하고 그동안의 기억을 되돌려서 한 자 한 자 신중히 써 내려간 만큼 많은 학생분들 또는 현업에 종사하시는 디자이너 분들에게 조금이라도 도움이 되었으면 한다.항상 하는 말이지만, 아직도 갈길은 멀다. 실력에 비해 너무나도 과분한 기회가 주어졌고 그 기대에 조금이라도 더 부합하기 위해 밤낮으로 열심히 노력했다. 세상에는 정말로 뛰어난 디자이너분들이 계시며 언젠간 그들과 어깨를 나란히 하고 더 창의적이고 밝은 디자이너가 되고자 나는 앞으로도 노력할 거다. 읽어주셔서 너무나도 감사하고 앞으로도 좋은 글로 찾아뵙고 싶다.감사합니다.#페이스북 #Facebook #인턴 #인턴후기 #인턴생활 #기업문화 #디자인 #디자인팀 #디자이너
조회수 2969

타다 클라이언트 개발기

앞서 종합 모빌리티 플랫폼인 타다의 시스템 설계를 위한 많은 고민과 기술적 결정들에 대해서 서버팀에서 소개한 바 있습니다. 이번 글에서는 타다 서비스를 출시하기까지 타다 모바일 클라이언트를 개발하는 과정에서 내린 클라이언트 팀의 전략적 결정들과, 타다 클라이언트를 개발하는데 사용한 기술들을 공유합니다.시작 전 상황3달 반의 개발 기간: 타다는 VCNC가 SOCAR에 인수되면서 개발하게 된 서비스입니다. 빠르게 시장에 뛰어들어서 선점하는 것이 무엇보다 중요했기에 시간과의 싸움은 필수적이었습니다. 프로젝트는 6월에 시작되었고 1.0 출시는 추석 연휴 직전인 9월 중순으로 결정되었습니다. VCNC에서 오프라인 운영은 처음이었기 때문에 차량을 실제로 운행해보면서 사용성 경험을 테스트할 필요가 있었습니다. 그래서 8월 초에 사내 테스트용 알파 버전을 출시하기로 했습니다.클라이언트 팀 통합: 비트윈 때는 Android/iOS 팀이 나뉘어 있었습니다. 회사 인수 과정에서 발생한 조직 개편으로 인해 타다 클라이언트 개발자는 5명으로 이루어졌습니다. 전부터 다른 OS 개발도 경험하고 싶던 적극적이고 열정적인 5명의 멤버들은 과감하게 팀을 통합해서 Android/iOS을 함께 개발하기로 했습니다.3개의 앱 개발: 타다의 서비스를 위해서는 Android/iOS, 라이더/드라이버 총 4개의 앱을 제작해야 합니다. 하지만 시간과 일정을 고려했을 때 4개의 앱을 다 제작하기는 무리라고 판단을 했습니다. iOS에서는 내비게이션 앱을 사용 중에 드라이버 앱으로 손쉽게 전환하는 기능을 제공할 수 없고 내비게이션 앱으로 경로 안내를 요청하는 것도 제한적이기 때문에 iOS 드라이버 앱은 제작하지 않기로 했습니다.무에서 시작한 프로젝트: 타다는 코드 베이스가 없는 empty repository에서 시작했습니다. 언어도 바뀌었고 레거시 코드와도 엮이고 싶지 않았기 때문에 비트윈에서 어떠한 라이브러리도 가져오지 않고 전부 새로 만들기로 했습니다.클라이언트 팀의 5명의 정예 용사들. by Sam코드 아키텍처 - RIBs프로젝트가 시작되고 기획이 진행되는 동안 3주의 시간을 기반 작업에 쓰기로 했습니다. 가장 먼저 진행한 것은 코드 아키텍처 정하기입니다. 당시에 제가 SAA(Single-Activity Application)에 관심을 가지고 있었는데, 때마침 Google I/O 2018의 세션 중 Modern Android development: Android Jetpack, Kotlin, and more 에서도 비슷한 언급이 나와서 팀에 제안했고, 본격적으로 조사를 해보았습니다. 팀원들이 조사를 진행해보니 Uber, Lyft, Grab 등 굴지의 모빌리티 서비스 회사들이 전부 SAA 기반으로 앱을 개발했다는 것을 알게 되었습니다. 무거운 리소스인 지도를 중심으로 화면이 구성되기에 반복적인 지도 리소스 할당/해제를 피하기 위한 필연적인 선택으로 보입니다. 큰 기업들이 수년간 서비스를 하며 문제를 느끼고 내린 선택인 만큼 저희도 따라가기로 결정했습니다. 비트윈 때 Activity Stack으로 인해 굉장히 고통을 겪은 적이 있는지라 SAA를 원하는 공감대도 있었고요.SAA로 개발을 하기로 정한 이후에는 어떤 프레임워크를 사용해서 개발할지를 고민했습니다. 여러 개의 오픈소스를 비교할 때 Android/iOS 간의 통일된 아키텍처로 개발할 수 있는지를 가장 중점적으로 보았습니다. 대부분의 팀원이 한쪽 OS에만 익숙하기 때문에 초보임에도 빠르게 적응하고 개발하려면 비즈니스 로직을 구현하는 부분이 통일되어 있어야 한다고 생각했습니다. Uber의 RIBs는 저희의 이런 요구를 가장 잘 충족했습니다. 거기에 데이터의 scope와 전달 방식 명확해서 side-effect 없이 개발할 수 있다는 점, 그로 인해 효율적으로 협업이 가능하고 여러명이 개발한 RIB 을 레고 조립하듯 합쳐서 기능을 완성할 수 있다는 점에서 RIBs를 선택하게 되었습니다.RIBs는 아키텍처를 이해하는 것 자체가 굉장히 난해합니다. 오픈소스 상으로 공개가 되지 않은 부분들도 있어서 저희의 입맛에 맞게 변형하는 데 매우 많은 시간을 할애했습니다. RIBs와 관련한 내용은 Nate(김남현)가 Let'Swift 2018에서 발표한 RxRIBs, Multiplatform architecture with Rx 의 영상 및 발표자료를 참조하세요.추후 RIBs를 상세하게 다루는 포스팅을 해보도록 하겠습니다.서버와의 통신 프로토콜새로운 서버 API가 생길 때마다 해당 API의 명세를 문서화하고 전달하는 것은 굉장히 불편한 일입니다. 또한 문서를 작성할 때나 클라이언트에서 모델 클래스를 생성할 때 오타가 발생할 수도 있습니다. 타다에서는 서버 클라이언트 간 API 규약을 Protocol Buffer를 사용해서 단일화된 방법으로 정의하고 자동화하기로 했습니다. 모든 API의 url은 .proto 파일 이름으로 정형화되어 있고 POST body로 Params 객체를 JSON으로 serialization 해서 보내면 Result JSON이 응답으로 옵니다. 서버가 새로운 API를 개발할 때 .proto 파일만 push 하면 클라이언트에서 스크립트를 돌려서 Model 객체를 생성하고 해당 객체를 사용해서 호출만 하면 되는 아주 간단하고 편한 방식입니다.참고로 타다의 서버군에 대한 설명은 타다 시스템 아키텍처에 기술되어 있습니다.기반 작업타다는 빈 repository에서 시작한 깔끔한 프로젝트였기 때문에 Base 코드와 내부 라이브러리들을 전부 새로 개발했습니다.API Controller, gRPC Controller서버와의 통신에 필요한 모듈들을 개발했습니다. 모든 API는 Rx의 Single과 Completable로 wrapping 되어 있습니다.RIBs가장 자주 사용하는 Router 패턴들을 wrapping.Android에서 구현이 공개되어 있지 않은 ScreenStack 구현.SAA이므로 Android에서 Activity가 아닌 화면 단위의 로깅을 구현.Router의 기초적인 화면 Transition을 구현RIB 뼈대 코드용 template 파일 제작Prefs(Android)/Store(iOS)타다에서는 DB를 사용하지 않고 key-value store로만 데이터를 저장합니다. Android SharedPreference와 iOS UserDefaults의 wrapper를 만들었습니다. Object를 serialization 해서 저장하는 기능, Rx 형태의 getter, cache layer, crypto layer 등이 구현되어 있습니다.Design SupportAndroid에서 drawable을 생성하지 않고 layout.xml 상에서 border, corner-radius, masking을 쉽게 설정하기 위해서 제작했습니다.ButterKtAndroid에서 View Binding 처리를 위해 개발했습니다. 비슷한 기능을 하는 Kotter Knife, Kotlin Android Extension이 가지고 있는 lazy binding 문제를 해결하고 싶었고 가능하면 Butter Knife와 달리 apt 없이 동작하는 라이브러리를 만들고 싶었습니다. 이와 관련된 저희의 생각은 여기에 David(김진형)이 상세하게 기록해 두었습니다. 코드도 공개되어 있으니 잘 활용해 보시길 바랍니다.ToolsModel CompilerPBAndK, swift-protobuf를 수정해 .proto 파일을 저희가 원하는 형태의 kotlin data class와 swift codable struct로 변환하는 스크립트를 구현했습니다.Import ResourceUI/UX 팀에서 작업해서 Google Drive File Stream으로 공유하는 리소스를 프로젝트에 sync 하는 스크립트입니다. 타다에서는 기본적으로 벡터 포맷(Android xml, iOS pdf)을 사용하고 Android에서 벡터로 표현이 안되는 이미지들은 png를 사용합니다. 또한 애니메이션을 위한 Lottie json 파일도 사용합니다. 현재는 Android 용으로만 스크립트가 구현되어 있고 리소스를 프로젝트 내의 각각의 res 폴더에 sync 하는 기능과 svg로 전달받은 벡터 파일을 Android xml 형식으로 변환하는 기능을 포함합니다.sync Lokalise타다에서는 Lokalise로 문자열 리소스를 관리합니다. strings.xml, Localizable.strings 파일로 다운받아서 프로젝트에 sync 하는 스크립트 입니다.Code Template & Settings개발 편의를 위한 간단한 Android Studio Code Template과 코드 통일성을 위한 idea settings를 공유합니다.사용된 기술들OS 공통Firebase: Analytics, Crashlytics, Messaging, Storage 등 다양한 용도로 Firebase를 활용하고 있습니다.gRPC, ProtoBuf: 서버에서 실시간 Event를 받기 위해서 사용합니다.RIBs: 타다의 기반 아키텍처 입니다.Lottie: 애니메이션 요소를 표현하기 위해 사용합니다.Semver: 앱의 버전은 Semantic Versioning 규약을 따라 정의합니다. 버전을 파싱하고 관리하기 위해서 Nate(김남현)가 Kotlin 버전과 Swift 버전의 라이브러리를 제작하고 공개했습니다.Braze: CRM(Customer Relationship Management) 툴인 Braze는 유저를 타게팅해서 전면팝업을 띄우거나 푸시 알림을 발송하기 위해 사용합니다.TeamCity, Fastlane, Beta: CI/CD를 위해서 개발 초기에는 Jenkins를 사용했습니다. 출시 대응을 빠르게 하기 위해서 parallel build 및 우선순위 컨트롤을 하고 싶었는데 Jenkins의 Parallel build가 원하는 대로 동작하지 않아서 현재는 TeamCity로 이전했습니다. Beta를 사용해서 모든 브랜치의 빌드를 배포해서 QA 팀에서 테스트할 수 있게 했습니다. 출시용 빌드는 Android의 경우 아직은 수동 업로드를 하고 있고 iOS의 경우 Fastlane으로 배포합니다.git-flow: Git branching model로는 git-flow를 사용합니다. Branch의 종류에 따라서 TeamCity에서의 빌드 우선순위가 결정됩니다.AndroidKotlin: 당연한 선택이겠죠? 타다의 모든 소스 코드는 Fork 해서 수정한 RIBs의 클래스들을 제외하면 전부 Kotlin으로 구현되어 있습니다.AndroidX: 타다 개발을 시작하는 순간에 AndroidX가 공개되었습니다. 기존 Support Library를 사용하게 되면 언젠가는 migration 해야 할 것이기 때문에 알파 버전임에도 불구하고 처음부터 사용하기로 했습니다. ConstraintLayout, PagingLibrary, Material Component, KTX 등 다양한 Component를 사용합니다.Retrofit, OkHttp: 서버와의 HTTP 통신을 위해서 사용합니다.RxJava: 클라이언트 팀은 Rx 없이는 개발할 수 없을 정도로 적극적으로 Rx를 활용합니다.AutoDispose: Rx subscription을 dispose 하기 위해서 사용합니다. 관련해서 도움이 될만한 글을 읽어보시는 것을 추천합니다. Why Not RxLifecycle?RxBinding: View 이벤트를 Observable 형태로 바꿔주는 RxBinding은 굉장히 유용합니다.Moshi: JSON 라이브러리입니다. Kotlin data class와의 호환을 위해서 Gson 대신 선택했습니다.Glide: 이미지 로딩을 위해서 사용합니다.Detekt: Kotlin을 위한 static code analyzer 입니다. Detekt의 extension을 통해 ktlint도 활용하고 있습니다.Dagger: RIBs는 Dependency injection을 기반으로 합니다. RIBs에선 어떠한 DI system이든 사용할 수 있게 Builder가 분리되어 있습니다. RIBs에서는 Dagger로 설명이 되어 있고 저희도 마찬가지로 Dagger를 사용합니다.ThreeTen Backport: Java8의 날짜 및 시간 라이브러리인 JSR-310의 Java SE6 & 7을 위한 backport 라이브러리입니다. 문자열 파싱 및 시간 연산을 위해 사용합니다.iOSSwift: Kotlin과 마찬가지로 당연한 선택입니다. Swift4.2의 CaseIterable Swift5의 Result 등 항상 최신 버전의 Swift를 사용합니다.RxSwift: 역시나 reactive programming은 필수입니다.RxCocoa, RxGesture: iOS에서도 역시 모든 뷰 이벤트는 Rx 형태로 감지합니다.SnapKit: AutoLayout DSL을 제공하므로 코드상에서 편하게 Constraint를 조절할 수 있습니다.Moya/RxSwift, Alamofire: Http 서버와의 통신을 위해 추상화된 네트워크 라이브러리인 Moya를 사용합니다. 역시나 Rx로 wrapping 된 버전을 사용하고 있습니다.Codable: Swift4부터 제공된 프로토콜로 JSON Encoding, Decoding으로 사용중입니다.Hero: RIBs의 Router가 attach/detach 될 때의 Transition을 처리하는데 이용합니다.Kingfisher: 이미지 로딩을 위해서 사용합니다.KeychainAccess: Access Token 같은 중요 정보를 안전하게 저장하기 위해 사용합니다.Swiftlint: SwiftLint는 fastlane action으로 실행해서 code validation을 합니다.출시 후의 회고짧은 시간에 여러 개의 앱을 만들기 위해서는 시간 및 인원을 아주 효율적으로 배분해야 했습니다. 각 OS의 기존 개발자들이 먼저 프로젝트 기반을 닦는 동안 나머지는 스터디를 진행했습니다. 차량 운영 경험을 쌓는 것이 알파 테스트의 목적이었으므로 일정에 맞추기 위해 드라이버 앱도 개발해야 하는 Android로만 알파 버전을 개발했습니다. 대신에 iOS 알파 버전은 서버팀 YB(김영범)가 아주 빠르게 웹앱으로 개발해주었습니다(1.0은 Native입니다.). 알파 버전의 스펙도 호출-하차까지의 시나리오 외의 다른 부가 기능은 전부 제외했습니다.회사 구성원들이 전부 처음 도전하는 분야였기에 기획을 포함해서 모두가 지속적인 변화에 대응해야 했습니다. 특히 사내 테스트를 시작한 직후 실제 운영을 해보며 깨닫고 변경한 기획 및 UX가 상당히 많았습니다. 개발적으로는 익숙하지 않은 아키텍처인 RIBs를 이해하며 개발하는 것이 생각 이상으로 난도가 높았고 개발하는 중간에도 큰 리팩터링을 여러 번 해야 해서 힘들었습니다. 이러한 이유들로 1.0 출시도 시작 전 상황에서 언급한 것보다 2주 정도 미뤄졌습니다.실제 타다 프로젝트 타임라인하지만 저희는 성공적으로 타다를 출시했습니다! 아래는 팀 내에서 출시를 회고하며 나왔던 몇몇 의견입니다.OS 간 아키텍처가 통일되어서 한 명이 같은 기능을 두 OS 전부 개발할 때 굉장히 효율적이다. 비즈니스 로직의 경우 정말로 Swift <-> Kotlin간 언어 번역을 하면 되는 정도.결과적으로 앱 개발 순서를 굉장히 잘 정했다. 한쪽을 먼저 빠르게 개발하고 문제점을 느껴보며 정비해 나가니까 프로젝트 후반부에 빠른 속도로 기능을 개발하는 데 도움이 되었다. 큰 수정을 양쪽 OS에 하지 않아도 됐던 게 좋았다.짧은 기간 개발했음에도 앱 퀄리티가 굉장히 만족스럽다. 매 상황에서 기술적 선택, 인원 배분 등 경험에서 우러나온 아주 적절한 판단들을 했다고 생각한다.각자 독립적으로 개발하던 기능들이 쉽게 합쳐지고 큰 문제없이 잘 동작하는 하나의 앱이 되는 과정이 정말 신기했다. 아키텍처 설계에 쓴 많은 시간이 결코 아깝지 않았다.마치며아직 저희가 하고 싶고 도전해야 하는 과제들은 무궁무진합니다. 그 중 간략히 몇 가지를 소개합니다.테스트 코드 작성: 시간과의 싸움 속에서 테스트 코드 작성을 지금까지 미뤄왔습니다. RIBs의 Interactor 에 구현된 비즈니스 로직은 반드시 테스트 되어야 합니다.OS 간 구조 통일: 같은 화면임에도 OS 간 작업자가 다른 경우 많은 파편화가 일어났습니다. 1순위로 RIB tree 및 Interactor의 비즈니스 로직 통일하는 작업을 진행하고 있습니다. AlertController 같은 공통적인 컴포넌트들도 최대한 포맷을 통일하려는 작업을 지속해서 진행할 예정입니다.iOS DI: RIBs에서 Android에선 Dagger를 활용해서 쉽게 Builder 구현이 가능하지만, iOS에서는 좋은 방법이 없어서 수동으로 DI를 해결하고 있었습니다. 그래서 Uber가 개발 중인 Needle을 적용하려고 관심 있게 보고 있습니다.네트워크 에러 handling 개선: 중첩돼서 뜨는 Alert를 해결하는 것, global 하게 에러를 처리하는 좋은 구조 찾기 등의 이슈가 있습니다.String Resource 관리: 개발하면서 생성하고 아직 Lokalise에 동기화하지 않은 리소스 키를 체크해서 빌드 오류를 발생시키려고 합니다. 또한 iOS에서 "some_key".localize 형태의 extension으로 번역을 코드상에서 불러오는데 key 값 오타가 나면 런타임에서만 오류를 알 수 있습니다. 따라서 String resource를 enum 형태로 관리하려고 합니다.그 외 50여 가지나 되는 팀원들이 하고 싶은 백로그 목록이 여러분을 기다리고 있습니다. 타다가 성공적으로 런칭할 수 있었던 것은 훌륭한 팀원들이 있었기 때문입니다. 앞으로 저희와 함께 좋은 서비스를 만들어 나갈 멋진 분들의 많은 관심 바랍니다.
조회수 1643

RxJava2 함수 파헤치기!

Overview지난 글 Rxjava를 이용한 안드로이드 개발에서는 RxJava의 Android 연결 방법과 기본적인 사용법을 다뤘습니다. 이번 글에서는 RxJava의 강력하고 다양한 함수들을 살펴보고자 합니다. Android에서 복잡하게 구현되는 내용들을 단 몇 개의 함수로 처리할 수 있는 RxJava를 꼭 사용해보길 권합니다.1. just2. fromArray/fromlterable3. range/rangLong4. interval5. timer6. map7. flatMap8. concatMap9. toList10. toMap11. toMultiMap12. filter13. distinct14. take15. skip16. throttleFirst17. throttleLast18. throttleWithTimeout참고: 공통적으로 사용하는 구독(수신) 클래스는 아래와 같습니다.static class CustomSubscriber<T> extends DisposableSubscriber<T> { @Override public void onNext(T t) { System.out.println(Thread.currentThread().getName() + " onNext( " + t + " )"); } @Override public void onError(Throwable t) { System.out.println(Thread.currentThread().getName() + " onError( " + t + ")"); } @Override public void onComplete() { System.out.println(Thread.currentThread().getName() + " onComplete()"); } } 1. just파라미터를 통해 받은 데이터로 Flowable을 생성하는 연산자입니다. 최대 10까지 전달할 수 있고, 모든 데이터가 수신되면 onComplete() 수신됩니다. 기본적인 Flowable 생성자 함수로 볼 수 있으며 단순 작업에서 많이 사용합니다.public static void just() { //파라미터 값을 순차적으로 송신하는 Flowable 생성 Flowable<String> flowable = Flowable.just("A", "B", "C", "D", "E", "F"); //구독을 시작한다. flowable.subscribe(new CustomSubscriber<>()); } 결과 main onNext( A ) main onNext( B ) main onNext( C ) main onNext( D ) main onNext( E ) main onNext( F ) main onComplete() 2. fromArray/fromIterablefromArray, fromIterable 함수는 파리미터로 배열 또는 Iterable(리스트 등)에 담긴 데이터를 순서대로 Flowable을 생성하는 연산자입니다. 모든 데이터를 순차적으로 송신 후 완료됩니다. 반복적인 데이터 변환 작업 같은 경우 for 문 대신 대체할 수 있습니다. 결과를 보면 main Thread 에서 작업 결과가 나오지만, flatMap 을 사용한다면 별도의 Thread로 main Thread의 부하를 막을 수 있습니다.1. fromArray public static void fromArray() { //fromArray 배열로 파라미터를 전달 받는다. Flowable<String> flowable = Flowable.fromArray("A", "B", "C", "D", "E"); //구독을 시작한다. flowable.subscribe(new CustomSubscriber<>()); } 결과 main onNext( A ) main onNext( B ) main onNext( C ) main onNext( D ) main onNext( E ) main onComplete() 2. fromIterable public static void fromIterable() { List<String> list = Arrays.asList("A", "B", "C", "D", "E"); //fromIterable 리스트로 파라미터를 전달받는다. Flowable<String> flowable = Flowable.fromIterable(list); //구독을 시작한다. flowable.subscribe(new CustomSubscriber<>()); } 결과 main onNext( A ) main onNext( B ) main onNext( C ) main onNext( D ) main onNext( E ) main onComplete() 파라미터와 함수는 다르지만 동일하게 처리된다. 3. range/rangLongrange 함수는 지정한 숫자부터 지정한 개수만큼 증가하는 Integer 값 데이터를 송신하는 Flowable를 생성합니다. rangLong 함수는 range와 동일하며 데이터 타입은 Long을 사용합니다. 두 함수 데이터 송신을 마치면 onComplete를 송신합니다.1. range public static void range() { //range(int start, int count) //start : 시작 값 //end : 발생하는 횟수 Flowable<Integer> flowable = Flowable.range(10, 5); //구독을 시작한다. flowable.subscribe(new CustomSubscriber<>()); } 결과 main onNext( 10 ) main onNext( 11 ) main onNext( 12 ) main onNext( 13 ) main onNext( 14 ) main onComplete() 2. rangLong public static void rangeLong() { //range(int start, int count) //start : 시작 값 //end : 발생하는 횟수 Flowable<Long> flowable = Flowable.rangeLong(10, 5); //구독을 시작한다. flowable.subscribe(new CustomSubscriber<>()); } 결과 main onNext( 10 ) main onNext( 11 ) main onNext( 12 ) main onNext( 13 ) main onNext( 14 ) main onComplete() 4. interval지정한 간격마다 0부터 시작해 Long 타입 숫자의 데이터를 송신하는 Flowable을 생성합니다. 데이터는 0, 1, 2, 4 순차적으로 증가된 데이터를 송신합니다. Android 에서는 반복적인 작업인 TimerTask를 대신해서 interval로 간단하게 처리할 수 있습니다. UI 변경이 필요한 부분에서는 interval scheduler를 AndroidSchedulers.mainThread() 를 변경해 적용할 수 있습니다.public static void interval() { //(long time, TimeUnit unit, Scheduler scheduler) //time : 발생 간격 시간 //unit : 간격 시간 단위 //scheduler : 발생 scheduler를 변경하여 사용할 수 있습니다. // ex)AndroidSchedulers.mainThread() // - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 // 1초 간격으로 데이터 요청을 송신하다. Flowable<Long> flowable = Flowable .interval(1000L, TimeUnit.MILLISECONDS).take(10); //구독을 시작한다. flowable.subscribe(new CustomSubscriber<>()); } 결과 RxComputationThreadPool-1 onNext( 0 ) RxComputationThreadPool-1 onNext( 1 ) RxComputationThreadPool-1 onNext( 2 ) RxComputationThreadPool-1 onNext( 3 ) RxComputationThreadPool-1 onNext( 4 ) RxComputationThreadPool-1 onNext( 5 ) RxComputationThreadPool-1 onNext( 6 ) RxComputationThreadPool-1 onNext( 7 ) RxComputationThreadPool-1 onNext( 8 ) RxComputationThreadPool-1 onNext( 9 ) 5. timertimer 함수는 호출된 시간부터 일정한 시간 동안 대기하고 Long 타입 0을 송신 및 종료하는 flowable을 생성합니다. interval이 조건까지 반복적으로 송신한다면, timer는 한번만 송신하고 종료됩니다.public static void timer() { SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy.MM.dd hh:mm ss"); System.out.println("현재시간 : " + simpleDateFormat.format(System.currentTimeMillis())); //(long time, TimeUnit unit, Scheduler scheduler) //time : 발생 간격 시간 //unit : 간격 시간 단위 //scheduler : 발생 scheduler를 변경하여 사용할 수 있습니다. // ex)AndroidSchedulers.mainThread() Flowable<Long> flowable = Flowable.timer(1000L, TimeUnit.MILLISECONDS); //구독을 시작한다. flowable.subscribe(value -> { System.out.println(" timer : " + simpleDateFormat.format(System.currentTimeMillis())); }, throwable -> { System.out.println(throwable); }, () -> { System.out.println(" complete"); }); } 결과 현재시간 : 2019.04.29 09:09 56 timer : 2019.04.29 09:09 57 complete 6. mapFlowable 에서 송신하는 데이터를 변환하고, 변환된 데이터를 송신하는 연산자입니다. 하나의 데이터만 송신할 수 있으며, 반드시 데이터를 송신해야 합니다. 혹여 송신되는 데이터가 null 을 포함하면 map 대신 아래의 flatMap 을사용하는 것이 좋습니다.public static void map() { Flowable<String> flowable = Flowable.just("A", "B", "C", "D", "E") //map(Function mapper) //mapper : 받은 데이터를 가공하는 함수형 인터페이스 //알파벳 값을 소문자로 변경하여 return 한다 .map(value -> value.toLowerCase()); //구독을 시작한다. flowable.subscribe(new CustomSubscriber<>()); } 결과 main onNext( a ) main onNext( b ) main onNext( c ) main onNext( d ) main onNext( e ) main onComplete() 7. flatMapflatMap은 map과 동일한 함수이지만, map과는 달리 여러 데이터가 담긴 Flowable을 반환할 수 있습니다. 또한 빈 Flowable를 리턴해 특정 데이터를 건너뛰거나 에러 Flowable를 송신할 수 있습니다.파라미터 mapper에서 새로운 Flowable의 데이터 전달이 아닌 다른 타임라인 Flowable로 작업하면 들어온 데이터 순서대로 출력을 지원하지 않습니다. 타임라인 Flowable(timer, delay, interval 등)에서는 가급적 사용을 피하거나, 순서에 지장이 없을 때 사용하는 것이 좋습니다.public static void flatMap() { Flowable<String> flowable = Flowable.range(10, 2) //flatMap(Function mapper, BiFunction combiner) //mapper : 받은 데이터로 새로운 Flowable를 생성하는 함수형 인터페이스 //combiner : mapper가 새로 생성한 Flowable 과 원본 데이터를 조합해 새로운 송신 데이트를 생성하는 함수형 인터페이스 //첫 번째 데이터를 받으면 새로운 Flowable를 생성한다. //take(3) : 3개까지만 발생한다. .flatMap(value -> Flowable.interval(100L, TimeUnit.MILLISECONDS).take(3), (value, newData) -> "value " + value + " newData " + newData); //구독을 시작한다. flowable.subscribe(new CustomSubscriber<>()); } 결과 RxComputationThreadPool-1 onNext( value 10 newData 0 ) RxComputationThreadPool-2 onNext( value 11 newData 0 ) RxComputationThreadPool-1 onNext( value 10 newData 1 ) RxComputationThreadPool-2 onNext( value 11 newData 1 ) RxComputationThreadPool-1 onNext( value 10 newData 2 ) RxComputationThreadPool-2 onNext( value 11 newData 2 ) RxComputationThreadPool-2 onComplete() 결과를 보면 각기 생성된 Flowable이 비동기식으로 송신 되기때문에 서로 다른 스레드에서 실행돼 데이터를 받는 순서대로 송신하지 않는다는 점을 주목하자 8. concatMap받은 데이터를 Flowable로 변환하고 변환된 Flowable을 하나씩 순서대로 실행해서 수신자에서 송신합니다. 다시 말해 여러 데이터를 계속 받더라도 첫 번째 데이터로 생성한 Flowable 의 처리가 끝나야 다음 데이터로 생성한 Flowable을 실행하는 것입니다.생성된 Flowable의 스레드에서 실행되더라도 데이터를 받은 순서대로 처리하는 것을 보장하지만, 처리 성능에 영향을 줄 수 있습니다.public static void concatMap() { Flowable<String> flowable = Flowable.range(10, 5) //map(Function mapper) //mapper : 받은 데이터를 가공하는 함수형 인터페이스 .concatMap(value -> Flowable.interval(100L, TimeUnit.MILLISECONDS).take(2) .map(data -> ("value : " + value + " data : " + data))); //구독을 시작한다. flowable.subscribe(new CustomSubscriber<>()); } 결과 RxComputationThreadPool-1 onNext( value : 10 data : 0 ) RxComputationThreadPool-1 onNext( value : 10 data : 1 ) RxComputationThreadPool-2 onNext( value : 11 data : 0 ) RxComputationThreadPool-2 onNext( value : 11 data : 1 ) RxComputationThreadPool-3 onNext( value : 12 data : 0 ) RxComputationThreadPool-3 onNext( value : 12 data : 1 ) RxComputationThreadPool-4 onNext( value : 13 data : 0 ) RxComputationThreadPool-4 onNext( value : 13 data : 1 ) RxComputationThreadPool-5 onNext( value : 14 data : 0 ) RxComputationThreadPool-5 onNext( value : 14 data : 1 ) RxComputationThreadPool-5 onComplete() 결과를 보면 생성된 Flowable 스레드와 데이터 순서대로 출력이 보장된다 것을 알 수 있다. 9. toListtoList는 송신할 데이터를 모두 리스트에 담아 전달합니다. 한꺼번에 데이터를 List로 가공해서 받기에 좋습니다. 하지만 많은 양의 데이터를 처리할 경우 버퍼가 생길 수 있고, 쌓은 데이터 때문에 메모리가 부족해질 수도 있습니다. 또한 수신되는 데이터는 하나이므로 Flowable이 아닌 Single 반환값을 사용합니다.public static void toList() { Single<List<String>> single = Flowable.just("A", "B", "C", "D", "E", "F") .toList(); // 구독을 시작한다. single.subscribe(new SingleObserver<List<String>>() { @Override public void onSubscribe(Disposable d) { System.out.println(Thread.currentThread().getName() + " onNext()"); } @Override public void onSuccess(List<String> strings) { //최종 완료된 리스트를 순서대로 출력한다. for (String text : strings) { System.out.println(Thread.currentThread().getName() + " onSuccess( " + text + " )"); } } @Override public void onError(Throwable e) { System.out.println(Thread.currentThread().getName() + " onError() " + e); } }); } 결과 main onNext() main onSuccess( A ) main onSuccess( B ) main onSuccess( C ) main onSuccess( D ) main onSuccess( E ) main onSuccess( F ) 10. toMaptoMap은 송신할 데이터를 모두 키와 값의 쌍으로 Map에 담아 전달합니다. 나머지는 toList의 특징과 같습니다. 송신되는 데이터 타입은 Map에 담아서 송신하는데 동일한 key에서 value는 마지막 데이터가 덮어 씁니다. 요청되는 값보다 결과 값이 적을 수도 있습니다. List 값을 손쉽게 key, value로 분리할 수 있는 함수이기도 합니다.public static void toMap() { Single<Map<Long, String>> single = Flowable.just("1A", "2B", "3C", "1D", "2E") //toMap(Fuction keySelector, Function valueSelector, Callable mapSupplier) //keySelector : 받은 데이터로 Map에서 사용할 키를 생성하는 함수형 인터페이스 //valueSelector : 받은 데이터로 Map 넣을 값을 생성하는 함수형 인터페이스 .toMap(value -> Long.valueOf(value.substring(0, 1)), data -> data.substring(1)); //구독을 시작한다. single.subscribe(new SingleObserver<Map<Long, String>>() { @Override public void onSubscribe(Disposable d) { System.out.println(Thread.currentThread().getName() + " onNext()"); } @Override public void onSuccess(Map<Long, String> longStringMap) { //최종 완료된 map을 순서대로 출력한다. for (long id : longStringMap.keySet()) { System.out.println(Thread.currentThread().getName() + " onSuccess( id : " + id + ", value " + longStringMap.get(id) + " )"); } } @Override public void onError(Throwable e) { System.out.println(Thread.currentThread().getName() + " onError() " + e); } }); } 결과 main onNext() main onSuccess( id : 1, value D ) main onSuccess( id : 2, value E ) main onSuccess( id : 3, value C ) 11. toMultiMap키와 컬렉션 값으로 이루어진 Map을 데이터로 변환하여 송신하는 함수입니다. 나머지 특징은 toList, toMap과 같습니다. toMap에서 중복되는 value를 관리하는 건 없었지만, value를 collection으로 관리하여 전달되는 데이터를 모두 수신할 수 있습니다.public static void toMultiMap() { Single<Map<String, Collection<Long>>> single = Flowable.interval(100L, TimeUnit.MILLISECONDS) .take(5) //toMultimap(Function keySelector, Function valueSelector) .toMultimap(value -> { //value가 홀수인지 짝수 인지 판단해서 key값을 리턴한다. if (value % 2 == 0) { return "짝수"; } else { return "홀수"; } }); //구독을 시작한다. single.subscribe(new SingleObserver<Map<String, Collection<Long>>>() { @Override public void onSubscribe(Disposable d) { System.out.println(Thread.currentThread().getName() + " onNext( " + d + " )"); } @Override public void onSuccess(Map<String, Collection<Long>> stringCollectionMap) { for (String key : stringCollectionMap.keySet()) { StringBuffer stringBuffer = new StringBuffer(); for (long value : stringCollectionMap.get(key)) { stringBuffer.append(" " + value); } System.out.println(Thread.currentThread().getName() + " onSuccess( id : " + key + ", value " + stringBuffer.toString() + ")"); } } @Override public void onError(Throwable e) { System.out.println(Thread.currentThread().getName() + " onError() " + e); } }); } 결과 main onNext() RxComputationThreadPool-1 onSuccess( id : 짝수, value 0 2 4 ) RxComputationThreadPool-1 onSuccess( id : 홀수, value 1 3 ) 12. filterfilter는 받은 데이터가 조건에 맞는지 판단해 결과가 true인 값만 송신합니다. 위의 just, fromArray, interval이 반복적인 케이스였다면, filter는 if문처럼 조건문의 역할을 할 수 있습니다. 반복문 함수와 조건문 함수를 같이 사용해 몇 줄 안에 for, if와 똑같이 구현할 수 있죠.public static void filter() { Flowable<Long> flowable = Flowable.interval(300L, TimeUnit.MILLISECONDS) //짝수만 통과한다. 3개만큼 .filter(value -> value % 2 == 0).take(3); //구독을 시작한다. flowable.subscribe(new CustomSubscriber<>()); } 결과 RxComputationThreadPool-1 onNext( 0 ) RxComputationThreadPool-1 onNext( 2 ) RxComputationThreadPool-1 onNext( 4 ) RxComputationThreadPool-1 onComplete() 13. distinct이미 처리된 데이터를 다시 볼 필요가 없을 때 사용하는 함수입니다. 송신하려는 데이터가 이미 송신된 데이터와 같다면 해당 데이터는 무시합니다. 이 함수는 내부에서 HashSet으로 데이터가 같은지 확인합니다.public static void distinct() { Flowable<String> flowable = Flowable.just("A", "a", "B", "b", "A", "a", "B", "b") //distinct(Function keySelector) //keySelector : 받은 데이터와 비교할 데이터를 확인하는 함수 //모두 소문자로 변환하여 알파벳 기준으로 데이터를 판단한다. .distinct(value -> value.toLowerCase()); //구독을 시작한다. flowable.subscribe(new CustomSubscriber<>()); } 결과 main onNext( A ) main onNext( B ) main onComplete() 14. take1.taketake 함수로 지정된 횟수만큼 받은 데이터를 송신합니다. 지정된 횟수에 도달하면 완료를 송신해 처리 종료합니다.2.takeUntil지정된 조건까지 데이터를 송신하는 연산자입니다. 조건이 되면 완료를 송신해 종료합니다.3.takeWhile지정된 조건이 해당할 때만 데이터를 송신하는 연산자입니다.4.takeLast데이터의 끝에서부터 지정한 조건까지 데이터를 송신하는 연산자입니다.take 함수는 한 화면에 출력되거나 칠요한 데이터만큼 리스트에서 값을 하나씩 수신할 때 사용합니다. 예를 들어 화면에 데이터가 6개가 필요하면 take를 이용해 원하는 만큼의 데이터를 가져올 수 있습니다.Flowable.take(6) 또한 이후에 나올 skip 함수를 같이 사용하면 두 번째 화면에서 필요한 데이터를 6개 가져올 수 있습니다.Flowable.skip(6).take(12) 1. take public static void take() { // 100 밀리세컨드만큼 반복하며 총 5개를 출력후 종료한다. Flowable<Long> flowable = Flowable.interval(100L, TimeUnit.MILLISECONDS) .take(5); //구독을 시작한다. flowable.subscribe(new CustomSubscriber<>()); } 결과 RxComputationThreadPool-1 onNext( 0 ) RxComputationThreadPool-1 onNext( 1 ) RxComputationThreadPool-1 onNext( 2 ) RxComputationThreadPool-1 onNext( 3 ) RxComputationThreadPool-1 onNext( 4 ) RxComputationThreadPool-1 onComplete() 2. takeUntil public static void takeUntil() { // 100 밀리세컨드만큼 반복하며 값이 5가 될때까지 송신한다. Flowable<Long> flowable = Flowable.interval(100L, TimeUnit.MILLISECONDS) .takeUntil(value -> value == 5); //구독을 시작한다. flowable.subscribe(new CustomSubscriber<>()); } 결과 RxComputationThreadPool-1 onNext( 0 ) RxComputationThreadPool-1 onNext( 1 ) RxComputationThreadPool-1 onNext( 2 ) RxComputationThreadPool-1 onNext( 3 ) RxComputationThreadPool-1 onNext( 4 ) RxComputationThreadPool-1 onNext( 5 ) RxComputationThreadPool-1 onComplete() 3. takeWhile public static void takeWhile() { // 100 밀리세컨드만큼 반복하며 값이 5가 아닐경우까지 송신한다. Flowable<Long> flowable = Flowable.interval(100L, TimeUnit.MILLISECONDS) .takeWhile(value -> value != 5); //구독을 시작한다. flowable.subscribe(new CustomSubscriber<>()); } 결과 RxComputationThreadPool-1 onNext( 0 ) RxComputationThreadPool-1 onNext( 1 ) RxComputationThreadPool-1 onNext( 2 ) RxComputationThreadPool-1 onNext( 3 ) RxComputationThreadPool-1 onNext( 4 ) RxComputationThreadPool-1 onComplete() 4. takeLast public static void takeLast() { //100밀리 세컨트만큼 반복하며 5개의 출력중 뒤에 2개만 송신한다. Flowable<Long> flowable = Flowable.interval(100L, TimeUnit.MILLISECONDS) .take(5) .takeLast(2); //구독을 시작한다. flowable.subscribe(new CustomSubscriber<>()); } 결과 RxComputationThreadPool-1 onNext( 3 ) RxComputationThreadPool-1 onNext( 4 ) RxComputationThreadPool-1 onComplete() 15. skip1.skip함수로 지정된 횟수만큼 받은 데이터 송신을 제외합니다. 지정된 횟수가 초과되면 나머지 데이터를 송신합니다.2.skipUntil지정된 조건까지 데이터 송신을 제외하는 연산자입니다. 조건이 되면 나머지 데이터를 송신합니다.3.skipWhile지정된 조건이 해당될 때만 데이터 송신을 제외하는 함수입니다.4.skipLast데이터의 끝에서부터 지정한 조건까지 데이터 송신을 제외하는 함수입니다.take와 반대의 기능을 갖고 있습니다. 보통 페이저나 리스트에서 paging을 처리할 때는 take와 skip을 혼용합니다.1. skip public static void skip() { //100 밀리세컨드만큼 반복하며 5번 발행하고, 처음 2개를 제외합니다. Flowable<Long> flowable = Flowable.interval(100L, TimeUnit.MILLISECONDS) .take(5) .skip(2); //구독을 시잔한다. flowable.subscribe(new CustomSubscriber<>()); } 결과 RxComputationThreadPool-1 onNext( 2 ) RxComputationThreadPool-1 onNext( 3 ) RxComputationThreadPool-1 onNext( 4 ) RxComputationThreadPool-1 onComplete() 2. skipUntil public static void skipUntil() { //300밀리 세컨드만큼 반복하며 5개를 발행하고, 1000 밀리세컨드 제외 후 송신합니다. Flowable<Long> flowable = Flowable.interval(300L, TimeUnit.MILLISECONDS) .skipUntil(Flowable.timer(1000L, TimeUnit.MILLISECONDS)) .take(5); //구독을 시잔한다. flowable.subscribe(new CustomSubscriber<>()); } 결과 RxComputationThreadPool-2 onNext( 3 ) RxComputationThreadPool-2 onNext( 4 ) RxComputationThreadPool-2 onNext( 5 ) RxComputationThreadPool-2 onNext( 6 ) RxComputationThreadPool-2 onNext( 7 ) RxComputationThreadPool-2 onComplete() 3. skipWhile public static void skipWhile() { //300밀리세컨드만큼 반복하며 5개를 발행하고, 데이터 3이 올때까지 데이터를 제외힙니다. Flowable<Long> flowable = Flowable.interval(300L, TimeUnit.MILLISECONDS) .skipWhile(value -> value != 3) .take(5); //구독을 시잔한다. flowable.subscribe(new CustomSubscriber<>()); } 결과 RxComputationThreadPool-1 onNext( 3 ) RxComputationThreadPool-1 onNext( 4 ) RxComputationThreadPool-1 onNext( 5 ) RxComputationThreadPool-1 onNext( 6 ) RxComputationThreadPool-1 onNext( 7 ) RxComputationThreadPool-1 onComplete() 4. skipLast public static void skipLast() { //1000 밀리세컨드만큼 반복하며 5개를 발행하고 마지막 2개는 제외합니다 Flowable<Long> flowable = Flowable.interval(1000L, TimeUnit.MILLISECONDS) .take(5) .skipLast(2); //구독을 시작한다. flowable.subscribe(new CustomSubscriber<>()); } 결과 RxComputationThreadPool-1 onNext( 0 ) RxComputationThreadPool-1 onNext( 1 ) RxComputationThreadPool-1 onNext( 2 ) RxComputationThreadPool-1 onComplete() 16. throttleFirst데이터를 송신하고 지정된 시간 동안 들어오는 요청을 무시합니다. 이 함수는 View의 Event 처리에서 많이 사용됩니다. 중복되는 처리를 막기 위해 최초 실행 후 일정 시간 동안 View의 클릭 이벤트나 API 이벤트를 막을 수 있기 때문에 비동기 처리와 화면에 직접적인 피드백이 발생했을 때 throttleFirst를 자주 사용하고 있습니다. //데이터 요청이 30 밀리초마다 5번 발생합니다. //데이터 요청 발생시 100 밀리세컨트 동안 들어오는 데이터 요청을 무시합니다. // — 0 — 1 — 2 — 3 — 4 interval 30 밀리초 마다 // — — -*- — throttleFirst 100 밀리초 무시 Flowable<Long> flowable = Flowable.interval(30L, TimeUnit.MILLISECONDS) .take(5).throttleFirst(100L, TimeUnit.MILLISECONDS); flowable.subscribe(new CustomSubscriber<>()); } 결과 RxComputationThreadPool-1 onNext( 0 ) RxComputationThreadPool-1 onNext( 4 ) RxComputationThreadPool-1 onComplete() 17. throttleLastthrottleLast 함수는 데이터를 송신하고 지정된 시간 동안 들어오는 마지막 요청을 송신합니다. 이 함수도 throttleFirst처럼 반복적인 선택 이벤트 처리에 유용하게 사용할 수 있습니다. 간단하게 장바구니 카운트 변경을 요청할 때 마지막 변경 이벤트 데이터만 처리하면 되므로 값이 선택되고 일정 시간이 지났을 때 API를 요청해 리소스 낭비를 줄일 수 있습니다.public static void throttleLast() { //데이터 요청이 1 초 마다 6번 발생합니다. //데이터 요청 발생시 2 초 동안 들어오는 마지막 요청을 송신하다. // - 0 - 1 - 2 - 3 - 4 interval 1 초 마다 // - - -* - throttleLast 2 초의 마지막 값 송신 Flowable<Long> flowable = Flowable.interval(1, TimeUnit.SECONDS) .take(5) .throttleLast(2, TimeUnit.SECONDS); flowable.subscribe(new CustomSubscriber<>()); } 결과 RxComputationThreadPool-1 onNext( 2 ) RxComputationThreadPool-1 onNext( 4 ) RxComputationThreadPool-1 onComplete() 18. throttleWithTimeoutthrottleWithTimeout 함수는 데이터를 송신하고 지정된 시간 동안 다음 데이터를 받지 못하면 현재 데이터를 송신합니다. 완료 시엔 마지막 데이터를 송신하고 종료됩니다.public static void throttleWithTimeout() { Flowable<String> flowable = Flowable.<String>create(emitter -> { emitter.onNext("A"); Thread.sleep(1000L); // 1000 밀리세컨드 슬립 // 500 밀리세컨드 동안 데이터 다음 데이터 요청이 없으므로 A 송신 emitter.onNext("B"); Thread.sleep(300L); // 300 밀리세컨드 슬립 emitter.onNext("C"); Thread.sleep(300L); // 300 밀리세컨드 슬립 emitter.onNext("D"); Thread.sleep(1000L); // 1000 밀리세컨드 슬립 // 500 밀리세컨드 동안 데이터 다음 데이터 요청이 없으므로 D 송신 emitter.onNext("E"); Thread.sleep(100L); // 100 밀리세컨드 슬립 emitter.onComplete(); //완료 요청 시 마지막 데이터 송신 후 종료 }, BackpressureStrategy.BUFFER) .throttleWithTimeout(500L, TimeUnit.MILLISECONDS); flowable.subscribe(new CustomSubscriber<>()); } 결과 RxComputationThreadPool-1 onNext( A ) RxComputationThreadPool-1 onNext( D ) main onNext( E ) main onComplete() ConclusionRxJava에서 많이 사용되고, 또 알고 있으면 좋은 함수들을 살펴봤습니다. 브랜디에서도 이 함수들을 응용해 그동안 다양한 기능을 구현했고, 복잡한 함수도 사용하고 있습니다. 지금까지는 Flowable로 송신과 수신이 1 : 1 로 진행되었지만, 다양한 수신자를 사용해 하나의 Flowable로도 다른 화면에서 여러 수신자를 등록하여 반복적인 작업을 할 수 있습니다. 덕분에 같은 작업을 코드 중복 없이 간단하게 구현할 수 있죠.다음 글에서는 2개 이상의 Flowable을 결합해 사용하는 방법과 Android View에서 RxJava를 응용하는 방법, 구독을 관리하는 방법 등 Android에서 유용하게 쓰는 방법들을 알아보겠습니다.글고재성 팀장 | R&D 개발MA팀[email protected]브랜디, 오직 예쁜 옷만
조회수 6221

인간 중심의 자동차 디자인을 완성하다 현대자동차 제품UX기획실

시대의 패러다임을 반영한 자동차 기능과 디자인을 연구하는 이들이 있습니다현대·기아자동차가 꿈꾸는 자동차는 이제 이동 수단에 그치지 않습니다. 생활 공간이자 새로운 라이프스타일이 창조되는 공간입니다. 그 새로운 자동차 생활을 여는 출발점에는 사람을 향한 이해와 배려가 담겨 있어야 함은 물론입니다. 인간공학적 이론과 사용자 경험을 바탕으로 가장 필요한 기술을 디자인, 그리고 제품 기획으로 표현하는 현대자동차 제품UX기획실. 그들과의 인터뷰를 통해 현대자동차그룹이 만들어가는 휴먼 오리엔티드의 의미와 가치를 만나봅니다.현대자동차 제품UX기획실 사람들현대자동차 제품UX기획실 연구원들을 소개합니다!제품UX통합개발팀 이윤하 책임연구원자동차 UX기획 및 선행개발을 담당합니다. 사용자 경험을 바탕으로 사람에게 꼭 필요한 기능을 제공하는 자동차를 만드는 일에 보람을 느낍니다.제품UX통합개발팀 김한비 책임연구원자동차 UX의 방향성을 고민하고, 로드맵을 수립하고 있습니다. 사용자 관점에서 차를 바라보고 기획하다 보면 곧 자신이 이상적으로 생각하는 차를 만들 수 있을 것이라고 생각합니다.제품UX전략팀 이동경 책임연구원SUV 차종의 HMI(Human Machine Interface) 개발을 담당하고 있습니다. 차량 콘셉트에 맞는 HMI 제안, 복잡해지는 차량의 기능들을 사람들이 보다 편리하고 쉽게 조작할 수 있도록 사용자 관점에서 검토하고 개선합니다.제품UX전략팀 정재훈 연구원차세대 HMI 콘셉트를 기획·개발하고 사용자 관점에서 검증합니다. 미래의 환경과 트렌드를 분석해 사용자에게 새로운 경험을 제공하는 일에 자부심을 느낍니다.제품UX전략팀 정지연 연구원현대·기아자동차만의 일관된 HMI를 제공하기 위한 표준화 업무를 담당합니다. 누구나 쉽고 편리하게 이용할 수 있는 차를 만드는 게 목표입니다.제품UX전략팀 문재민 연구원자동차에 새로운 기능이 추가되거나 조작 방식이 변하면 사람들은 혼란을 겪습니다. 원리평가를 통해 사람이 가진 특성을 데이터화하여, 새로운 기술을 적용하는 데 기준을 제공합니다.휴먼 오리엔티드 기술에 대하여제품UX통합개발팀 김한비 책임연구원휴먼 오리엔티드 기술의 중요성이 부각되면서 자동차 분야에서도 사용자 중심의 기술과 디자인이 큰 주목을 받고 있습니다. 자동차 개발에 있어 제품UX기획실만의 원칙이 있다면 무엇인가요? 김한비 책임연구원 UX(User Experience)란 사용자가 제품이나 서비스를 이용하면서 생각하거나 느끼는 총체적인 경험을 말합니다. 이를 바탕으로 그것이 꼭 기술이 아닐지라도, 사람을 중심에 두고 꼭 필요한 ‘무엇’을 기획하는 것이 우리의 일입니다. 사람들에게 정말 필요한 차를 만들려면 사용하는 사람의 입장에서 차를 바라봐야 해요. 즉 사람들이 운전할 때 가장 불편함을 느끼는 것은 무엇인지, 어린아이가 안전하게 차에 오르내리려면 어떻게 해야 하는지, 사람들은 어떤 소리를 좋아하는지 등을 고민하는 것이죠. 이는 사람에게 꼭 필요한 기능을 갖춘 차를 만들기 위한 필수 불가결한 과정입니다.정재훈 연구원 인간공학 기반의 HMI 개발은 자동차와 사람이 원활하게 소통할 수 있도록 돕는 것에 초점을 맞춥니다. 현재 차량에서 제공하는 안전·편의 기능은 200개 정도인데 자율주행 등 기술이 고도화될수록 더 많은 기능이 추가될 수밖에 없어요. 이때 사람 중심의 개발 원칙이 없다면 좋은 기능이 아무리 많아도 너무 복잡하고 어려워 사용하지 않게 되죠. 이를 방지하기 위해 우리 연구원들은 주행 중 운전자의 시선 분산을 최소화하는 ‘안전성’, 생각한 대로 쉽게 조작할 수 있는 ‘직관성’, 필요한 정보와 기능만을 제공하는 ‘간결성’을 최우선으로 HMI 표준화를 확립합니다. 쉽게 말해 돌발 상황, 혹은 일상적인 주행에서 운전자들이 흔히 보이는 행동을 분석해 가장 필요로 하는 기능을 가장 손이 닿기 쉬운 곳에 배치하는 것이죠.제품UX통합개발팀 이윤하 책임연구원제품UX전략팀 문재민 연구원사용자 경험은 어떤 방식으로 수집하고, 어떤 과정을 거쳐 차량에 적용하고 있나요? 이윤하 책임연구원 사용자 조사는 기획 단계부터 철저하게 사용자를 중심으로 진행됩니다. 여러 가지 방법이 있지만, 대개 시장 분석 데이터를 통해 우리가 만들고자 하는 차량의 주요 사용자를 설정하는 것부터 시작됩니다. 그 뒤 설정된 사용자의 집이나 사무실에 방문해 자동차를 타면서 겪는 불편함이나 상황들을 직접 듣고 함께 차를 타보며, 앞으로 자동차에 기대하는 기능이나 디자인에 대한 요구를 파악하죠. 단순한 인터뷰가 아닌 직접 관찰하고 체험함으로써 사용자가 이상적으로 생각하는 자동차의 본질을 이해하는 것입니다. 그렇게 수집된 데이터를 바탕으로 사용자 경험의 콘셉트와 이를 구현할 기능들을 기획합니다. 이후 설계 부문 연구자들과 협의해 실제 차량에 구현하고, 원리 평가 및 실험을 통해 꼭 필요한 기능만을 선별하죠.문재민 연구원 실질적이고 정교한 사용자 경험 데이터를 얻으려면 실사용자의 사용 패턴을 정량적으로 분석해야 합니다. 대표적인 방법이 사람의 시선이 어떻게 이동하는지를 측정하는 ‘시선이동 측정장비’로 주행 중 운전자의 행동 패턴을 분석하는 것이죠. 니로 EV에는 다이얼식 변속기가 적용되어 있습니다. 이 새로운 방식의 변속 장치를 운전자가 기존의 방식보다 더 편리하게 사용할 수 있도록 시선이동 측정장비를 이용해 기어를 바꿀 때 어느 곳을 보는지, 시선이 얼마 동안 머무는지, 얼마나 자주 보는지 등을 파악했습니다. 그리고 사용 패턴을 분석해 다이얼식 변속기의 위치와 크기, 제어 방식을 결정했습니다.제품UX전략팀 정지연 연구원평소 사용자들의 마음을 읽기 위해 관심을 두거나 노력하는 부분은 무엇인가요?정지연 연구원 최근 유행하는 기기들은 직접 사용해보는 편입니다. 드론, RC카, 세그웨이, 홈 IoT 기기 등 가리지 않죠. 새로운 아이디어를 생각해내고 이를 구현하는 것도 중요하지만, 지금 있는 것들을 잘 융합하는 것만으로도 얼마든지 새로움을 만들 수 있습니다. 지금은 자동차랑 크게 상관없어 보이는 기술도 언젠가는 자동차에서도 쓰일 수 있다고 생각합니다.김한비 책임연구원 음성 인터페이스에 관심이 많습니다. 집에서 AI 스피커를 사용하고 있는데 어느 날 네 살 아이가 AI 스피커에 “000, 핑크퐁 펭귄 노래 틀어줘~”라고 말하는 걸 봤어요. 글을 몰라서 휴대폰이나 컴퓨터에서는 자신이 원하는 노래를 찾을 수 없는데, 말로 하니 쉽게 사용할 수 있는 거죠. 또 AI 스피커는 이름도 있고, 대답도 하니까 더 친근하게 느끼는 것 같아요. 차도 ‘삶의 동반자’가 되려면 누구나 사용할 수 있고, 인간의 감성적인 부분까지 만족시키는 쪽으로 더 발전해야 한다고 생각합니다.제품UX전략팀 정재훈 연구원지금까지 진행한 UX 관련 기획 중 가장 보람을 느꼈던 순간이 있다면 말씀해주세요.정재훈 연구원 2016년도 제네시스 EQ900가 인간공학회가 주관하는 ‘인간공학디자인 대상’을 수상한 것이 기억에 남습니다. 제네시스 EQ900는 제네시스 브랜드를 론칭하고 처음 출시된 차량으로 ‘인간 중심의 진보(Human-Centered Luxury)’라는 브랜드 방향성을 가지고 있었던 만큼 다양한 새로운 시도를 했었거든요. 주행 중 시선이 분산되는 것을 최소화하기 위해 사용자 경험을 면밀히 분석, 필요한 순간에 가장 편리하게 조작할 수 있는 위치에 스위치를 배치했습니다. 또 항공기 인테리어 엑스포를 방문한 경험을 바탕으로 후석 시트에 앉을 때 항공기 1등석 수준의 편안함을 느낄 수 있도록 다양한 착좌 자세를 제공하고, 조작도 원터치로 작동되는 현재의 시트를 만들 수 있었습니다.정지연 연구원 현대·기아자동차는 2013년도부터 모든 차량의 실내 스위치를 기능에 따라 같은 위치에 배치한 ‘실내 스위치 표준화’를 적용하고 있습니다. 이후 미국 J.D. Power에서 발표한 IQS(자동차 초기 품질 평가)나 미국에서 제품 구매에 가장 영향력을 미치는 잡지인 < 컨슈머리포트 >에서 사용하기 편리하다는 평가를 꾸준히 받고 있죠. 또 친구에게 현대·기아자동차의 스위치 표준화에 대한 재미있는 이야기를 들은 적이 있습니다. 가끔 카셰어링을 이용하는데 꼭 현대·기아자동차를 선택한다고 하더라고요. 다른 회사 차량에 비해 스위치가 깔끔하고 사용하기 편리하게 정리되어 있기 때문이죠. 특히 주유소에서 주유구 버튼을 찾지 못해 진땀 흘릴 일이 없다는 게 가장 큰 이유였습니다.이윤하 책임연구원 올해 초 출시된 신형 싼타페는 패밀리 SUV로서, 특히 어린 자녀를 둔 사용자들의 경험을 바탕으로 안전을 중심으로 기획·개발되었죠. 안전하차보조(SEA), 후석동승자알림(ROA) 기능이 대표적이라고 할 수 있습니다. 안전하차보조는 주행 중 차선 변경 시 옆 차선의 차량 접근을 알려주는 후측방보조알림(BCW)에 쓰이는 센서를 주차 중에도 활용할 수 있도록 만든 기능입니다. 옆에서 차가 지나갈 때 뒷좌석의 어린아이가 문을 열려고 하면 자동으로 ‘차일드락’이 걸려 문이 열리지 않게 하죠. 후석동승자알림은 목적지에 도착했을 때 어린아이나 반려동물 등을 두고 내리면 운전자에게 알려줘 혹시 모를 불상사를 막아주는 기능입니다. 출시 후, 각종 언론과 자동차 관련 커뮤니티 등에서 해당 기능들이 크게 주목받아 보람을 느꼈습니다.제품UX전략팀 이동경 책임연구원UX 관점에서 미래의 자동차 기술과 디자인은 어떤 방향으로 발전해나갈까요? 이동경 책임연구원 이제 사람들은 공간이 바뀐다고 해서 하던 일을 멈추지 않습니다. 휴대폰으로 듣던 음악을 차에 타자마자 스피커로 이어 듣고, 태블릿에서 결제한 콘텐츠를 차량 모니터로 보죠. 모든 것이 연결되는 커넥티드 세상으로 변해가고 있는 것입니다. 따라서 자동차는 출발과 도착만을 이어주는 이동 수단이 아니라, 자신이 경험하고 있는 모든 것이 공유되는 또 하나의 공간이 되는 셈이죠. 미래 UX 역시 이러한 경험을 제공하고 새로운 시간 창출에 더 큰 가치를 두고 발전하지 않을까 생각합니다.김한비 책임연구원 차가 똑똑해질수록 사람들의 기대는 커집니다. 현대·기아자동차가 꿈꾸는 이상적인 자동차 역시 사람들에게 ‘삶의 동반자(Life Companion)’로 다가가는 자동차입니다. 즉 자동차를 이동 수단에서 여행의 동반자로, 편리한 기계에서 나를 이해하는 친구로, 운전하는 공간에서 머물고 싶은 공간으로 만드는 것이 현대·기아자동차의 UX 방향성입니다. 지금까지 차량에서의 고객 경험은 ‘승차-주행 준비-주행-하차’로 한정되어 있었죠. 하지만 모든 것이 초연결되는 미래 자동차를 만들기 위해서는 고객의 라이프스타일을 이해하고, 함께 소통하며 감정까지 교감할 수 있어야 해요. 그것은 자동차뿐 아니라 모든 UX의 궁극적인 지향점이라고 생각합니다.글. 임종관사진. 안용길 도트 스튜디오 ▶ 해당 기사는 현대자동차그룹 모터스라인에서 원문을 확인할 수 있습니다.#현대 #현대그룹 #현대자동차그룹 #현대자동차 #기아자동차 #제네시스 #모터스라인 #UX #사용자_경험 #HMI #자동차_디자인 #자동차_기능 #휴먼_오리엔티드 #인간공학 #직무소개 #직무정보 #HMG저널 #HMG_Journal #HMG #기업문화 #조직문화 #UX중심 #고객중심 #구성원인터뷰
조회수 3393

조직에서 반드시 나타나는 문제점

스타트업의 대표는 직원이 3명이던 초창기 시절, 직원을 항상 고맙게 여기고 평등하고 수평적으로 회사가 운영되기를 바랬다. 하지만 매출이 늘고 구성원이 5명 7명 10명으로 점점 커지면서 기묘한 일이 일어나기 시작한다. 분명 다른 회사에 비해 처우도 좋고 비전도 있었지만 직원들과 대표의 사이는 조금씩 조금씩 멀어지게 된다. 대표가 디테일을 챙기는 꼼꼼함을 보여서 직원들에게 스트레스를 주긴 했지만 조직내에서 큰그림을 볼 수 있는 사람은 여전히 대표뿐이었고, 아무리 직원들이 열심히 일해도 대표의 마음에 꼭 들수는 없었다.직원수가 15명쯤 되었을때 대표에게 받는 스트레스와 구성원간의 다툼이 생기면서 처음으로 퇴사하는 직원이 발생한다. 대표는 마음이 아펐다. 회사의 비전달성을 위해 가장 열심히 일했던 직원이었기 때문이다. 이후 대표는 회사의 운영방식을 바꾸기 시작한다. 중간 관리자를 고용하고 직원들과의 거리를 두게 된다. 중간관리자 뒤로 숨기 시작한다. 직원과의 마찰과 스트레스를 막아줄 일종의 성벽의 역할로 중간 관리자를 쓰기 시작한것이다.  지나치게 직원들을 몰아붙인 점을 인정하고, 중간 관리자에게 모든일을 일임하기로 한다. 그리고 소통은 오직 중간 관리자와만 하기 시작한다.중간관리자가 생겼지만 조직에서는 항상 문제가 발생한다. 직원들끼리 편을 가르기도 하고, 중간관리자들끼리 사소한 문제로 싸우기도 한다. 대표는 중간관리자 뒤에 숨는것으로 모든것이 해결되지 않는다는 것을 깨닫게 된다. 이와중에  똑똑하고 일잘하는 중간관리자와 촉망받는 기술자 몇명이 회사를 나가서 경쟁 업체를 창업한다. 대표는 깊은 배신감을 느끼게 되고 직원들에 대한 신뢰를 조금씩 거둬들인다. 대표는 조직 운영에 대해서는 미숙한 자신의 능력을 탓하게 된다. 그리고 이때 부터 뭔가 이상한 일이 일어난다.이전까지 대표는 관리자급 직원을 채용할때 무조건 뛰어난 능력과 실력을 봤다. 그런데 이때부터는 자기 말을 잘듣고, 그다지 능력이 뛰어나지 않은 사람을 뽑기 시작한 것이다. 능력이 뛰어나지 않은 사람을 뽑기 시작하면서 조직내에서 정치가 생겨난다. 그리고 대표를 중심으로 여러겹의 위계질서와 장벽이 생기게 된다. 새로 들어온 직원이 다수를 이루게 되면서 더욱더 빠른 속도로 대표와 직원들 간의 거리는 멀어지게 된다. 대표는 항상 인간적이고 소통하는 회사를 만들고 싶었지만, 결국 조직원과 자신을 가로막는 거대한 장벽을 치게 된다.  왜 이렇게 변했을까? 정도의 차이일뿐, 이런 패턴을 피해간 회사는 없을 것이다. 끊임없이 발전하는 회사는 위의 패턴이 진행되는 과정속에서도, 큰틀에서 일이 돌아갈 수 있도록 정교한 시스템을 구축한 회사이다. 직원들간의 마찰과 다툼, 편가르기도어떻게 보면 당연한 과정이다. 좀 과격하게 표현하자면 회사는 나의 성과가 저사람보다 드러나지 않으면 패배하는 자본주의의 싸움터이기 때문이다.작은 스타트업이 대기업에 비해 큰 페널티(자본, 인력, 시스템)를 가지고 사업을 하듯이, 큰조직의 패널티는 위와 같은 조직의 문제이다. 그래서 작은 스타트업이 때로는 거대한 대기업을 이길 수 있다.네이트온 모바일이 왜 카톡에 밀렸을까? SK직원이 미래에는 모바일 메신저시장이 중요하다는 사실을 몰라서 일까? 진작에 알았지만 큰조직의 기본 페널티때문에 도저히 작은 조직이었던 카카오톡의 실행력과 스피드를 이겨낼 수 없었기 때문에 독점했던 시장을 송두리째 빼앗겼다.민주적이고 수평적이면서 효율적인 거대 조직은 없다. 작은 스타트업은 성장하면서 필연적으로 조직의 문제점을 키울 수 밖에 없기에 규모가 작을때 최대한 이점을 잘 활용해야한다. 반면 애초에 큰 조직은 어떻게 하면 큰조직의 부작용을 최소화할 수 있는 시스템을 만들어 나가는데 온힘을 쏟아야 할것이다.#삼분의일 #매트리스 #조직문화 #인사이트 #기업문화 #스타트업 #창업자 #창업가
조회수 949

미팅룸의 브랜딩 : 비쥬얼브랜딩의 시작

우리 회사도 브랜딩을 해보자!!! 라는 생각이 등장하고 나면 우리 회사의 대부분의 것들이 꼬져 보입니다. 이 브랜딩이란 게 참으로 단어도 멋지고 막 요즘 사방팔방 브랜딩난리이니, 안하면 뒤쳐진 것 같은 느낌과 동시에 브랜딩을 한다는 것이 막 새 집 이사가는 것 같은 설레임을 주기도 합니다. 뭔가 대단한 것을 하고 있는 듯한 기분도 들고, 이번에 확실하게 브랜딩을 해놓으면 막 우리회사가 여기저기 회자되며 사람들 입에 오르내리고 카페에서도 막 '너 그거 써봤어?' 라는 얘기가 오고가는 상상을 합니다. 브랜딩만 하면 우리 회사 졸라 짱이 되는거야!! 가즈아!!!!브랜딩 가즈아아아!! 하지만 현실은 좀 다릅니다. 브랜딩을 한다고 해서 뭐가 갑자기 바뀌는 것도 아니고, 대단히 엄청난 충격이 쎄게 오는 것도 아닙니다. 단시간내에 우르르 인기를 끈다고 해서 그게 지속되리란 법도 없고, 오히려 급격한 이미지구축은 '쟤 왜 저래?' 라는 갸웃거림을 자아내기도 합니다. 일단 설레이는 마음은 좀 내려놓고 브랜딩을 시작하기에 앞서 생각을 먼저 정리해보도록 하겠습니다. 그리고 생각을 정리한 자료를 가지고, 디자이너를 만나보는 것이지요. (또는 내부디자이너에게 요청하거나). 광대하고 우주적인 개념들을 크앙크앙거리며 열변을 토한다고 상대방이 쉽게 이해할 순 없습니다. 일단 미팅을 하고 내 생각을 잘 전달해서 최적의 결과물을 만들려면, 말하는 쪽에서 최대한 깔끔한 언어를 구사해주어야 합니다.준비할 내용이 있습니다.1. A4용지 여러 장2. 연필과 지우개3. 충언을 서슴치않는 직원 1명(팩트폭력역할)4. 지당하신 말씀이시옵니다 직원 1명(멘탈지킴이)5. 자료뭉치(외부레퍼런스와 내부사업자료 모두)6. 마음(멘탈 꽉 잡으시고)각 준비물들의 쓰임새는 아래의 미팅준비단계에서 차근차근 설명하겠습니다. 일단 준비가 완료되면 본격적인 생각정리 스텝을 밟아보도록 하죠.1단계 : 내부정리1. 브랜딩은 일을 벌리는 것이 아닙니다.2. 그러나 일이 생기는 것은 어쩔 수 없습니다.3. 새로운 일이 하나 생길 때는 기존의 일을 하나 줄일 수 있는 지 고민해야 합니다.4. 내부 업무부터 효율적으로 만듭니다. 여유공간을 주는 것이지요.5. 내부자료를 꺼냅니다(준비물5번)6. 우리 사업의 사업계획서와 제안서, 업무분장표, 프로젝트현황 등을 나열합니다.7. 우리는 도대체 왜 때문에 바쁘고 정신없는지 충신(준비물3번)으로부터 듣습니다.8. 지당한 말씀 격려를 받으며 멘탈을 잠시 다잡습니다.(준비물 4번)9. 내부에 쓸데없는 일들과 인력낭비를 쳐냅니다.10. 시간과 인력을 확보한 뒤 다음을 진행합니다.위 단계에서 시간과 인력이 확보가 되지 못했는데 억지로 브랜딩을 감행하거나, 원대한 꿈을 맘껏 펼치겠다고 하면 사무실의 7개지옥이 열리고 어디선가 켈베로스가 등장해 컴터를 다 씹어먹는 사태가 발행할 수 있습니다.대표님 마음이야 충분히 이해합니다. 빨리 브랜딩해서 챡챡 정리된 멋진 회사의 모습을 보고싶겠지만... 항상 일은 내가 아닌 실무자가 하고 있단 사실을 깨달아야 합니다. 일에는 순서가 있고 체계와 비용이 필요합니다. 사람이 하는 일이다보니 그렇게 우주적인 내용을 챡챡 만들어내기 힘듭니다.  직원들에게 인피니티스톤이라도 하나씩 크리스마스 선물로 주신다면 모르겠지만.우리 브랜드를 이해하려면 적어도 우주급빌런은 되어야 하지2단계 : 브랜드정리1. 대표님 자신과 직원들을 찬찬히 보십시다.2. 이 사람들을 한 마디로 묶으면 어떤 집단일까요3. 우리가 누구인지부터 규정합니다.4. 그 사람이 뭔 일을 하고 있는지 규정합니다.5. 그거 왜 하고 있는지 살핍니다.6. 누가 무엇을 왜 하는 지 문장으로 만듭니다.7. 여기까지는 그냥 브랜딩 경영서에 나오는 것들입니다.8. 그것과 상관없는 일들을 구별합니다.9. 그건 왜 시작하게 되었는지 생각해봅니다.10. 대부분 그냥 돈때문입니다.11. 일단 그것들은 저리 치워놓습니다.12. 6번내용을 실제로 구현하는 우리 회사의 정책은 무엇인지 살펴봅니다.13. 대부분 없습니다.14. 없으니까 미팅을 하는 거겠죠.15. 로고, 슬로건, 제안서, 회사소개서 등등을 쭉 꺼내옵니다.16. 6번과 관계가 있는지 살펴봅니다.17. 대부분 없습니다..18. 합리화시키지 않습니다. 딱 봤을 때 아니면 아닌겁니다.19. 만들어야 할 것들 리스트를 정리합니다.20. 각 제작물들의 역할과 기능을 정의합니다.내부 브랜딩정리는 참 어렵습니다. 크게 3가지 이유죠.우선은 합리화가 지립니다. 사람이 말은 하면 할수록 말이 된다고...생각이 많아지고 말이 계속될수록 이것도 말이 되고 저것도 말이 되는 것 같습니다. 하지만 소비자들은 그렇게 오랜 고민을 통해 브랜딩을 인식하지 않습니다. 직관적인 겁니다. 그리고 이성적이지도 않고 논리적인 것도 아닙니다. 그러니 6번을 규정했다면 그 기준에 맞춰서 나머지 것들을 판단합니다.두 번째는 애정이 넘칩니다. 어쨋든 사업초기에 애써서 고생해서 만든 눈물과 애환과 피와 땀이 녹아있는 사랑스러운 것들이라서 쉽사리 버리기가 힘듭니다. 제작물에 인격을 부여하지 마세요. 애정어린 옛 것들로 사업하는 게 아닙니다. 방향에 맞으면 남기고, 아니면 버립니다. 주로 대표님이 혼자 하기엔 마음의 상처가 너무 크니, 냉정하기가 이루말할 데 없는 직원에게 일임하도록 합시다. 마지막으론 6번 정리가 안됩니다. 6번의 잘못된 예를 보여드리겠습니다.; 열정 넘치는 사람들이 소비자의 행복을 위해 만드는 가치있는 생활가구세상 멋진 말을 다 가져다 붙이는 게 정의가 아닙니다. 정의...라는 것은 어떤 개념으로 일축되어야 합니다. 정의에 또다른 정의가 필요한 저런 문장은 그냥 똥입니다.; 가구덕후들이 모여 만든 원룸족들을 위한 공간창출 생활가구이렇게 '아, 1인가구 전문기업!' 으로 일축!!... 물론 퀸사이즈도 팔고 3인용 식탁도 팔겠죠. 하지만 그건 플러스알파인 겁니다. 우리가 이것저것 다판다고 해서 '이것저것 다팝니다.' 라는 식이라거나 (아예 다이소처럼 그걸 컨셉으로 가져갈 게 아니라면) 가치있는 가구를 팝니다 는 등의 어쩌라고?식의 문장들만 늘어놓는 것이 딱히 좋은 방식은 아닙니다. 일단 우리의 메인을 잡고 가는 겁니다. '메인은 이건데 너무 1인가구만 팔면 2인가구는 불편할까봐 이것도 팔고있어' 라는 식인거죠. 그래서 6번을 정의할 땐 아주 메인과 부가적인 영역을 잘 구분해야 합니다. 잘 모르겠으면 지금부터 정리해서 잡도록 합시다.아니 브랜드에 멋진 말 안쓰면 회사 기가 죽습니까3단계 : 미팅준비1. 2단계의 19,20번에서 준비한 리스트와 장표를 들고 갑니다.2. 6번문장도 숙지합니다.3. 제작의뢰를 합니다.4. 단가책정을 하거나 총 금액으로 합산합니다.5. 보통 저런 비쥬얼브랜딩 제작물을 따로따로 만들진 않습니다.6. 제작 후엔 관리가이드도 함께 요청합니다.7. 기한을 산정합니다.8. 디자이너와 레퍼런스 정리를 하고9. 이 다음부턴 보통 디자인 커뮤니케이션과 같습니다.10. 브랜딩담당 직원과 컨택포인트를 연결합니다.여기서 10번의 직원은 브랜딩총괄을 하란 것이 아닙니다. 디자이너와 커뮤니케이션 할 수 있는 채널을 압축시키는 것이지, 그 사람이 혼자 브랜딩하란 얘기가 절대 아닙니다. 근데 보통은 절대 아니라고 이렇게 말해도 어느덧 시간이 지나면 그사람이 독박쓰는 경우가 99%더군요. 그래서 직원들 사이에서 뭐 똥을 밟았니, 잘못 걸렸다느니 하는 소리가 나오는 겁니다. 업무분장할 땐 이걸 명확히 해줘야 합니다. 그 담당직원에게 '너가 다 하는 게 아니야.' 라고 달래봤자 소용없습니다. 다른 직원들에게 다른 일을 주는 것이 더 중요하죠.넌 디자이너와 컨택만 해넌 실무운영가이드를 만들어넌 현재까지 만들었던 기존 제작물 파일들모아서 외장하드에 봉인시켜넌 대외홍보용 채널 확보해놔넌 매장인테리어, 앱 리뉴얼 단가랑 프로세스 확인해이런 식으로 나누어주는 겁니다. 내부적으로 '아 쟤가 다하는구나.' 라는 분위기를 만들지 않는 것이 좋습니다.상처를 치료해줄 사람 어디 없나 가만히 놔 두다간 끊임없이 덧나 사랑도 사람도 너무나도 겁나 혼자인게 무서워 난 잊혀질까 두려워 상처를 치료해줄 사람 어디 없나...제발이렇게 미팅을 시작하도록 합시다. 비쥬얼브랜딩 미팅 이후의 제작단계는 디자이너가 알아서 할 일입니다. 물론 그 디자이너가 어떻게 브랜드제작물을 만드는가는 제 다른 매거진의 글의 링크를 통해 확인하시면 편하실 듯 합니다.1. 로고를 만들어보쟈2. 회사소개서를 만들어보쟈(1)3. 회사소개서를 만들어보쟈(2)4. 제안서를 만들어보쟈5. 컨셉을 잡아보자오늘 글의 본질은 '내 생각을 먼저 정리하고 미팅을 하자' 라는 것입니다. 제 경험상 미팅이 폭망하거나 또는 뭔 말인지 못 알아듣겠거나, 하기 싫거나 또는 해도 결과물이 이상하게 나오거나, 잘나와도 관리가 안되는 모오오오오오오오든 이유는 최초에 '제작의 목적' 자체가 흐지부지였기 때문입니다. 처음엔 그렇게 말하지 않았잖아요...흐르르그르흐르륵제대로 기한을 잡고 내부정리를 먼저 하라는 이유는 서로 언짢고 매너없는 경우를 피하기 위함입니다. 개인적으로 프로젝트하다가 '내부적으로 정리가 안되서요..좀 바빠서요...다음 주에 피드백 드릴께요..' 라는 식으로 계속 딜레이만 되거나 또는 컨택포인트라고 알려준 사람이 전혀 내용을 모르고 있거나, '확인하고 다시 연락드릴께요.' 라거나.... 아니 왜 자기 회사 브랜딩프로젝트를 직원이 확인을 해봐야 하는거지??? 애시당초 내가 몸담고 있는 곳의 정체성이 어떻게 바뀌는 지에 대한 공유와 협의가 전혀 이루어지지 않았다는 얘기 아닐까요. 대기업이나 BX팀이 따로 존재하는 경우는 그럴 수도 있을 것 같지만, 제가 저런 말을 들은 곳은 소위 '그래서는 안되는 곳' 들 이었습니다. 그러니 프로젝트를 시작하기전엔 일단 우리가 그 프로젝트를 잘 운영할 수 있는 여건인가? 를 확인해 보는 것이 서로를 위해 좋을 듯 합니다.그리고 2단계에서 먼저 브랜드정리를 하는 이유는 추후에 '아 맞다!!..' 또는 제작도중에 계속 방향이 바뀌고 딴 소리가 나오는 것을 최소화 시키기 위해서입니다. 시안에 대한 수정이 이루어질 순 있겠으나, 전체 방향자체가 바뀌어버리면 안되는 겁니다. 이것은 신뢰도에 문제이기도 합니다. 어쩃든 디자이너도 외부사람입니다. 일단 그에게 비춰지는 회사의 이미지를 생각해보세요. 이랬다저랬다 생각만 많고 정리도 안되어있고... 이런 느낌으로 다가가선 안될 듯 합니다.마지막으로 예산관리는 어떤 것들을 하느냐에 따라 다르지만, 솔직히 말해서 50만원들여서 제안서 하나 만드는 걸 브랜딩이라고 하지 않았으면 합니다. 그건 그냥 제안서 만드는 거지 브랜딩이 아닙니다. 비쥬얼브랜딩 프로젝트는 보통 개월단위 또는 연단위로 진행되고 예산도 보통 수백에서 수천까지 예상보다 훨씬 "많이"듭니다.그리고 제작되는 제작물의 가이드와 관리, 운영까지 모두 프로세스잡고 실제 보여지는 과정까지가 비쥬얼브랜딩이기 때문에 시안만 전달하고 안녕~하는 것은 그냥 디자인 작업한 거지 뭐 브랜딩했다뭐한다 얘기하기가 조금 이상한 부분이 있습니다. 디자인의 본질은 그 기능에 있습니다. 디자이너가 결과물의 기능을 100%책임질 순 없지만, 떠내보내고 나몰라라 하는 것도 웃긴 일입니다. 적어도 궤도상에 올리는 단계까지의 가이드는 제공해주는 것이 맞습니다. 가능하면 그 과정까지 인볼브하는 것이 좋구요.모두모두 깔끔한 정리로 깔끔한 미팅과 만족스런 결과물들로 새해를 시작하셨으면 좋겠습니다!!!~~메리 크리스마스 :)

기업문화 엿볼 때, 더팀스

로그인

/