제목: Naming Things in Swift

최근에 나는 여러 프로그래밍 언어와 환경을 사용해보고 있고, 내 기술을 다양하게 만드려 노력하고 있다. 나는 보통 리액트(React), 스위프트, Objective-C, 스칼라로 작업해왔다. 이것들은 각자 그들의 어풍과 규약을 가지고 있다. 나는 실제 경험을 통해 배우면서 언어를 비교하고 언어의 차이를 발견하여 더 나은 스위프트 개발자가 되는데 적용시켜보기로했다.

내가 스칼라를 배울 수 있게 도와준 내 상사는 네이밍에관한 블로그 포스팅(링크)을 보내주었고 스칼라에서 다른 수준의 장황함을 사용할 때 그것의 포괄적인 설명으로 나에게 깊은 인상을 주었다. 오늘 내 목표는 스위프트에서 언제 간결해야하고 언제 설명을 덧붙여야하는지에대한 위 포스팅과 비슷한 멋진 글을 쓰려한다. 이 스칼라 포스팅에서 몇 예시를 빌려 스위프트와 iOS 앱을 연관시킬 것이다.

여러분이 프로그래밍할때 간결함을 좋아하든 하지않든, 스위프트는 당신이 선호하는 수준의 말수로 코드를 작성할 수 있게 하는 기능을 가지고 있다. 네이밍을 넘어, 트레일링 클로저 문법, 이름없는 파라미터, 익명의 클로저 인자는 어떨때는 간결하게 해주고, 어떨때는 풀어서 설명해준다.

간결해야하는지 아닌지 그 질문이 아니라, 바로 어디에 간결하게(혹은 풀어서 길게) 하면 되는지이다.

스위프트는 꽤 오랫동안 겪어왔는데, 이것은 코드가 제네럴하게 접목될 수 있는 관용구를 개발하면서 시작되었었다. 스위프트 창시자들은 휼륭한 문서인 offical API design guidelines(링크)를 배포하는데 충분히 친절해왔다. 이것들이 좋긴하지만 나는 좀 더 원하는게 따로 있다. 우리가 어떻게 스위프트의 관용적인 직감을 만들 수 있는지 이야기해보고 싶다. 우리는 세부적으로 직관적으로 네이밍하는 것에 대해 다룬 뒤 언어의 특성에대해 토론해볼것이다.

철학
스위프트 API 설계의 원리에서 특히 네이밍이 언급하는 것은 다음과 같다.
  • 사용되는 시점에서 명료함은 가장 중요한 목표이다.
  • 명료함이 간단함보다 더 중요하다.
멋진 가이드라인인데, 좀 더 깊게 들어가보자. Haoyi의 스칼라 블로그 포스팅에는 우리가 뭔가 네이밍을 붙일때 우리의 목표가 무엇인지 말해준다.
프로그래머가 아직 모르지만 알고싶어하는 것을 보여주어라
매우 흥미로운 가이드라인이며, 이것은 우리에게 코드의 맥락을. 생각하게 만들고, 미래에 어디에서 동작할지 생각하게 만든다.코드는 한번만 쓰여지지만, 계속해서 읽혀진다. 따라서 프로그래머들은 읽기 편하게 최적화시키고 작성하는 것에 힘을 들여야함을 잊지말자. 그리고 읽기 최적화될때 고려해야할 가장 중요한 점은 바로 문맥이다. 스칼라 블로그 포스팅에서 이것들을 잘 정리해 놓았다(링크). 그리고 문맥이란 프로그래머가 이미 알고 있는 것과 프로그래머가 알고 싶어 하는 것 둘 다를 모두 포함한다.

프로그래머가 이미 아는 것
  • 당신의 코드베이스에서 예전에 이미 본 것
  • 다른 코드베이스에서 예전에 이미 본 것
  • 이전 작업에서 그들이 골랐던 사실들
프로그래머가 알고 싶어 하는 것
  • 그들이 하는 것에 영향을 주는 것
  • 그들이 이해할 필요가 있는 것
  • 그들이 익숙하지 않은 것
  • 정확함, 보안, 성능등의 이유로 특별히 위험한 것
이것은 포괄적인게 아니다.

누구나 그리고 언제나, 당신의 코드를 읽고 있다고 생각해라. 코드를 매일 사용하는 사람이 직장동료일까? 아니면 지금으로부터 6개월뒤의 자기자신일까? 당신의 오픈소스 프로젝트에 가볍게 컨트리뷰트를 하려고 하고있는것일까? 이러한 여러 상황들은 어떤 함수의 이름을 어떻게 정할지 영향을 받을 것이다. 설명해보자.

당신의 코드를 매일 사용하는 한 동료의경우 당신의 코드베이스와 그것의 규약에 완전히 친숙하다면 간결한 코드가 최고일 것이다. 만약 6개월동안 그 코드베이스에서 작업할 계획이 없다면 그 규약이 결국 생소하게 되어갈 것이므로 설명하는 말처럼 만드는게 더 도움이 될 것이다. 오픈소스 프로젝트의 가벼운 컨트리뷰터들은 아마 큰 코드베이스가 어떻게 서로 맞춰지는지 이해할 수 없을 것이다. 따라서 지나친 설명은 당신 프로젝트의 컨트리뷰터가 이해하는데 도움을 줄 수 있을 것이다.

어떤 사람이 당신의 코드를 읽고싶어할지, 그리고 그들의 목적이 무엇인지 생각해보아라.

가이드라인
이것은 원칙이 아닌 가이드라인이다. 여러분의 직감이 규칙을 지키기 싫어한다면 그렇게하지 말아라. 이제 중요한 순서로 네이밍에 관한 가이드라인에대해 이야기해보고자한다. 그리고 항상 마음속에 문맥을 기억하자!

(네기 지금 이 글에서 나온(링크) 가이드라인을 스위프트에 적용시키는 점을 기억해달라-우리는 이 포스팅과 그 저자인 Li Haoyi(링크)에게 감사해야한다)

넓은 범위의 이름은 길어야한다
이 예제에서 i라는 이름이 왜 괜찮을까?
for var i in 0..<10 {
  print(i)
}
그러나 여기서는 왜 안될까?
struct MyStruct {
  let i: Int
}
i라는 놈이 코드베이스 어디에서 쓰이는지 생각해보아라. 처음 예제에서 i는 for문 안에서만 접근되었다. 그러나 두번째 예제는 구조체의 맴버이며 저 구조체를 사용하는 어디에서나 i를 접근할 수 있는데, 잠재적으로 모든 코드베이스에서 사용가능하다. 기볍게 본 것으로 i는 매우 널리 쓰일 수도 있다는 이유때문에 i의 문맥을 찾을 수 없다.

우리는 프로그래머들이 아직 모르지만 알고싶어할 코드 어떤것을 알려주어야한다는 점을 잊지말자. 위 구조체를 고쳐보자.
struct MyStruct {
  let numberOfInteractions: Int
}

여기서 말하고자하는 바는, 루프에서 쓰이는 모든 변수가 짧아야함을 의미하는게 아니라, 넓게 쓰일 것의 이름은 길어야한다는 의미이다. 이에 반해 아래 예제를 보자 이 예제는 루프 안에서도 짧은 변수가 나쁜 방법일 수 있음을 보여준다.
for var i in 0..<10 {

  ...

  ...

  let data = Data(repeating: 0, count: i)

  ...

  ...

  writeToDb(transformedData, i) // Tricky C API...

  ...

  ...

  ...

  let temp = i + 1

  ...
}
우리 모드 i가 길어야 좋을 거라는 점에 동의할 것이라 생각된다. 왜냐? 그 사용의 범위가 넓은데다가 더 사용되기 때문이다. 이것이 다음 가이드라인으로 연결시켜준다.

더 사용된 이름들은 짧아야한다.
스위프트에서 처음 배웠던 함수인 print를 한번 보자. 함수의 이름처럼 "print"는 아주 완벽하게 동작한다.
print("Hi there!")
그렇다면 왜 "cache"가 별로 좋지 않은 것일까?
class Downloader {
  func cache() { ... }
}

...

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
  func applicationDidFinishLaunching(_ application: UIApplication) {
    ...
    downloader.cache() // Only called at app startup.
  }
}
print는 아주 많이 사용되고 모든 스위프트 개발자들이 그것에 익숙하다고 생각할 것이다. cache는 한번만 쓰이며 거의 모습을 보지 못하는 커스텀 객체에 정의해 놓는다. 이런것은 긴 이름이 의미가 있다.
class Downloader {
  func initializeCache() { ... }
}
훨씬 낫다.

위험한 이름은 길어야한다. 
몇 함수들은 그들이 하는 때문에 이름이 길다. 위험한 함수들은 이름이 길어야하는 반면 빈번하게 쓰이는 일상의 함수들은 짧아야한다. 여기 아주 긴 함수이다.
extension Downloader {
  func loadDataFieldsFromOfflineCache() { ... }
}
대신 loadFromCache으로 될 수 있습니다.
extension Downloader {
  func loadFromCache() { ... }
}
그러나 이 함수를 길게하는 것이 의미가 있는지 생각해보자.
extension Downloader {
  func deleteAPICredentialsFromCache() { ... }
}
이 함수를 호출하는 것이 위험하여 긴 이름을 가지게 되었다. 우리는 사고로 사용자의 데이터를 지워버리는 일은 항상 피하고 싶을 것이다. 그렇기 때문에 이렇게 너무 간결하게 호출하고 싶지 않을 것이다.
extension Downloader {
  func delToken() { /* deletes user data omg! */ }
}
우리는 프로그래머들이 아직 모르지만 알고싶어할 코드 어떤것을 알려주어야한다는 점을 잊지말자. 이것이 사용자 데이터를 제거할 때 함수를 호출하는 누군가가 당연히 그 사실을 알고 싶어할 것이라고 상상한다.

소스 문맥의 이름들은 짧아야한다.
내부 타입이 존재하는 타입 이름은 짧아야한다. 그리고 외부 타입이 존재하면 길어야한다. 아래를 한번 생각해보자.
protocol Delegate {
  ...
}
Delegate 프로토콜이 무엇을 위한 것인지 알 수 없으므로 이것은 너무 짧다. 좀 더 긴 이름을 붙여서 더 낫게 해보자.
protocol DownloaderDelegate {
  ...
}
멋지다! 이제 저 프로토콜이 무엇을 위한 것인지 알도록 도와준다.

스위프트 컴파일러가 타임으로 프로토콜을 도와준다면 대안의 방안이 될 수 있다.
class Downloader {
  protocol Delegate {
    ...
  }
}
이것은 충분히 자격이 있는 Downloader.Delegate로 확장할 수 있다. 그러나 슬프게도 스위프트는 아직 이런식으로 감쌓여진 프로토콜의 종류를 지원하지 않는다.

그냥 이름으로 타입 정보 중복을 피해야한다.
class Downloader {
  protocol DownloaderDelegate {
    ...
  }
}
개발자들은 이미 Downloader 클래스 안에 타입들이 이 클래스와 동작해야한다는 것을 알기 때문에 정보의 중복은 무의미하다. 이제 마지막 가이드라인으로 넘어가자.

강타입(Strongly Typed) 이름들은 짧아야한다.
스위프트는 강력하게 표현력있는 타입 시스템을 가지며, 우리는 이를 이용하여 이름을 짧게 만들 수 있다. 예를들어 아래 프로퍼티를 생각해보자.
class Downloader {
  var downloaderDelegate: Delegate
}
우리는 이미 저 델리게이트 프로퍼티가 DownLoader 클래스에 속한다는 것을 알기 때문에 프로퍼티 이름으로서 downloaderDelegate를 부르는 것이 불필요하다.

아래에 또다른 카운터 예제가 있다.
func zipTwoSequences<...>(_ sequence1: Sequence1, _ sequence2: Sequence2) -> ...
대신에 표준 라이브러리는 이것만 포함한다.
func zip<...>(_ sequence1: Sequence1, _ sequence2: Sequence2) -> ...
타입 시그니처에서 인자가 시퀀스라는게 확실하기 때문이다.

여기까지가 네이밍 가이드라인에대한 이야기이고, 이제는 간결하게 만들어주는 스위프트 특징들을 이야기해보자!

전반적으로 이름들을 생략하기
설명이 긴 것부터 간결한 것까지 그 범주중에 굉장히 "간결함"의 끝에는 아예 이름이 없는 것이다. 트레일링 크로저 문법, 이름없는 파라미터, 익명의 클로저 인자들로 이름없이 할 수 있다. 그것들을 사용할 때는 아래 가이드라인을 넘는 문제이다.

클로저 문법 추적은 매우 편하고 함수 호출을 더 간결하게 하도록 도와준다. Ray Wenderlich의 스위프트스위프트 스타일 가이드에서 쿨로저 섹션(링크)의 말을 빌리자면, 클로저의 목적이 모호하다면 트레일링 클로저 문법은 사용하지 마라고 한다. 예를들자면 아래같은 경우가 나쁜 경우이다.
UIView.animate(withDuration: 1.0, animations: {
  ...
}) { finished in
  ...
}
이렇게하는 것이 훨씬 더 명료하다.
UIView.animate(withDuration: 1.0, animations: {
  ...
}, completion: { finished in
  ...
})
이름없는 파라미터(unnamed parameters)의경우는 인자 레이블로 공식 스위프트 API 가이드라인을 참고할것이다.
  • 인자들이 유용하게 구별되지 않을때 모든 레이블들을 생략하라.(ex. union(set1, set2))
  • 함수 이름의 문법이 첫번째 인자가 무엇인지 명확할때 레이블들을 생략하라.(ex. addSubview(y))
  • 타입 규약을 위해서는 레이블들을 생략하라.(ex. Int64(someUInt32))
  • 그렇지 않으면 (일반적으로는) 인자 레이블을 명시하라.

마지막으로 익명의 클로저 인자가 남았다. 대부분 클로저의 길이에따라 이것을 사용하는데, "넓은 범위의 이름은 길어야한다"는 규칙과 일맥상통한다.

만약 여러분의 클로저가 몇가지 안되는 일을 한다면 익명의 클로저 인자를 사용하라.
(0..<10).map({ String($0) })
아래는 과하게 설명이 긴 카운터 예제이다.
(0..<10).map({ number in String(number) })
그리고 아래의 것은 네이밍에관해 처음 두가지 가이드라인을 접목하지 않을 때 어떤식으로 생겼을 수 있는지 보여준다.
(0..<10).map({
  ...

  ...

  let data = Data(repeating: 0, count: $0)

  ...

  ...

  return Model(fromData: data, index: $0)
})
다시한번 Ray Wenderlich 가이드로가서 클로저에대한 정보를 살펴보길 바란다.

오늘 다루었던 가이드라인이라는 것은 절대적인 어떤 것이 아님을 기억하자. 경험하고 다른 사람에게 물어보고 그리고 배우자. 그 과정을 즐길 수 있길 바란다!


이 블로그는 공부하고 공유하는 목적으로 운영되고 있습니다. 번역글에대한 피드백은 언제나 환영이며, 좋은글 추천도 함께 받고 있습니다. 피드백은 

으로 보내주시면 됩니다.



WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,