트랙
/
Crystal
Crystal
/
연습 문제
/
비밀번호 잠금
비밀번호 잠금

비밀번호 잠금

학습 연습 문제

소개

Crystal에서는 변수가 여러 타입으로 이루어질 수 있어요. 이것을 유니언 타입이라고 해요. Crystal에서는 컴파일러가 유니언 타입을 추론하는 경우가 흔해요.

Note

유니언 타입은 여러 타입으로 이루어져 있어도 런타임에서는 여전히 하나의 타입이에요. 즉, 유니언 타입이 String과 Int32로 이루어져 있다면 동시에 둘 다가 되는 것이 아니에요. 대신 String이거나 Int32 중 하나가 돼요.

유니언 타입은 타입들을 파이프(|)로 구분해서 선언해요. 보통 괄호 안에 넣지만, 꼭 그럴 필요는 없어요. 가장 흔한 유니언 타입은 T가 어떤 타입일 때 (T | Nil)이고, Nil을 반환할 수 있는 메서드에서 나타날 수 있어요. 이것은 변수가 nilable하다고도 해요.

foo : String = "Hello"
foo = nil # Error: type must be String, not (String | Nil)

foo : (String | Nil) = "Hello"
foo = nil

타입이 꼭 두 개로 제한되는 것은 아니고, 원하는 만큼 많아도 돼요.

foo : (String | Int32 | Nil | Float64) = "Hello"
foo = 1
foo = nil
foo = 1.0

typeof와 Object#class

변수의 타입을 알아내는 방법은 두 가지가 있어요. typeof를 사용하는 방법과 Object#class를 사용하는 방법이에요. 차이점은 typeof는 컴파일 타임에 변수의 타입을 반환하고, Object#class는 런타임에 타입을 반환한다는 거예요. 즉, 변수가 유니언 타입인지 확인하고 싶을 때 Object#class는 그것을 알려줄 수 없어요. 런타임에는 단일 타입인 타입만 반환하기 때문이에요.

foo = 0 == 0 ? "a" : 1
typeof(foo) # => (String | Int32)
foo.class # => String

유니언 타입의 연산

유니언 타입은 런타임에서는 단일 타입이기 때문에, 모든 일반적인 연산이 동작해요. 하지만 코드를 컴파일할 때 컴파일러는 그것이 어떤 타입인지 알아야 해요. 따라서 타입별 연산을 사용하려면, 코드가 해당 타입 중 하나만 될 수 있는 방식으로 작성되어야 해요.

foo : (String | Int32) = "Hello"
foo.downcase # Error: undefined method 'downcase' for (String | Int32)

Crystal에는 유니언 타입을 위한 특별한 메서드가 있어요. 바로 is_a? 메서드로, 타입을 인자로 받아 불리언을 반환해요. nil? 메서드는 is_a?(Nil)의 축약형이에요. is_a? 메서드를 제어 표현식에 넣으면 컴파일러에게 어떤 타입인지 알려주게 되고, 따라서 그것이 그 타입임을 보장해요. 그리고 else 분기에서는 그것이 그 타입이 아니라는 것이 보장돼요.

foo : (String | Int32) = "Hello"
if foo.is_a?(String)
  typeof(foo)  # => String
  foo.downcase # => "hello"
end

이 is_a?는 인자로 단일 타입만 받는 것으로 제한되지 않고, 유니언 타입도 받을 수 있어요. 여러 타입에 대해 &&와 함께 사용할 수도 있어요.

Note

is_a? 메서드는 제어 표현식과 함께 사용할 때 인스턴스 변수나 클래스 변수에는 쓸 수 없어요. 대신 이런 것들은 먼저 지역 변수에 할당해야 해요.

as

유니언 타입을 단일 타입으로 만드는 한 가지 방법은, 특정 타입일 때만 분기에 들어갈 수 있게 만드는 거예요. 또 다른 방법은 as 메서드를 사용하는 거예요. 이 메서드는 런타임 검사를 통해 유니언 타입을 단일 타입으로 만들어요. 타입이 기대한 타입이 아니면 예외가 발생해요.

foo : String | Int32 = "Hello"
foo.as(String).downcase # => "hello"

foo.as(Int32) # Error: can't cast String to Int32
Caution

이 방법은 타입이 기대한 타입이라고 확신할 때, 또는 그렇지 않을 때 예외를 발생시키고 싶을 때만 쓰는 거예요.

이 방법을 잘못된 설정과 함께 사용하면 예상치 못한 동작이 생길 수 있어요.

as?

as?는 as와 아주 비슷하게 동작하지만, 타입이 기대한 타입이 아니면 예외를 발생시키는 대신 nil을 반환해요. 즉, 기대한 타입과 Nil의 유니언 타입을 반환해요.

foo : (String | Int32) = "Hello"
foo.as?(String).downcase # => "hello"

foo.as?(Int32) # => nil

nilable 축약형

nilable은 변수가 어떤 타입이거나 Nil일 수 있다는 뜻이에요. 이것은 (T | Nil)로 쓸 수 있어요. 그런데 nilable 타입은 비교적 흔하기 때문에, T?라는 축약형이 있어요.

# This:
foo : (String | Nil) = "Hello"
foo = nil

# Is the same as:
foo : String? = "Hello"
foo = nil

지침

여러분이 일하는 회사에서 곧 Smarty 5라는 새로운 스마트폰을 출시하려고 해요. 이 스마트폰에는 완전히 새로운 카메라 시스템과 새로운 디자인, 그리고 smartyOS라는 새로운 운영 체제에서 동작하는 혁신적인 새 프로세서가 들어 있어요.

새 프로세서는 그 어느 때보다 안전한 비밀번호를 처리할 수 있는 성능을 갖추고 있어서, 회사는 이를 활용하기로 했어요. 그래서 회사는 사용자가 비밀번호를 설정하고, 입력한 비밀번호가 올바른지 확인할 수 있는 휴대폰용 비밀번호 잠금 시스템을 구현해 달라고 요청했어요.

Caution

이 연습 문제에서 다루는 비밀번호 시스템은 안전하지 않으며, 교육 목적으로만 사용해요. 실제 애플리케이션에는 절대 사용하면 안 돼요.

1. 비밀번호 설정하기

이 휴대폰에서는 단순한 숫자 비밀번호부터 더 안전한 영숫자 비밀번호, 심지어 지문을 저장하는 비밀번호까지 여러 가지 유형의 비밀번호를 사용할 수 있어요.

이 비밀번호들은 각각 다른 타입을 사용해요. 숫자 비밀번호는 Int32, 영숫자 비밀번호는 String, 지문 비밀번호는 Float64를 사용해요.

세 가지 유형의 비밀번호 중 하나를 인자로 받아 @password라는 인스턴스 변수에 저장하는 초기화 메서드를 가진 PasswordLock 클래스를 구현해요.

password_lock = PasswordLock.new(1234)
# => #<PasswordLock:0x7f8e1b8c0b80 @password=1234>

2. 비밀번호 암호화하기

회사는 비밀번호가 평문으로 저장되지 않도록 암호화하기로 했고, 이를 위한 메서드를 구현해 달라고 요청했어요.

비밀번호 유형마다 고유한 암호화 방식이 있어요.

  • Int32 비밀번호: 비밀번호를 2로 나누고 가장 가까운 정수로 반올림해요.
  • String 비밀번호: 비밀번호를 뒤집어요.
  • Float64 비밀번호: 비밀번호에 4를 곱해요.

인자를 받지 않고 @password 인스턴스 변수를 암호화된 값으로 수정하는 encrypt 인스턴스 메서드를 구현해요.

password_lock = PasswordLock.new(1234)
# => #<PasswordLock:0x7f8e1b8c0b80 @password=1234>

password_lock.encrypt
# => #<PasswordLock:0x7f8e1b8c0b80 @password=617>

3. 비밀번호가 올바른지 확인하기

회사는 주어진 비밀번호가 올바른지 확인하는 메서드도 구현해 달라고 요청했어요.

비밀번호가 틀리면 nil을, 올바르면 "Unlocked"를 반환하는 메서드를 원해요.

확인할 비밀번호를 인자로 받는 unlock? 인스턴스 메서드를 구현해요. 이 메서드는 비밀번호가 틀리면 nil을, 올바르면 "Unlocked"를 반환해야 해요.

password_lock = PasswordLock.new(1234)
password_lock.encrypt
password_lock.unlock?(1234)
# => "Unlocked"
GitHub에서 편집 링크가 새 창이나 탭에서 열려요
Crystal Exercism

비밀번호 잠금 문제를 시작해 볼 준비가 됐나요?

Exercism에 가입하고 Crystal 트랙을 개념 26개연습 문제 142개, 그리고 실제 사람의 멘토링과 함께 배우고 익혀 보세요. 모두 무료예요.