OpenAPI 3.0 스펙 사용

https://swagger.io/specification/


참고 사이트

작성 도구 - WebStrom 플러그인

  • OpenAPI (Swagger) Editor

예시

openapi: 3.0.3
info:
  title: Project API
  description: Project API DOC
  contact:
    email: 1024corp@1024corp.com
  version: 0.0.1
servers:
  - url: <https://dev-api.com>
    description: 테스트 서버
  - url: 
    description: 로컬 서버
tags:
  - name: 회원(Member) API
paths:
  "/api/v1/member/{id}":
    put:
      tags:
        - 회원(Member) API
      summary: 회원 수정
      description: 회원 수정 API
      operationId: V1MemberUpdate
      parameters:
        - name: id
          in: path
          required: true
          example: 1
          description: 회원 식별자 정보
          schema:
            type: number
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                user_name:
                  type: string
                  example: ModifyName
                  description: 수정 이름 정보
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  version:
                    type: string
                    example: 0.0.1
                    description: Version
                  timestamp:
                    type: number
                    example: 1672531200
                    description: Unix Timestamp
                  meta:
                    type: object
                    nullable: true
                    example: null
                    description: Meta
                  data:
                    type: object
                    nullable: true
                    example: null
                    description: Data
                required:
                  - version
                  - timestamp
                  - meta
                  - data
        "400":
          description: Bad Request
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: array
                    items:
                      type: object
                      properties:
                        statusCode:
                          type: number
                          example: 400
                        message:
                          type: string
                          example: user_name must be a string
                        error:
                          type: string
                          example: Bad Request
                required:
                  - message
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
        "409":
          description: Conflict
        "500":
          description: Internal Server Error
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: array
                    items:
                      type: string
                    example: [user_name cannot be modified.]
                required:
                  - message
      security:
        - AccessToken: []
components:
  schemas: {}
  securitySchemes:
    AccessToken:
      type: apiKey
      in: header
      name: access_token
      description: Enter your Access Token
  1. 기본 정보
    • openapi : openAPI 버전 명시
    • info : api의 정보
  2. 서버 정보
    • servers : 호스팅 서버 정보
  3. 경로
    • paths : api 엔드포인트 경로, 각 경로에는 HTTP 메서드(GET, POST, PUT, DELETE 등)와 해당 경로의 요청/응답 형식을 정의하는 섹션을 포함한다
  4. 주석
    • tags : api를 그룹화 하기 위한 태그 정의
    • summary, description : api에 대한 설명
  5. 요청
    • parameters : 매개변수 정의
    • requestBody 요청 본문 형식 정의
  6. 응답
    • response : 응답 형식 정의, 상태 코드와 응답 형식 지정, content 필드에 데이터 스키마 형식 지정
  7. 보안 / 인증
    • security : api의 인증 방법 정의
    • securitySchemes : 사용되는 인증 체계 정의

Provider 살펴보기

정리 하기 앞서 Provider 의 역할은 Dependency 를 Nest Core가 Register 할 수 있도록 등록하는 곳이라 이해하면 쉬울 듯 하다. 이런 Dependency들을 등록하기 위해서는 @Injectable() 이라는 데코레이터로 선언하여 사용할 수 있으며 NestJS 에서 제공 되는 Provider 종류는 아래 와 같다.

  • ClassProvider
  • ValueProvider
  • FactoryProvider
  • ExistingProvider

 

provider 인터페이스 경로

NestJs \node_modules\@nestjs\common\interfaces\modules\provider.interface.d.ts

 

Class Provider

인터페이스 정의

export interface ClassProvider<T = any> {
    /**
     * Injection token
     */
    provide: InjectionToken;
    /**
     * 주입할 인스턴스의 클래스.
     */
    useClass: Type<T>;
    /**
     * 주입한 인스턴스의 생명 주기 default Singleton
     */
    scope?: Scope;
    /**
     * 현재 ClassProvider 에 다른 프로바이더를 주입 할 경우 (선택적)
     */
    inject?: never;
		...
}

1. 의존성 주입이 가능한 클래스 데코레이터 표기

@Injectbale() 데코레이터를 정의하여 ExampleClassService 는 의존성 주입이 가능한 클래스임을 명시한다.

// example-class.service.ts

@Injectbale()
export class ExampleClassService {
   public getProfile(){
     return {
         name: 'dongmin', 
         age: 34,
         phone:'010-8796-9394'
     };
  }
}

2. 의존하는 서비스를 정의 하기 위한 모듈 클래스 작성 후 데코레이터 표기 

@Module() 데코레이터를 정의 하여 해당 모듈에서 사용할 프로바이더( providers) 리스트를 정의 한다.

 

이해하기 쉽게 정리 하자면 @Module() 데코레이터에서 providers 배열에 등록된 서비스 프로바이더들은 키-값 쌍으로 매칭되어 Nest의 IoC (Inversion of Control) 컨테이너에서 관리 되며 각 프로바이더는 키(토큰)와 서비스 인스턴스로 매핑된다.

// example-class.module.ts

@Module({
  providers: [
      {
        provide: ExampleClassService,
        useClass: ExampleClassService 
       }
   ],
   controllers:[
    ExampleClassController 
   ]
})
export class ExampleClassModule {}

위 코드에서 ExampleClassService는 providers 배열에 등록되었으며, 이것은 IoC 컨테이너에서 "ExampleClassService"라는 토큰과 ExampleClassService 클래스의 인스턴스를 매칭하게 되며.

 

이 서비스를 해당 모듈 또는 다른 모듈에서 주입하거나 사용할 수 있게 된다.

Value Provider

인터페이스 정의

export interface ValueProvider<T = any> {
    /**
     * Injection token
     */
    provide: InjectionToken;
    /**
     * 공급자의 인스턴스 값.
     */
    useValue: T;
    /**
	 * 현재 ValueProvider 에 다른 프로바이더를 주입 할 경우 (선택적)
     */
    inject?: never;
}

Value Provider는 클래스 대신 값을 주입할 때 사용되며 아래는 useValue를 사용하여 상수 값을 주입 한다.

const profileValue = {
  name: 'dongmin',
  age: 34,
  phone: '010-8796-9394',
};

@Module({
  providers: [
    {
      provide: 'PROFILE_VALUE',
      useValue: profileValue, // 상수 값을 주입
    },
  ],
})
export class ExampleValueModule {}

 

Factory Provider

인터페이스 정의

export interface FactoryProvider<T = any> {
    /**
     * Injection token
     */
    provide: InjectionToken;
    /**
     * 주입할 공급자의 인스턴스를 반환하는 팩토리 함수.
     */
    useFactory: (...args: any[]) => T | Promise<T>;
    /**
     * 현재 FactoryProvider 에 다른 프로바이더를 주입 할 경우 (선택적)
     */
    inject?: Array<InjectionToken | OptionalFactoryDependency>;
	...
}

Factory Provider는 주입될 객체를 생성하기 위한 팩토리 함수를 사용하며. 아래는 useFactory를 사용하여 객체를 생성한다.

class ProfileService {
  getProfile() {
    return {
      name: 'dongmin',
      age: 34,
      phone: '010-8796-9394',
    };
  }
}

@Module({
  providers: [
    ProfileService,
    {
      provide: 'PROFILE_FACTORY',
      useFactory: (profileService: ProfileService) => profileService.getProfile(),
      inject: [ProfileService], // 의존성 주입
    },
  ],
})
export class ExampleFactoryModule {}

 

 

Existing Provider

인터페이스 정의

export interface ExistingProvider<T = any> {
    /**
     * Injection token
     */
    provide: InjectionToken;
    /**
     * 지정할 공급자.
     */
    useExisting: any;
}

Existing Provider는 이미 등록된 다른 프로바이더를 래핑하여 새로운 프로바이더를 생성합니다. 아래는 useExisting를 사용하여 기존 프로바이더를 래핑한다.

class LoggerService {
  log(message: string) {
    console.log(message);
  }
}

@Module({
  providers: [LoggerService],
})
export class ExampleModule {}

import { Module, Inject } from '@nestjs/common';

const ExampleLoggerProvider = {
  provide: 'EXAMPLE_LOGGER',
  useExisting: LoggerService, // LoggerService를 래핑
};

@Module({
  providers: [ExampleLoggerProvider],
})
export class ExampleLoggerModule {}

 

정리

프로바이더는 NestJS의 의존성 주입 시스템의 핵심 구성 요소이며, 프로바이더를 사용하여 NestJS 애플리케이션을 구성하고 의존성을 관리할 수 있습니다. 코드의 변경 없이도 다른 구현체로 전환하거나, 특정 조건에 따라 동적으로 다른 프로바이더를 사용하는 데 유용합니다.

  • Class Provider (클래스 프로바이더): 클래스를 주입할 때 사용하며, useClass 속성에 클래스 타입을 지정합니다. 해당 클래스의 인스턴스가 주입됩니다.
  • Value Provider (값 제공자): 클래스 대신 값 또는 상수를 주입할 때 사용합니다. useValue 속성에 값을 할당하고, provide 속성에 해당 값에 대한 고유한 토큰을 제공합니다.
  • Factory Provider (팩토리 제공자): 주입될 객체를 생성하기 위한 팩토리 함수를 사용합니다. useFactory 속성에 팩토리 함수를 정의하고, 이 함수가 반환하는 값을 주입합니다.
  • Existing Provider (기존 제공자): 이미 등록된 다른 프로바이더를 래핑하여 새로운 프로바이더를 생성합니다. useExisting 속성에 기존 프로바이더를 지정하여 래핑합니다.

'Typescript' 카테고리의 다른 글

[NestJs] - 요청 라이프 사이클  (0) 2022.09.30

 

이 확장 프로그램은 전체 텍스트 검색을 사용 설정하고 빠른 전체 텍스트 검색을 위해 2그램(bigram) 색인을 허용합니다.

참조 - https://cloud.google.com/sql/docs/postgres/extensions?hl=ko

 

1. 현재 설치된 extension 조회

select * from pg_extension;

 2. 설치가 되어있지 않은 경우, pg_bigm 설치

create extension pg_bigm with schema live;

3. gin index 설정

create index moment_hash_tag__tag_name on moment_hash_tag using gin (mht__tag_name gin_bigm_ops);

4. index 확인

select *
from pg_indexes
where tablename = 'moment_hash_tag';

5. 테스트 데이터 등록

insert into moment_hash_tag
(mc__id, mht__tag_name)
values
(1001, '비오오는 날'),
(1001, '프랑스파리여행'),
(1001, '아일랜드 어느 거리... 커피 한잔');

6. 인덱스 확인 쿼리 실행

explain analyse select * from moment_hash_tag where mht__tag_name like '%커피%';
  • 데이터가 적은 경우 Seq Scan 을 하는데, 이때 현재 세션에 대해 Seq Scan 을 비활성화
SET enable_seqscan TO off;
  • 인덱싱 상태 보기
select show_bigm(mht__tag_name) from moment_hash_tag limit 500;

JavaScript 특징

Promise 병렬 처리하는 방법을 알아보기 전에 JavaScript의 두 가지 특성부터 확인해 본다

싱글 스레드(single-thread)

JavaScript는 싱글 스레드(Single-Thread) 방식이므로 명령어 또는 코드가 순차적으로 실행됩니다. 그러므로 동시에 2개 이상의 함수를 실행시킬 수 없습니다.

이벤트 루프(Event Loop)

JavaScript는 싱글 스레드 방식으로 동작하므로 이론적으로 비동기 작업이 불가능합니다. 하지만, 싱글 스레드가 이벤트 루프에 의해 처리되므로 멀티 스레딩(Multi-Thread) 방식으로 동작하는 것처럼 보여줍니다.

이벤트 루프의 실행 에뮬레이터 http://latentflip.com/loupe

Promise

Promise는 비동기 처리에 사용되는 객체입니다. JavaScript 특성인 이벤트 루프의 규범을 따르지 않고 비동기 이벤트로 분류됩니다.

이벤트 루프는 Promise를 발견하면 비동기로 동작하는 기능(메서드, 함수)들이 이벤트 루프에서 분리됩니다. 비동기로 동작하는 기능들은 Promise가 이행(fulfilled)될 때까지 대기열(Queue)에 배치됩니다.

버튼을 클릭하면 onClick() 함수가 호출된다고 가정합시다. onClick() 함수에는 두 개의 비동기 호출이 존재합니다.

onClick = async () => {
  try {
    console.time("ABC");
    const data001 = await new Promise((resolve) =>
      setTimeout(resolve, 3000, "promise001")
    );

    const data002 = await new Promise((resolve) =>
      setTimeout(resolve, 5000, "promise002")
    );
    console.timeEnd("ABC");
  } catch (error) {
    console.error(error);
  }
};

실행 결과

ABC: 8004ms

표준 async/await 구문을 사용하여 두 개의 Promise를 처리하면, 코드가 순차적으로 처리되므로 첫 번째 Promise가 해결될 때까지 두 번째 Promise는 실행되지 않습니다.

첫 번째 Promise는 3초, 두 번째 Promise는 5초 걸릴 것으로 예상됩니다. console.time()을 사용하여 onClick() 함수의 실행시간을 측정하면 대략 8초입니다.

Promise.all()

Promise.all() 메서드는 인수로 해결해야 하는 모든 Promise를 가지며, 단일 Promise를 반환합니다.

onClick = async () => {
  try {
    console.time("ABC");
    const data001 = new Promise((resolve) =>
      setTimeout(resolve, 3000, "promise001")
    );

    const data002 = new Promise((resolve) =>
      setTimeout(resolve, 5000, "promise002")
    );

    const data003 = await Promise.all([data001, data002]);
    console.log(data003);
    console.timeEnd("ABC");
  } catch (error) {
    console.error(error);
  }
};

실행 결과

(2) ["promise001", "promise002"]
ABC: 5004.5ms

첫 번째 Promise와 두 번째 Promise 앞에 await 키워드를 제거 후 Promise가 할당된 객체인 data001과 data002를 Promise.all() 메서드에 배열로 전달합니다.

Promise.all() 함수는 두 개의 비동기 요청이 해결될 때까지 기다립니다. 결과적으로 비동기 처리 시간이 감소되며, 두 개의 Promise가 멀티 스레드 방식으로 처리되는 것처럼 보이게 합니다.

Promise.all() 함수는 전달된 Promise에서 하나라도 실패하면, 즉시 거부(reject)합니다.

onClick = async () => {
  console.time("ABC");
  try {
    const data001 = new Promise((resolve) =>
      setTimeout(resolve, 3000, "promise001")
    );

    const data002 = Promise.reject("promise002 reject");

    const data003 = await Promise.all([data001, data002]);
    console.log(data003);
  } catch (error) {
    console.error(error);
  } finally {
    console.timeEnd("ABC");
  }
};

실행 결과

TypeError: undefined is not a promise
ABC: 3.100000001490116ms

data002가 거부되었으므로 data001을 처리하지 않고 catch문으로 바로 이동합니다.

Promise.allSettled()

Promise.allSettled() 함수는 Promise.all() 함수와 동일한 인수를 가지며, 단일 Promise를 반환합니다.

Promise.allSettled() 함수의 특징은 전달된 Promise에서 거부(reject)되었는지 여부에 관계없이 해결된 Promise를 반환한다는 것입니다.

반환된 Promise는 이행된(fulfilled) 상태와 거부(rejected) 상태를 가집니다.

onClick = async () => {
  console.time("ABC");
  try {
    const data001 = new Promise((resolve) =>
      setTimeout(resolve, 3000, "promise001")
    );

    const data002 = Promise.reject("promise002 reject");

    const data003 = await Promise.allSettled([data001, data002]);
    console.log(data003);
  } catch (error) {
    console.error(error);
  } finally {
    console.timeEnd("ABC");
  }
};

실행 결과


정리

  • Promise.all() 함수와 Promise.allSettled() 함수로 Promise를 병렬 처리할 수 있습니다.
  • 모든 Promise가 정상적으로 이행(fulfilled)되어야 하는 경우 Promise.all() 함수를 호출합니다.
  • Promise 거부와 상관없이 병렬로 처리해야 하는 경우 Promise.allSettled() 함수를 호출합니다.

Background

프로그래머의 가장 어려운 업무가 이름 짓기라는 설문 결과도 있듯이 변수에 적절한 이름을 지어주는 것은 어렵고 오래걸리는 일이다.

영어가 모국어가 아닌 사람들에게는 더 어려울 수 밖에 없는데 특히 Bool 변수명을 올바르게 지으려면 몇가지 영문법을 숙지해야한다. Bool 변수명은 사소한 차이로도 의미가 많이 바뀌어 코드를 읽는 사람을 더 헷갈리게 할 수도 있기 때문에 조금이라도 더 명확하고 문법적으로 맞는 Bool 변수명을 짓는 것이 중요하다는 생각이다.

Cases

Cocoa Touch의 여러 클래스들을 훑어보면서 Bool 변수 작명을 위해 알아야하는 영문법을 네 가지 케이스들로 정리해봤다.

  • is 용법
  • 조동사 용법
  • has 용법
  • 동사원형 용법

is 용법

is로 시작하는 변수명이 가장 흔한 케이스 아닌가 싶다. 뒤에 나오는 단어의 특징에 따라 세 가지로 나눌 수 있다.

  • is + 명사
  • is + 현재진행형(~ing)
  • is + 형용사

is + 명사

“(무엇)인가?” 라는 뜻으로 쓰인다.

func isDescendant(of view: UIView) -> Bool //UIView: "view의 자식인가?" 

is + 현재진행형(~ing)

“~하는 중인가?” 라는 뜻이 필요할 때 쓰면 된다.

var isExecuting: Bool { get } //Operation: "오퍼레이션의 작업이 현재 실행 중인가?"
var isPending: Bool { get } //MSMessage: "메시지가 보내지기 전 대기 중인가?"

is + 형용사

이제부터 살짝 헷갈릴 수 있다.

형용사도 두 종류로 나뉜다.

  • 단어 자체가 형용사인 것 - opaque, readable, visible 등
  • 과거분사 형태 - hidden, selected, highlighted, completed 등

과거분사(past participle)는 간단히 말해 동사로 만든 형용사라고 생각하면 된다. 동사를 과거분사로 바꾸면 뜻이 여러가지로 바뀔 수 있는데, 일단 여기서는 수동태라고 생각하면 되겠다. hide(숨다) - hidden(숨겨진), select(선택하다) - selected(선택된), complete(완료하다) - completed(완료된) 등등. (보다시피 동사 뒤에 -ed가 붙는 형태가 가장 흔하지만, 내가 쓰려는 동사의 과거분사를 모르겠으면 사전을 찾아보자.)

UIKit을 쓰다보면 정말 많이 보는 UIView의 프로퍼티들이 이런 경우다.

var isOpaque: Bool { get set }
var isSelected: Bool { get set }
var isHighlighted: Bool { get set }
var isHidden: Bool { get set } 

❗주의❗is로 시작하는 변수명을 짓다가 범하는 흔한 실수가 바로 is + 동사원형 을 쓰는 것이다.

isAuthorize, isHide, isFind 등등.

가령,

var isEdit: Bool //gg

edit이라는 단어가 명사로 쓰일수도 있어서 해석의 여지는 있지만 뜻이 명확하지 않아 일반적으로는 곧바로 해석하기 쉽지 않다. 😧 더 잘 할 수 있다.

아래와 같이 적절하게 바꿔주면 해석이 더 쉽고 빠르다.

var isEditable: Bool //편집할 수 있는가?
var isEditing: Bool //편집 중인가?
var canEdit: Bool //편집할 수 있는가? -> 다음 '조동사 용법' 섹션 참고

4월 11일 추가

닷넷 프레임워크에 DataRowView.IsEdit이라는 불리언 변수가 있다는 제보를 받았다. 문서를 보면 ‘row가 edit mode인지’를 나타내는 불리언인데 닷넷 개발자가 아닌 다른 개발자가 edit mode 라는 것을 모르는 상태에서 문서를 읽어보지 않고는 한번에 이해할 수 없었을 것이다. 하지만 만약 IsEdit이 닷넷 프레임워크에서 자주 쓰이는 변수명이자, edit이 edit mode를 의미한다는 것이 컨벤션이라면 괜찮을 수 있다. 변수명에 신경쓰는 이유 자체가 다른 개발자(또는 내일의 나)가 내 코드를 쉽고 빠르게 이해하게 하려는 것이기에.

조동사 용법

조동사(modal verb)는 동사를 돕는 동사란 뜻인데 can, should, will 등이 있다. 주의할 점은 조동사 + 동사원형 으로 써야한다는 것 하나 뿐이다.

can: “~ 할 수 있는가?”

should, will: “~ 해야 하는가?” 혹은 “~ 할 것인가?”

등등.

var canBecomeFirstResponder: Bool { get } //UIResponder: first responder가 될 수 있는가?
var shouldRefreshRefetchedObjects: Bool { get set } //NSFetchRequest: 가져온 값을 refresh 할 것인가?

has 용법

has로 시작하는 Bool 변수명은 상대적으로 빈도가 낮지만 뜻이 전혀 다르게 쓰이는 두 가지가 있어서 알아두면 유용하다.

  • has + 명사
  • has + 과거분사

has + 명사

has 다음 명사가 나오면 “~를 가지고 있는가?” 라는 뜻이다. has는 have의 3인칭 단수인데 3인칭 단수에 대해서는 다음 파트에서 자세히 다룬다.

var hasiCloudAccount: Bool { get } //CKUserIdentity: 관련된 iCloud 계정을 가지고 있는가?
var hasVideo: Bool { get set } //CXCallUpdate: 콜에 비디오가 포함되어 있는가?

has + 과거분사

모든 케이스를 통틀어 가장 덜 쓰이는 케이스 같으므로 만약 이해 안되더라도 넘어가도 지장 없다. 게다가 is + 과거분사와 뜻이 거의 같기 때문에 꼭 알아야할 필요도 없어보인다.

이때의 과거분사는 아까 is + 과거분사 때와는 다른 의미이다. has + 과거분사는 현재완료 의 의미를 가지는데 굳이 해석하자면 ‘과거에 완료된 것이 현재까지 유지되고 있다’는 뜻이다. 따라서 Bool 변수로 쓰이면 ‘~가 유지되고 있는가?’ 라고 해석할 수 있다.

var hasConnected: Bool { get } //CXCall: 콜이 연결되어 있는가?
var hasEnded: Bool { get } //CXCall: 콜이 끝났는가?

근데 isConnected, isEnded로 해도 의미가 비슷하다. 영어로 미묘한 느낌적인 차이가 있긴 하지만 수능을 다시 볼게 아니라면 꼭 알아야하는 건 아니라고 생각한다.

동사원형 용법

Bool 변수 짓기의 끝판왕이라는 생각에 마지막에 넣었다. 이거까지 잘 쓸 줄 알면 원어민 개발자 부러워하지 않아도 된다. (지극히 개인적인 의견)

주의할 점은 동사원형을 3인칭 단수로 써야한다는 것이다. 3인칭 단수는 보통 동사원형 뒤에 -s나 -es가 붙는 형태이다. Cocoa Touch에서 자주 쓰이는 단어들을 보면,

  • supports: ~을 지원하는가?
  • includes: ~을 포함하는가?
  • shows: ~을 보여줄 것인가?
  • allows: ~을 허용할 것인가?
  • accepts: ~을 받아 주는가?
  • contains: ~을 포함하고 있는가?

등이 있다. 이정도 단어들의 쓰임새만 알아도 풍부한 Bool 변수명을 짓기에 충분한 것 같다.

var supportsVideo: Bool //CXProviderConfiguration: 비디오를 지원하는가?
var includesCallsInRecents: Bool //CXProviderConfiguration
var showsBackgroundLocationIndicator: Bool //CLLocationManager
var allowsEditing: Bool //CNContactViewController: 편집을 허용하는가?
var acceptsFirstResponder: Bool //NSResponder

그 외에도 returns, preserves 등도 있었다.

var preservesSuperviewLayoutMargins: Bool //UIView
var returnsObjectsAsFaults: Bool //NSFetchRequest

하지만 3인칭 단수가 아닌 경우도 있다. 꽤나 예외적인 경우로 판단되기 때문에 이 역시 이해가 안되더라도 그냥 넘어가도 무방하다.

//Core Location의 CLRegion 
var notifyOnEntry: Bool { get set }
var notifyOnExit: Bool { get set }

유저의 기기가 해당 region을 벗어나거나 진입할 때 delegate를 통해 노티피케이션을 받을지 말지를 나타내는 Bool 값이다. 이 경우 notifies가 아닌 동사원형 그대로 쓰인 이유는 region 인스턴스가 notify를 하는 주체가 아니기 때문이다. 만약 notifies로 썼다면 region 인스턴스가 노티를 준다는 뜻인데 이는 맞지 않다. (아마도 CLLocationManager가 노티를 주지 않을까. 아무튼 region 인스턴스가 notify를 하는 주체가 아니기 때문에 3인칭 단수를 쓰지 않았다.)

3인칭 단수가 중요한 이유

코드 한 줄을 하나의 문장으로 비유하면 주어 역할을 하는 인스턴스가 3인칭 단수이기 때문에 문법적으로 꼭 써야하는 이유도 있지만, 3인칭 단수로 쓰지 않을 경우 스위프트 API 디자인 가이드와의 일관성이 깨져서 코드를 읽는 사람을 혼란에 빠뜨릴 수 있다. 가이드에 따르면 mutating 함수는 동사원형으로, nonmutating 함수는 동사 뒤에 -ed나 -ing를 붙여서 쓴다.

친숙한 예시로 Array의 sort()와 sorted()를 생각하면 된다.

mutating func sort() -> Void //in-place sort
func sorted() -> [Element] //정렬된 새 배열을 리턴

가령,

let overlaps: Bool = region1.overlaps(region2) //region1과 region2가 겹치는가?
region1.overlap(region2) //region1을 region2와 겹치는 부분으로 mutate
let region3 = region1.overlapping(region2) //region1과 region2가 겹치는 부분만 새로운 region 인스턴스로 리턴

이런 식으로 각각 Bool, mutating, nonmutating 함수의 이름을 지어줘야 가독성을 해치지 않고, 영문법적으로도 올바르고, 가이드에 충실한 네이밍을 할 수 있다.

마무리

정리하자면

  • is 용법
    • is + 명사
    • is + 현재진행(~ing)
    • is + 형용사
    • is + 동사원형 (절대 쓰면 안됨)
  • 조동사 용법
    • can, should, will 등
    • 조동사 + 동사원형
  • has 용법
    • has + 명사
    • has + 과거분사 (is + 과거분사와 의미 거의 동일)
  • 동사원형 용법
    • 3인칭 단수

 

Reference 

https://soojin.ro/blog/naming-boolean-variables

+ Recent posts