스위프트로 다시보는 객체지향 프로그래밍: 피해야할 코딩 습관

배경

컴퓨터와 회화(painting)를 전공했고 꽤 성공한 누군가가 프로그래머는 과학자보다 화가와 공통점이 더 많다고 했다. 정말로, 프로그래밍을 잘하려는 노력을 하다보면 과학보다는 경험적으로 실력을 쌓아야하는 운동이나 미술과 비슷하다고 느껴진다.

좋은 그림을 알아보는 능력과 그림을 잘 그리는 것이 별개이듯 코드를 보고 좋다 나쁘다를 판단할 줄 아는 것과 좋은 코드를 짜는 실력은 안타깝게도 붙어오지 않는다. 코드를 많이 보게 되고 많이 짜보고 남이 짠 코드를 유지보수 해보면서 경험적으로 판별하는 후각이 조금은 탑재된 것 같다. 하지만 내가 키보드를 두들기기 시작하면 답답함이 찾아온다. 이렇게 짜면 안된다는.. 본능적인? 경험적인? 느낌은 마구 들지만, 그렇다면 이렇게 말고 어떻게 다르게, 좋게 짜야하는지에 대한 명쾌한 답은 없는 그런 상태이다. 학창 시절에 시험을 치다보면 자신있게 답을 고르는 경우도 있지만 확실히 아닌 답을 제거한 후에 남아있는 것 중에서 고르는 경우도 있다. 확실히 아닌 것을 배제하고 나면 남아 있는 선택지에 더 많은 고민을 투자할 수 있고, 답을 선택할 확률이 높아진다. 프로그래밍으로 다시 돌아와보면, 잘못된 패턴이나 유지보수에 좋지 않은 코드의 모습을 알고 이를 피하면 좀 더 나은 코드를 쓰게 되지 않을까 하는 생각을 하게 됐다.

작년, 회사에서 함수형 스위프트 프로그래밍 스터디를 했었다. 스터디를 시작하던 날, 공부 방향을 잡기 위해 가이드를 받고자 사내에서 함수형 스위프트 강의를 하셨던 멘토님을 초빙해 조언을 부탁했다. 그 자리에서 멘토님은 지금까지 약 1년간 내 프로그래밍 공부의 방향이 된 말을 하셨다. “함수형 프로그래밍이 얼마나 어떻게 좋은지 비교하려면 잘 짠 함수형 코드와 잘 짠 객제 지향형 코드를 비교해야하는데 아직도 내가 객체 지향으로 잘 짠다고 할 수 없어서 비교가 어렵다.” 아.. ‘OOP식’으로 매일 코딩을 한다고해서, 할 줄 아는 것이 OOP 밖에 없다고 내가 OOP를 하고 있는거구나 라고 여겼었던 내 무의식적인 생각이 뿌리부터 흔들렸다. 그래서 다시 기본부터 잘해야겠다고 생각했고 이 글은 지난 몇 개월 간 습득한 지식의 요약이다.

스위프트와 SOLID

SOLID는 로버트 마틴이라는 사람이 명명한 객체 지향 프로그램 및 설계의 기본 원칙 다섯 개이다. 스위프트는 멀티패러다임 언어이지만 Foundation, UIKit 등 코코아 터치 프레임워크들은 기본적으로 OOP에 근간을 두고 있다. SOLID 원칙들과 iOS를 연관지어서 살펴보니 내가 쓰고 있는 많은 클래스들이 왜 그런식으로 만들어졌는지 알 수 있었고, 또 내가 어떻게 이 원칙들을 녹여낼 수 있는지 좀 더 와닿게 이해할 수 있었다. 본문에서는 각 원칙에 따라 만들어진(혹은 위반하고 있는) iOS 프레임워크의 예시와 함께 이 원칙을 잘 지키기 위해서 어떻게 코딩을 해야하는지, 특히 이 원칙을 어긴 것은 어떤 형태인지 중점적으로 정리했다. 앞서 말했듯, 처음부터 코드를 잘 짤 순 없으니 최소한 잘못된 코드가 어떤건지는 알고 피해가자는 컨셉이다. 그러면 어려운 이야기를 전부 이해하지 못했더라도 어떻게든 그 방향으로는 나아갈 수 있다고 생각하기 때문이다.

다만 이 글에서는 마지막 두 원칙을 제외한 SOL(쏠?)까지만을 다룬다. 그 이유는 여러가지가 있는데, 아직 뒷 부분은 설명을 할 수 있을 만큼 이해를 다 하지 못했고 또 ID는 이 글의 컨셉에 맞춰 해당 원칙이 위반된 상황을 판단하려고 하는 것이 훨씬 어렵고 미묘하기 때문이다. 하지만 SOL만으로도 내 코드의 개선 사항을 수두룩 빡빡하게 찾는데는 전혀 부족함이 없었고 끝이 안보인다!

Single Responsibility Principle

단일책임 원칙은 모든 클래스는 하나의 책임만 가져야 한다는 원칙이다. 로버트 마틴은 책임을 변경하려는 이유로 정의했다. SRP의 가장 대표적인 위반 사례는 Massive View Controller 현상이다. 이런 MVC의 문제를 해결하기 위해 여러 다른 패턴들이 생겨나고 시도되고 있는데, 그것들의 공통점은 너무 많은 역할을 하고 있는 뷰컨트롤러를 쪼개서 단일 책임만을 가지는 여러 클래스를 만들려고 하는 것이다.

SRP를 지키기 위해 당장 시도해볼 수 있는 것은 클래스를 작게 만드는 것이다. 클래스가 작다는 것이 SRP의 충분 조건은 아니지만 필요 조건이다. 이 목표를 달성하기 위해서 멘토님이 제시한 10-200룰을 기준으로 코딩해보고 있다. 10-200룰이란 함수는 10줄 이내, 클래스는 200줄 이내로 만드는 것이다. 하지만 이를 엄격하게 지키기는 쉽지 않고, 아직은 이상향으로 여기는 수준이다. 현실적으로는 200줄이 넘어가면 혼자 머리 속에서 경보를 발령하고 더이상 안 늘어나게 노력하거나 리팩토링을 시도한다. 10-200룰은 실제로 시도해보면 정말 많은 고민을 하게하고 나를 불편하게 만드는 규칙이다. 하지만 그런 불편함을 극복해나가면서 배움이 생기는 것 같다.

우선 ‘함수는 10줄’ 룰을 지키려면 함수는 하나의 작업만 해야한다라는 대원칙을 지킬 수 밖에 없고 또 추상화 레벨에 대해 고민을 하게 된다. 추상화 레벨이란 얼만큼의 정보를 해당 함수에서 노출할 것인가 하는 것이다. 신라면 봉지 뒷면에 써있는 조리법을 살펴보자. 물 550ml를 끓인다, 면과 스프, 후레이크를 넣는다, 4분 30초 더 끓인다로 되어있다. 세 단계의 추상화 레벨이 비슷하다. 물을 끓이는 단계는 더 쪼개면 냄비를 꺼낸다, 정수기를 튼다, 물을 담는다, 가스렌지에 올린다, 불을 지핀다 등등이 있을 것이다. 하지만 이걸 라면 끓이는 법에 포함시키진 않는다. 왜냐면 그것들은 물 550ml를 끓인다라는 단계(함수)로 묶어서 표현했기 때문이다. 이런 식으로 작업(함수)을 어느 수준까지 쪼개서 설명(추상화)할 것인지에 대해 고민을 하게 만들어 준다.

‘클래스는 200줄’ 룰을 지키려면 프로퍼티나 함수, 메서드를 그냥 손이 가는 곳 아무데나 만들어서 쓸 수 없다. 꼭 얘가 이 곳에 있어야 하는 이유를 찾아야 한다. 해당 클래스에 있을 이유가 없으면 SRP 위반이다. 가장 쉽게 판단할 수 있는 방법 하나는, 함수나 메서드 내부에서 self의 프로퍼티나 메서드를 얼마나 쓰고 있는지 보는 것이다. 만약 하나도 쓰고 있지 않다면 그 클래스 안에 있을 이유가 전혀 없는 것이다. 또는 self의 호출 빈도가 적을수록 클래스와 연관성이 떨어지는 것이니 나중에 클래스를 리팩토링 하거나 다이어트 시켜야 할 상황이 오면 우선적으로 내쫓을 후보가 되는 것이다.

SRP를 모든 패턴의 시작이라고 한다. SRP를 제대로 지키지 못한 채로 코딩을 하면 그 어떤 패턴을 도입해보려고 해도 잘 안될 가능성이 높다고 한다. 클래스는 하나의 역할만 해야한다는 것, 객체지향을 배우면서 가장 먼저 배우는 원칙임에도 정말 지키기 쉽지 않은 것 같다.

Open-Closed Principle

개방폐쇄 원칙은 확장에는 열려 있으나 변경에는 닫혀있어야 한다는 원칙이다. 열려 있다는 것은 기능 추가나 변경을 할 수 있어야 한다는 것이고 닫혀있다는 것은 기능 추가를 할 때 그 모듈을 쓰고 있는 코드들을 줄줄이 수정하지 않아야 한다는 것이다. OCP 위반의 대표적인 예는 어떤 타입에 대한 반복적인 분기문이다. 즉, 하나의 enum에 대해 여러 군데에서 반복적으로 if/switch문을 쓰고 있다면 고민을 해봐야한다. 왜냐하면 이런 경우, 기능 추가는 case를 한줄 추가하는 것만큼이나 쉽지만 그렇게 하는 순간 해당 enum을 스위칭하고 있는 모-오든 코드를 찾아서 수정해줘야 한다. exhaustive하게 짰다면 그나마 컴파일러의 도움을 받을 수도 있겠지만 default문을 추가했다면 그것도 여의치 않다.

OCP는 if/switch를 최대한 안 쓰는 방법을 통해 연습할 수 있다. 모든 분기문을 없애는 것은 아니고(불가능하고) enum 같이 타입을 분기하는 지점에 대해서. 처음에 이 얘기를 들었을때는 말도 안된다고 생각했다. if문은 프로그래밍의 가장 기본 아닌가? 어떻게 if/switch 없이 분기를 할 수 있지? 처음엔 너무 어색하고 답답했지만 시도해보면서 어떤식으로 내 코드에 도움이 되는지 조금씩 느끼고 있다. 앞서 언급한 10-200룰과도 상호보완적이다. 분기문을 없애는 것 만으로도 함수 및 클래스의 길이를 많이 줄일 수 있다.

그러면 if/switch문을 대체할 수 있는 방법 두 가지를 소개한다. 첫번째로 protocol(혹은 class)을 만들고 상속받아 쓰는 방법이다. 이 방법이 직접적으로 OCP를 지키는 구조다. 더 자세한 내용은 이 블로그에 있는 내용을 참고하면 되겠다. 또 한가지 간단한 방법은 딕셔너리를 활용하는 것이다. 다만 이 방법은 OCP를 지키는 구조는 아니다. 얘는 default절이 있는 switch문과 동일한 약점이 있기 때문에 case가 자주 변경될 것 같을 때는 피해야하고, 분기문을 없애고 싶을 때 제한적으로 사용하면 좋을 것 같다.

기존 switch문 코드

switch reason {
  case .initializing:
    self.instructionMessage = "Move your phone".localized
  case .excessiveMotion:
    self.instructionMessage = "Slow down".localized
  case .insufficientFeatures:
    self.instructionMessage = "Keep moving your phone".localized
  case .relocalizing:
    self.instructionMessage = "Move your phone".localized
}

딕셔너리 활용하여 분기문을 없앤 코드

//적절한 곳에 딕셔너리 생성
let trackingStateMessages: [TrackingState.Reason : String] 
                         = [.initializing.        : "Move your phone".localized,
                            .excessiveMotion      : "Slow down".localized,
                            .insufficientFeatures : "Keep moving your phone".localized,
                            .relocalizing         : "Move your phone".localized]

//switch문 대체
self.instructionMessage = trackingStateMessages[reason]

Liskov Subtitution Principle

마지막(?)으로 리스코프 치환 원칙은 상위 타입(수퍼클래스)을 하위 타입(서브클래스)의 인스턴스로 바꿔도 프로그램의 동작을 해치지 않아야 한다는 원칙이다. 정의는 어렵지만 이를 지키기 위한 방법은 의외로 간단하다. 자식이 부모의 동작을 제한해서는 안된다. 전형적인 위반 사례로는 직사각형을 상속받아서 만든 정사각형 클래스를 생각하면 된다. 정사각형은 너비와 높이가 같아야 하기 때문에 너비와 높이를 자유롭게 바꿀 수 있는 직사각형 부모 클래스의 동작을 제한해야만 원하는 동작을 만들 수 있다. 이런 경우가 LSP의 위반이다. LSP를 지키기 위해서는 직사각형이 정사각형을 상속받거나, 아니면 너비와 높이를 아예 let으로 만들어버리면 된다. (값을 바꾸는 동작 자체를 없애버리면 제한할 동작이 없기 때문에)

iOS 프레임워크에서도 LSP 위반 사례를 찾아 볼 수 있는데,

var label = UILabel(frame: .zero)
var button = UIButton(frame: .zero)
var segmentedControl = UISegmentedControl(frame: .zero)
var textField = UITextField(frame: .zero)
var slider = UISlider(frame: .zero)
var switchButton = UISwitch(frame: .zero)
var activityIndicator = UIActivityIndicatorView(frame: .zero)
var progressView = UIProgressView(frame: .zero)
var stepper = UIStepper(frame: .zero)
var imageView = UIImageView(frame: .zero)

let views: [UIView] = [...] //위 뷰들을 UIView 어레이에 저장

views.forEach { $0.frame.size.height = 30 } //뷰들의 height를 30으로 변경

let height = views.reduce(0) { $0 + $1.frame.height } 
print(height) //과연 결과는?

10개의 UIView subclass들의 높이를 30으로 바꿨으니 300이 되길 예상할수도 있지만 실제 결과는 272다. 왜냐하면 일부 뷰들은 intrinsicSize를 마음대로 바꿀수 없게 되어 있기 때문이다. 이렇게 UIView(부모 타입)의 동작(높이를 바꾸는 것)을 제한하는 일부 뷰들(UIProgressView, UISwitch 등)이 바로 LSP 위반이다.

또한 iOS 개발을 하다보면 아래의 코드를 본 적이 있고 만들어낸 적도 있을 것이다. 이처럼 부모의 함수를 오버라이드해서 퇴화시켜 버리는 함수는 만들면 LSP 위반이다.

required init?(coder aDecoder: NSCoder) {
  fatalError("init(coder:) has not been implemented")
}

LSP를 절대 어기지 않고 프로그래밍을 하는 것은 어렵다. 하지만 너무 많은 곳에서 LSP를 어긴다면 문제가 생긴다. 상위 클래스를 기준으로 코딩할 수 없어지고, 그렇게되면 상속 자체가 의미가 없어지고 OCP조차 지킬수 없게 된다. 예를 들어 UITableView는 UITableViewCell을 기준으로 만들어져 있기 때문에 우리가 만든 커스텀 셀이 LSP를 지키지 않는다면 테이블뷰도 제대로 동작하지 않을 것이다. 또한 protocol를 만드는 이유도 추상화된 인터페이스를 기준으로 코드를 작성하기 위해서인데 상속받은 타입이 protocol의 메서드를 퇴화시켜버리면 프로토콜을 기준으로 작성된 코드들이 줄줄이 망가질 수 밖에 없다.

상속은 객체 지향 프로그래밍의 중요한 부분이고 잘 쓰면 유용하지만 잘못된 상속을 만들면 문제가 발생할 수 있다. 반면 때에 따라서는 LSP를 위반함으로써 편리함과 단순함을 얻게 될 수도 있다. 결론적으로, LSP를 모든 곳에서 지키려고만 하면 비효율적이고 너무 자주 위반하면 예상하지 못한 결과들 때문에 안정성을 해치게 된다. 그러므로 상속을 만들때는 LSP 위반인지 아닌지를 숙지하고 사용하는 것이 좋겠다.

결론

이 글의 목표는 SOLID 대원칙을 완벽히 이해하려는 것이 아니다. 게다가 모든 원칙을 철저히 다 지키는 코드만 짜려고 하는 것은 비효율적이라고 볼 수도 있다. 예를 들어 모든 것을 OCP로만 짜려고 하면 너어무 불편하다. 분기문을 쓰지 않으려고 매번 프로토콜을 만들고 상속을 해야한다면 개발하는 시간도 오래걸리고 불필요한 복잡성만 증가하는 꼴이다. 따라서 적절한 트레이드 오프가 필요하다. 다섯 가지 원칙 중에서 SRP와 OCP는 이해는 되지만 가장 지키기 어렵고 끊임없이 노력해야하는 부분인 것 같다. 무엇이 정답인지 모르고 헷갈릴때 확실히 정답이 아닌 것을 지워내는 것처럼, 위 원칙을 어기는 코드 습관을 파악해서 일단 그것부터 제거하고 개선해보려고 한다.

끝.

같이 보면 좋은 자료(코드 예제): OOD Principles in Swift, Design Patterns in Swift

이 글의 초안을 읽어준 김형중, 김찬희, 김창기님에게 고마움을 전합니다.

Tags: Objected Oriented Programming, SOLID, Swift, iOS  

스위프트 API 디자인 가이드라인 초초초-요약본

https://swift.org 에서 스위프트 API 디자인 가이드라인의 존재 이유를 다음과 같이 설명하고 있다.

스위프트 3.0의 출시 목표는 프로그래머에게 일관된 사용자 경험을 제공하기 위한 표준을 세우는 것이고, API에 등장하는 이름과 표현법을 통해 이를 달성하고 있다. 이 가이드라인은 여러분의 코드가 더 큰 스위프트 생태계의 일부인 것처럼 느껴지게 하는 방법을 설명하고 있다.

‘프로그래머의 사용자 경험’이라는 단어를 썼다는 것이 어색하기도 하고 놀랍기도 하고 한편으론 정말 애플스럽다. 그런데 왠지 익숙한 표현법 아닌가?

애플 플랫폼과 매끄럽게 어우러지는 훌륭한 앱을 디자인 하기 위한 … (후략)

잘 알려진 아이콘, 텍스트 스타일, 통일성 있는 용어, 시스템이 제공하는 인터페이스 요소들을 활용하여 유저에게 일관된 경험을 제공 … (후략)

Human Interface Guidelines에 등장하는 문장들이다. 최종 사용자들에게 좋은 사용자경험을 주는 애플 플랫폼 앱을 만들기 위해 따라야하는 것이 ‘휴먼 인터페이스 가이드라인’이라면, 스위프트 개발자에게 좋은 사용자경험을 주는 코드를 만들기 위해 따라야하는 것은 ‘스위프트 API 디자인 가이드라인’인 셈이다.

가이드라인에는 꽤 많은 내용이 담겨 있다. 그렇다고 문서가 눈에 쏙쏙 들어오는 편도 아니고 누구나 들었을때 ‘음 그렇지’라고 고개가 끄덕여지는 그런 내용도 아니다. 이런 이유로 가이드라인에 무슨 내용이 담겨 있는지, 어디서부터 시작해야될지 잘 모르겠는 분들이 많은 것 같다. 약 일 년 정도 API 디자인 가이드라인과 애플 개발자 문서를 옆구리에 끼고, 아니 모니터에 띄워놓고 개발했던 경험을 토대로 내 마음속 중요도 순으로 추리고 요약해봤다. 그룹핑도 적용 상황을 기준으로 내 마음대로 재구성했다.

⚠️ 가이드라인의 모든 내용을 담고 있지 않다. 한 20% 정도? 하지만 적용되는 빈도로 따지면 아래 내용들을 합치면 전체 케이스의 50%는 넘는 것 같다.

함수 및 프로퍼티 이름

  • 메서드나 함수는 사이드 이펙트의 유무에 따라 이름 짓는다.

    • 사이드 이펙트 없는 함수는 명사구로 읽혀야한다.

    ⛔️ 잘못된 예

      x.getDistance(to: y) //"y까지의 거리를 가져와라"
    

    ✅ 올바른 예

      x.distance(to: y) //"y까지의 거리"
    

    사이드 이펙트 없이 값을 리턴하는 메서드(일명 “getter”)에 get을 붙이는 것이 대표적인 가이드라인 위배이다. 코코아터치 프레임워크에서 get, fetch, request로 시작하는 메서드는 전부 completion handler를 받는 비동기 작업 뿐이다.

    참고 : Swift 개발자처럼 변수 이름 짓기

    • 사이드 이펙트가 있는 함수는 명령형 동사구로 읽혀야한다. (명령형이라는 것은 동사원형을 쓴다는 것이다.)
      print(x)
      x.sort()
      x.append(y)
    

    위 함수들은 모두 함수 스코프 밖에까지 영향을 미친다(사이드 이펙트). print는 콘솔에 찍히고 sort와 append는 x의 값을 바꿔버린다.

  • mutating과 nonmutating 메서드 쌍은 일관성있게 이름 짓는다. 보통 mutating 함수는 명령형 동사로 쓰고 nonmutating은 “ed”나 “ing”를 뒤에 붙여서 사용한다.

Mutating Nonmutating
x.sort() z = x.sorted()
x.append(y) z = x.appending(y)
  • Bool 메서드나 프로퍼티 이름은 인스턴스에 대한 평서문처럼 읽혀야한다.

    e.g. x.isEmpty, line1.intersects(line2)

    참고 : Bool 변수 이름 제대로 짓기 위한 최소한의 영어 문법

  • 대소문자 규칙을 따른다.

    • 타입이나 프로토콜은 UpperCamelCase를 따르고 그 외에는 lowerCamelCase를 따른다.
    • 대문자 약어는 낙타표기법에 따라 전체 대문자 혹은 소문자로 통일한다.
      var utf8Bytes: [UTF8.CodeUnit]
      var isRepresentableAsASCII = true
      var userSMTPServer: SecureSMTPServer
    

    우리가 많이 쓰는 대문자 약어 중에 URL이 이 조건에 해당될 것이다. 규칙을 따르려면 아래처럼 사용해야 한다.

      let urlString = "https://soojin.ro"
      let blogURL = URL(string: urlString)
    

파라미터명(Parameter Names)

파라미터 명명 규칙은 좀 더 세부적으로 생성자일 때와 메서드일 때로 나뉜다.

생성자

  • 생성자의 첫번째 파라미터명은 타입 이름과 구(phrase)를 이뤄서는 안된다. 다시 말해 생성자의 파라미터명에는 아래처럼 전치사나 접속사 등을 써서 문장처럼 이어지게 만들지 말라는 말이다.

    ⛔️ 잘못된 예

      let foreground = Color(havingRGBValuesRed: 32, green: 64, andBlue: 128)
      let newPart = factory.makeWidget(havingGearCount: 42, andSpindleCount: 14)
      let ref = Link(to: destination)
    

    대신 아래처럼 having, and, to, with 등의 단어들을 제거한다.

    ✅ 올바른 예

      let foreground = Color(red: 32, green: 64, blue: 128)
      let newPart = factory.makeWidget(gears: 42, spindles: 14)
      let ref = Link(target: destination)
    
  • 무손실 타입 변환(value preserving type conversion)을 하는 생성자는 첫번째 파라미터명을 생략한다. 추가적으로, 손실이 일어나는 타입 변환이라면 어떤 손실이 일어나는지 명시해주는 것을 추천한다.

    ✅ 올바른 예

      Int64(someUInt32)
      String(someNumber)
      String(someNumber, radix: 16)
      UInt32(truncating: someUInt64) //64비트 -> 32비트로 손실 발생 명시
    

즉, 무손실 타입 변환이 일어나는 생성자가 아니라면 첫번째 파라미터에는 이름을 꼭 부여한다.

메서드 및 함수

  • 첫번째 파라미터가 전치사(to, in, at, from, with 등등)구의 일부라면 파라미터명을 부여한다.

    ✅ 올바른 예

      x.removeBoxes(havingLength: 12)
      employees.remove(at: x)
    
  • 그렇지 않고 첫번째 파라미터가 자연스럽게 이어지면 생략한다.

    ✅ 올바른 예

      x.addSubview(y)
      allViews.remove(cancelButton)
      x.insert(y, at: z)
      x.append(y)
    
  • 그 외 모든 경우에는 파라미터 이름을 부여한다.

끝.

가이드라인 원본

가이드라인 번역본

Tags: Swift API Design Guidelines, abriged  

대학생이라면 네이버 캠퍼스 핵데이에 참가해야하는 이유 + 지원 꿀팁

네이버 캠퍼스 핵데이란?

네이버 캠퍼스 핵데이(이하 핵데이)는 연 2회, 1박 2일로 진행되는 해커톤 행사이다. 다만 일반적으로 우리가 해커톤이라고 부르는 것과는 다르다. 일반적인 해커톤은 하나의 테마가 주어지고 그 테마에 맞는 소프트웨어를 개발, 마지막 날에 시상식으로 마무리한다. 반면 핵데이는 시상을 위해 경쟁하는 대회가 아니다. 참여 멘티와 멘토가 서로서로 배워가는 시간이다. 멘티는 네이버 현업 개발자에게 멘토링을 받고 멘토도 업무와 연관있거나 평소에 관심 있던 주제를 발제해 함께 도전하고 배우기 위해 참가한다.

핵데이에서는 현업 네이버 개발자 한 명과 대학생 멘티 2~3 명이 한 팀이 된다. 멘티는 수 십 개의 주제 중에 참여하고 싶은 주제를 3순위까지 선택해서 지원한다. 멘토는 미션을 가장 잘 수행할 것 같은 지원자를 선발해서 자신의 팀을 꾸린다. 합격 발표가 나면 멘티는 깃헙 리포와 메신저 방에 초대를 받게 된다. 그로부터 핵데이까지 2주 정도의 기간이 있는데 이 기간에 온라인으로 멘토링을 하기도 한다. 핵데이는 시작부터 끝까지 팀이 중심이 되며 매우 자율성이 높다. 그래서 멘토와 멘티의 팀워크가 중요하다. 적극적으로 임할수록 더 많은 것을 배워갈 수 있다.

커넥트원

핵데이는 물 좋고 공기 좋은 춘천 네이버 커넥트원에서 1박 2일 동안 진행된다. 오후 12시 ~ 1시쯤 그린팩토리에 모여서 오리엔테이션을 하고 버스에 탄다. 약 한 시간 반을 달려 도착하면 짧은 오리엔테이션을 마친 후 자리 세팅을 하고 본격적으로 해커톤이 시작된다. 무제한 제공되는 스낵과 커피와 음료수를 먹으면서, 다같이 개발 방향을 논의하기도 하고 막히는 부분을 서로서로 도우면서 문제를 해결한다. 깃헙에 코드를 푸시하면 멘토가 보고 리뷰를 해주기도 한다. 시작할 때만 해도 ‘이걸 어떻게 개발하지’ 라고 생각했던 문제들이 조금씩 풀린다. 자매품으론 ‘아니 벌써 10시야?’ 라는 말도 있다.

핵데이 참가자들은 평가 받기 위한 결과물을 만들지 않고 스스로 배우기 위해 개발을 한다. 그래서 이튿날 시상식이나 발표회 같은 건 없다. 대신 팀 별로 모여서 자유롭게 회고를 한다. 참가자 사이에 경쟁도 없다. 멘티들은 협업하는 관계이다. 협업해야 모두가 각자 더 많은걸 배워갈 수 있다.

핵데이에서 얻어 가는 것

이 행사의 VIP는 대학생 개발자다. 핵데이를 준비하시는 분들은 참가 멘티들이 더 배우고 성장할 수 있게 세심한 부분까지 신경쓰신다. 행사가 거듭될수록 멘티들의 만족도도 높아지고 있다고 한다. 나는 여태까지 멘티로 한 번, 멘토로 두 번 참가했다. 왜냐면 멘토로서 얻어 갈 수 있는 것도 정말 많다. 하물며 해커톤 + 멘토링 + 인턴십 3종 세트를 얻어가는 멘티들에게는 이 행사가 풀 패키지나 다름 없다.

좀 더 자세히 적어본다면,

  • 기술적 성장: 앞서 언급했듯이 캠퍼스 핵데이는 상을 받는 대회가 아니다. ‘어렵지만 해결할 수 있는’ 난이도의 문제와 직접 부딪히면서 나의 실력을 한 단계 업그레이드할 수 있는 자리이다. 뿐만 아니라 개발자 커리어나 평소 궁금했던 것들을 멘토에게 물어보고 조언을 얻을 수도 있다. 기술적 성장에는 코딩 실력 뿐 아니라 커뮤니케이션, 협업, 코드 리뷰, 토론, 문제 해결 능력이 포함된다. 핵데이에서는 이 모든걸 경험해볼 수 있다. 학교와 회사의 딱 중간이라는 느낌이 든다. 그래서 중요한 것이 자기 주도성이다.

  • 인턴십: 채용형 인턴십, 체험형 인턴십 두 가지가 있다. 주제별로 인턴십 종류가 정해져 있어서 지원할 때 유의하여 지원하면 된다. 4학년이 채용형 인턴십에 합격하면 인턴십이 끝난 후 최종 면접을 통해 채용으로 이어진다. 1 ~ 3학년은 채용 전환 면접이 없는 체험형 인턴십을 하게 된다.

  • 네트워크: 관심사가 비슷한 친구들을 만날 좋은 기회다. 보통은 주제가 iOS, 안드로이드, 웹프론트엔드, 백엔드, 머신러닝/딥러닝 등으로 분류되기 때문에 나와 같은 걸 개발하는 또래 친구들을 만나 새로운 자극을 받을 수 있다. 그 뿐 아니라 인턴까지 하게 되면 네이버 팀분들과 계속 알고 지낼 수 있다. 나도 체험형 동계 인턴십을 했었는데 인턴이 끝난 뒤에도 팀 회식에 불러주시고, 여름 방학이 다가올 즈음에 방학 때 할 일 없으면 인턴 또 하러 오라고 제안해주시는 등 좋은 기회를 많이 얻을 수 있었다.

지원서 작성 팁

멘토로 참여하면서 100여 명의 지원자를 검토해보고 느낀 점을 토대로 지원서 작성 팁을 몇 개 정리해봤다.

지원서는 네이버 공식 사이트에서 작성하고 제출한다. 핵데이에 등록된 주제를 볼 수 있는 깃헙 리포가 공유되는데 거기서 흥미를 끄는 주제를 고른다. 그리고 관심 있는 주제를 선호순으로 작성한다. 단순히 선택하고 끝이 아니라, 내가 왜 이 프로젝트를 잘 할 수 있는지 설명을 해야한다. 프로젝트마다 ‘요구사항’이 있는데 내가 이걸 잘 해낼 수 있다는걸 설득한다는 마음으로 써본다. 비슷한 주제의 프로젝트를 해본 경험이 있다면 꼭 밝힌다. 없더라도 왜 관심을 가지게 됐는지, 관심을 갖고 나서 어떤 활동을 했는지 등을 쓴다. 핵데이 지원 전에는 잘 몰랐더라도 주제를 보고나서 관심이 생겼다면 공고가 뜬 시점부터 지원 마감까지 몇 주의 기간이 있으니 이 때를 활용해서 튜토리얼을 찾아서 해보거나 프로토타입을 직접 만들어 보고나서 지원서를 작성하는 것도 좋은 방법이다.

주제별 멘토가 직접 멘티를 선발하기 때문에 통일된 기준은 없다. 어떤 멘토님은 깃헙 활동과 커밋 메시지를 보고 뽑았다고 한 경우도 있고, 해커톤 수상 경력을 좋게 보고 뽑았다고 하는 분들도 있다. 내가 멘토링하는 주제는 iOS 개발이다. 그래서 지원자들은 보통 본인이 개발한 앱을 적는다. 여태 지원자 중에 iOS 앱을 아예 개발해보지 않은 사람은 없었던 것 같다. 대부분의 지원자가 앱 개발 경험이 있기 때문에 결과물을 잘 어필하는게 중요한 것 같다. 앱 소개도 여러 방법으로 할 수 있다. 앱스토어에 출시되어 있다면 링크 하나만으로도 충분하고 직접 써볼 수 있기 때문에 제일 좋다. 공개되어 있지 않아도 소개 페이지나 시연 영상이 있다면 충분하다. 만약 깃헙에 프로젝트가 올라가 있다면 README를 활용해서 소개 글을 넣을 수도 있다. README에 스크린샷이나 gif가 있다면 어떤 앱인지 한 눈에 파악할 수 있어서 좋다. (혹시나 빌드가 안되서 앱을 설치해 볼 수 없다면 말짱 도루묵). 그리고 그 경험이 핵데이에서 어떻게 도움이 될지 간략하게 써주면 좋을 것 같다. 만약 여러 명이 같이 개발한 프로젝트라면 본인이 개발한 부분을 명시해주는 것이 좋다.

캠퍼스 핵데이는 채용과 연계된 행사이기도 해서 주제와 딱 맞는 경험이 없더라도 기본 프로그래밍 실력을 갖추고 있고 관심 분야가 팀과 맞는 지원자를 뽑기도 한다. 지원서에 대회 수상 경력, 대외 활동, 관심 분야, 기술 실력을 나타낼 수 있는 프로젝트를 적을수 있는 항목이 있다. 특히 채용을 염두해 둔 4학년을 뽑을 때는 기술 스택이 일치하는지를 보게 된다. 앱 개발과는 달리 백엔드 분야는 언어나 프레임워크가 훨씬 다양하다. 백엔드라 하더라도 노드를 쓰느냐 자바를 쓰느냐 파이썬을 쓰느냐 등등에 따라 많이 달라서 아무래도 팀이 쓰는 언어나 프레임워크를 써본 경험이 있는 후보를 선호할 것 같다.

사실 매번 뽑고 싶은 지원자들이 많아서 합격자를 선발하는 일이 정말 힘들다. 멘티당 최대 세 명까지 선정할 수 있는데 보통 뽑고 싶은 지원자가 9 ~ 10명 정도는 된다 😱. 흥미로운 프로젝트를 해봤거나 iOS 개발 외에도 머신러닝이나, 혹은 아예 개발과 무관한 다양한 분야를 경험해 본 멘티에게서 새로운 걸 배우고 자극 받기도 한다. 그래서 더 많은 대학생들이 캠퍼스 핵데이에 와서 성장하고 좋은 네트워크를 쌓고 무엇보다 새로운 경험을 해갔으면 하는 바람이 있다. 그리고 더 많은 회사들이 이런 핵데이를 열었으면 하는 바람도.

이 글의 초안을 읽어준 조소현에게 고마움을 전합니다.

Tags: NAVER CAMPUS HACKDAY, mentoring, application tips  

앱스토어 1위를 하면서 배운 것 (feat. 사이드 프로젝트)

2014년 12월 17일 보안카드 위젯을 출시했다. 며칠 뒤 금융 카테고리 1위, 한 달 뒤엔 유료앱 전체 2위까지 올랐다. 그 후 꾸준한 인기에 힘입어 2015년, 2016년, 2017년 3년 연속 올해를 빛낸 인기 앱에 선정됐고 현재 별점 4.7점(누적 리뷰 1,285개)을 달고 있다.

보안카드 위젯은 모바일 뱅킹을 할 때 필요한 은행별 ‘보안카드’를 아이폰에 암호화해서 저장해두고 알림센터 위젯을 통해 앱 전환 없이 간편하고 빠르게 조회할 수 있는 유틸리티성 금융 앱이다.

(2017년꺼는 스크린샷을 안 찍어놨다..)

수업 과제로 시작했던 사이드 프로젝트지만 많은 사람들이 좋아해준 덕분에 값진 경험을 할 수 있었다. 유저들의 칭찬에 들뜨기도 했고 부정적인 리뷰와 꾸짖는 메일로 인해 상처도 받고 감정이 상할때도 있었다. 하지만 그런 경험들로 인해 다양한 유저들을 이해하고 그들의 입장에서 생각해 볼 수 있었다. 무엇보다 사용자들과 함께 만드는 과정도 매우 즐거웠다.

토스나 카카오뱅크 같은 편리하고 혁신적인 앱들의 등장으로 이제는 내리막길을 가고 있지만 보안카드 위젯을 만들고 운영하면서 배운 것들은 또 새로운 프로젝트를 시작할 때 도움이 될 것 같아서 그 동안의 과정을 회고해보며 정리해봤다.

1. 일단 만든다

보안카드 위젯은 넥스트 재학 시절 iOS 수업의 기말 과제로 제출하기 위해 시작한 프로젝트였고, 엄청 독창적이고 새로운 걸 만들기보다는 내가 스마트폰으로 이미 쓰고 있던 앱 중 하나를 비슷하게 만들어보면서 개발 공부를 할 목적이었다. 다만 과제 제출로 끝내지 않고 사람들이 돈 주고 살 만한 완성도로 만들어서 실제 출시까지 해보자는 목표가 있었다. 그래서 여러 후보 중에 개인적으로 가장 오랫동안 유용하게 쓰고 있던 보안카드 앱을 만들어보기로 결정했다.

괜찮은 아이디어라는 생각이 들고 재밌을 것 같으면 일단 만들어 보자. 만들기 전 주변 사람들의 의견을 들어보는 것도 도움이 될 수는 있다. 하지만 부정적이거나 미적지근한 주위의 말에 너무 휘둘릴 필요 없다고 생각한다. 긍정적인 피드백이 오히려 빨간불일 수도 있다. 듣자마자 사람들이 ‘괜찮은데?’라고 하는 아이디어는 누구나 한번쯤은 떠올렸을만한, 괜찮아 보이는 아이디어에 그칠 가능성이 높다. 실리콘밸리의 스타텁 인큐베이터 Y Combinator에서는 이를 시트콤 아이디어라고 부른다고 한다. [1] 사업할 것도 아니고, 너무 거창하게 생각할 필요 없이 재밌으면 된 것이다.

2. 기능은 적게 출시는 빠르게

Minimum Viable Product(최소 기능 제품, MVP)를 첫 출시 목표로 삼는다. 처음부터 기능을 많이 넣으려다보면 사이드 프로젝트가 질질 끌리고 결국 끝을 보지 못하고 흐지부지될 수 있다. 일단 가장 중요한 기능 한 두 개만 만들어서 출시를 하고, 나머지 기능은 유저들의 의견을 받아 가면서 차차 추가하면 된다. 같은 일을 하더라도 실사용자들이 직접 요청한 것이라면 더 의욕이 생기고, 본업에 소진한 집중력과 체력도 다시 샘솟는다. 보안카드 위젯을 출시하고 얼마 후 입사했는데 유저들이 지속적으로 기능을 제안해주고 개선 사항을 남겨준 덕분에 퇴근 후에도 지치지 않고 앱을 발전시킬 수 있었다.

출시를 빠르게 하고 유저를 모아보자. 실사용자들의 피드백은 사이드 프로젝트의 가장 큰 원동력이다.

3. 차별화를 한다

보안카드 위젯의 제일 중요한 차별점은 위젯에서 바로 조회 할 수 있게 만든 것이다. 하지만 처음부터 차별점을 염두해두고 시작한 것은 아니었다. 개발을 한창 하던 와중에 당시 릴리즈된 iOS 8의 다양한 기능들을 살펴보다가 알림센터 위젯 기능을 결합해야겠다는 아이디어가 떠올랐다. 이 아이디어가 처음 떠오른 뒤 주위 사람들에게 설명을 해줬을 때 특별히 반응을 보인 사람은 없었다. 대부분 그냥 ‘아 그렇구나’ 정도였다. 하지만 개발을 해서 프로토타입을 보여주고나니 사람들이 ‘아이디어 좋다’ 이런 피드백을 주었다. 말로 듣는 것과 실물 사이의 간격은 꽤 크다는 걸 느꼈다.

🎯 보안카드 위젯의 차별점: Today Extension 기능

깔끔한 디자인과 심플한 UX만으로도 충분한 차별점을 만들 수 있다. 그리고 그런 차별점이 있다면 유저들은 비슷한 앱을 이미 쓰고 있더라도 새로 구매하는 경향이 분명 있는 것 같다. 특히 기존에 쓰던 앱이 오랫동안 업데이트가 되지 않는다면 유저들은 디자인이 좀 더 이쁘고 최신 iOS 기능을 탑재한 앱으로 갈아타기 때문에 기회는 항상 열려 있다.

🎯 앱 아이콘 개선

(좌) 1.0 버전 앱 아이콘 (우) 개선된 디자인

4. 한국 유료 앱스토어 공략

아래는 2015년 ~ 2017년 사이 판매량 그래프인데, 1년에 한번씩 판매량이 눈에 띄게 증가할 때가 있었다.

바로 매년 아이폰이 출시 되는 시기이다. 안드로이드에서 아이폰으로 넘어오는 사람들 뿐 아니라 새 아이폰으로 바꾼 사람들이 이때 앱스토어를 구경하면서 새 앱을 찾아보는게 아닌가 싶다. 그리고 실제로 노트, 캘린더, 카메라 앱 등은 정말 꾸준히 새로운 앱들이 나오고 있다. 그러니 이미 앱스토어에 비슷한 앱이 있다고 프로젝트를 접지 말고 차별점을 가지고 계속해서 관리하면 유저층을 쌓을 수 있다고 생각한다.

랭킹별 다운로드 규모

주위 사람들에게 판매량을 알려주면 다들 생각보다 적다면서 놀란다. 보안카드 위젯은 특성상 ‘국내용’ 앱이기 때문에 96%는 한국 앱스토어에서 판매됐다. 그래서 순수 국내 앱스토어 만의 규모를 파악해볼 수 있었다. (씁쓸..😢) [2]

유료앱 차트 랭킹 1일 평균 다운로드
5위 이내 200 ~ 300
10 ~ 30위 30 ~ 50
30 ~ 50위 10 ~ 20
50 ~ 100위 5 ~ 15

금융 카테고리 내에서 1위를 할 때는 하루 평균 150 ~ 200 정도 였다. 한국 앱스토어 규모가 작다고 볼 수도 있지만 뒤집어 생각하면 인기 차트에 올라가기 좀 더 쉽다는 뜻 아닐까. 그리고 순위에 오르면 마케팅 비용 없이도 사람들에게 지속적으로 노출되기 때문에 매우 좋다. 노출 👉 다운로드 발생 👉 순위 유지 👉 노출의 선순환이 발생하는 것 같다. 그래서 사이드 프로젝트를 띄우기에 괜찮은 환경이지 않나 생각도 든다.

UI 업데이트

4년 동안 약 20번의 업데이트를 해보면서 유저들은 UI 업데이트 자체를 꺼린다는 걸 느꼈다. 너무나 심플해서 바꿀 UI가 있나 싶은 정도의 앱인데도 버튼이나 글자 색, 모양, 크기, 배치 등을 바꾸면 한동안은 낮은 별점과 악플 같은 리뷰들을 많이 받는다. 거지같다, 업뎃하지 마라, 어이없다, 돌려놔라 등등.. 익숙함이 최고의 UI인 것이다. 그래도 바꿀건 바꿔야한다는 생각에, UI를 개선할때는 당분간 욕 먹을 각오하고 감행해야 한다.

결론

사이드 프로젝트를 하자!


✏️ Notes

[1] Paul Graham, How to Get Startup Ideas: 시트콤 작가들이 각본을 쓰기 위해 만들어낸 그럴싸한 아이디어라는 뜻

[2] 보안카드 위젯의 총 누적 다운로드 수는 5.5만

📍 Special

이바닥늬우스 : 찰지고 신나는 테크바닥 늬우스

이 글의 초안을 읽어준 조소현, 강한용, 김결, 김남훈, 김영, 이승원에게 고마움을 전합니다.

Tags: side project, Security Cards Widget, App Store, MVP