Docker에서 태그 검색하기

Docker CLI를 사용할 때 docker search 명령어를 사용하지만 tag 정보까지 보여주진 않습니다.

사용하고자 하는 이미지의 특정 버전(태그)를 이용해서 pull하고 싶을 때 docker registry v2 API를 이용해서 찾을 수 있습니다.

아래 스크립트는 Mac OS X에서 테스트하였습니다.

JQ 설치

curl로 API를 호출하면 결과가 json 포맷으로 나오는데 이를 파싱하기 위해서 jq를 사용합니다.

jq가 설치되어 있지 않다면 brew를 이용해 간단히 설치할 수있습니다.

$ brew install jq

스크립트 생성

CURL로 API를 호출하는 스크립트를 만듭니다.

  #!/usr/local/bin/zsh
  curl -s -S "https://hub.docker.com/v2/repositories/library/$@/tags/" | jq '."results"[]["name"]' |sort

위 스크립트를 적당한 이름 (제 경우는 docker-search-tag)으로 저장합니다.

저는 zsh를 사용하기 때문에 경로가 위와 같습니다. bash를 사용하신다면 #!/usr/bin/bash로 수정하세요

실행해봅니다.

docker hub에 있는 내용과 비교해 봅니다.

코틀린 기본 – 흐름 제어

IF 표현식

코틀린에서 if문은 표현식이다. 따라서 값을 리턴한다. 일반적인 if 구문으로도 가능하기 때문에 3항 연산자(ternary operator : ?)가 없다

// 일반적 용법
var max = a
if (a < b) max = b

// With else
var max: Int
if (a > b) {
    max = a
} else {
    max = b
}

// 표현식
val max = if (a > b) a else b

if 구문이 블럭으로 구성되어 있다면 블럭의 가장 마지막 식이 리턴된다.

val max = if (a > b) {
    print("Choose A")
    a
} else {
    print("Choose B")
    b
}

if문을 선언문(statement)가 아닌 표현식(expression)으로 사용하고자 한다면 반드시 else 블럭이 있어야 한다

WHEN 표현식

when 구문은 C 스타일 언어에서의 switch 구문을 대체한다. 가장 간단한 형태의 용법은 다음과 같다

when (x) {
    1 -> print("x == 1")
    2 -> print("x == 2")
    else -> {
        print("x is neither 1 nor 2")
    }
}

when 구문은 인자에 대해 모든 비교 구문이 만족될 때까지 순차적으로 비교를 진행한다. when 구문은 선언문(statement)와 표현식(expression)형태 모두 사용할 수 있다. 표현식으로 사용될 때는 반족한 비교 구문의 값이 전체 표현식의 값이 되고 선언문 형태로 사용할 때에는 각 비교 구문식의 값은 무시된다

else 구문은 모든 비교 구문을 만족하지 못할 때 실행된다. when 구문이 표현식으로 사용될 때에는 컴파일러가 정의된 구문들이 모든 값을 커버할 수 있다고 판단되지 않으면 else 구문을 반드시 지정해야 한다.

만약 여러 값들이 동일한 방식으로 처리되어야 한다면 비교식에 값을 콤마(,)로 구분하여 나열할 수 있다

when (x) {
    0, 1 -> print("x == 0 or x == 1")
    else -> print("otherwise")
}

비교 구문에는 상수 형태가 아니라 임의의 표현식도 사용할 수 있다

when (x) {
    parseInt(s) -> print("s encodes x")
    else -> print("s does not enocde x")
}

또한 in 혹은 !in을 이용하여 값에 대한 범위 비교도 할 수 있다

when (x) {
    in 1..10 -> print("x is in the range")
    in validNumbers -> print("x is valid")
    !in 10..20 -> print("x is outside the range")
    else -> print("none of the above")
}

또 다른 사용법은 값이 특정한 타입이거나 아닐 경우이다. 코틀린의 스마트 캐스팅 기능 때문에 비교 후 별도의 캐스팅 작업없이 타입의 메서드나 프로퍼티를 사용할 수 있다.

fun hasPrefix(x: Any) = when(x) {
    is String -> x.startsWith("prefix")
    else -> false
}

when 구문은 if-else if 체인을 대체할 수 있다. 인자가 제공되지 않고 비교식이 boolean 표현식이라면 비교 구문이 true일 때 블럭이 실행된다

when {
    x.isOdd() -> print("x is odd")
    x.isEven() -> print("x is even")
    else -> print("x is funny")
}

when구문 문법을 참조

FOR Loop

for 구문은 iterator를 제공하는 어떤 타입의 내용을 순차적으로 탐방한다. 문법은 다음과 같다

for(item in collection) print(item)

바디 부분은 블럭으로 사용할 수 있다

for(item: Int in ints) {
    // ...
}

위에서 언급한대로 for구문은 iterator를 제공하는 어떤 타입에 대해서 사용할 수 있는데

  • iterator() 함수를 멤버 혹은 확장함수로 가지는 타입에 대해 리턴 타입은
    • next() 함수를 멤버 혹은 확장 함수로 가지고
    • Boolean을 리턴하는 hasNext()를 가져야 한다

위 3개의 함수는 operator로 선언되어 있어야 한다

for 루프는 iterator 객체를 지원하지 않는 배열에 대해 index 기반의 루프를 수행한다

만약 배열이나 List 타입에 대해 index값과 함께 탐색하고 싶다면 아래와 같은 방식으로 할 수 있다

for(in in array.indices) {
    print(array[i])
}

혹은 아래와 같은 방식도 가능하다

for((index, value) in array.withIndex()) {
    println("the element at $index is $value")
}

for 구문 문법 참조

While Loop

while 구문과 do..while 구문은 일반적인 형태로 사용할 수 있다

while(x > 0) {
    x--
}

do {
    val y = retrieveData()    
} while(y != null)

while 구문 문법 참조

Break and continue in loops

코틀린은 전통적인 루프 내에서 breakcontinue 키워드를 제공한다

Return and Jump 참조

코틀린 기본 – 패키지

공식 문서 정리 중

소스 코드는 첫 라인에 패키지 선언으로 시작할 수 있다

package foo.bar

fun baz() {}

class Goo {}

소스 파일의 모든 컨텐트 (클래스, 함수 등)은 동일한 패키지에 위치하게 된다. 따라서 위 예제의 baz()foo.bar.baz가 되고 Goofoo.bar.Goo가 된다.

패키지 선언이 되어 있지 않으면 default 패키지에 포함된다

기본 임포트

아래 패키지들은 기본으로 임포트 된다.

  • kotlin.*
  • kotlin.annotation.*
  • kotlin.collection.*
  • kotlin.comparison.* (1.1부터)
  • kotlin.io.*
  • kotlin.ranges.*
  • kotlin.sequences.*
  • kotlin.text.*

대상 플랫폼에 따라 패키지들이 추가적으로 자동 임포트된다

  • JVM
    • java.lang.*
    • kotlin.jvm.*
  • JS
    • kotlin.js.*

임포트

단일 이름으로 임포트

import foo.Bar

스코프 내의 모든 컨텐트를 임포트하기

import foo.*

이름의 중복이 있어서 헷갈리는 경우 as 키워드로 이름을 변경해서 쓸 수 있다.

import foo.Bar
import bar.BAR as bBar

import 키워드는 클래스를 임포트할 때만 국한되어 사용할 수 있는 것은 아니다. import 키워드로 아래 내용 또한 임포트할 수 있다

  • 최상의 레벨의 함수와 프로퍼티
  • object로 선언된 객체의 함수와 프로퍼티
  • 열거형 (enum) 상수

자바와는 다르게 코틀린은 import static 키워드를 지원하지 않는다. 일반적인 import 키워드를 사용해야 한다

최상의 선언의 가시성

최상이 레벨에 선언된 데이터가 private으로 선언되었다면 그 데이터가 선언된 파일 내부에서도 private이 된다

Log 파일 이름 찾기

Logback을 쓰던 중에 로그 파일을 watch하고 싶은데 가져온 로거 객체가 사용하는 파일 이름을 알고 싶어서…

    fun logFileName(): String? {
        val ctx = LoggerFactory.getILoggerFactory() as LoggerContext
        val logger = ctx.loggerList.find { logger.name.equals(it.name) }

        if(logger == null) return null
        else {
            for(appender in logger.iteratorForAppenders()) {
                if(appender is FileAppender) {
                    return appender.file
                }
            }
            return null
        }
    }

해당 이름의 로거 객체가 컨텍스트에 없거나 FileAppender가 아닐 때는 null을 리턴하는 모양새가 보기 좋진 않지만…

Kotlin – 기초(1)

공식 문서 정리 중

기본 타입

Kotlin에서는 모든 것이 객체이고 따라서 모든 변수의 멤버 함수나 프로퍼티를 호출할 수 있다. 몇몇 타입은 내장되어 있어 사용자들에게는 일반적인 클래스들이 있다. 이 섹션에서는 이런 타입의 대부분을 차지하고 있는 numbers, characters, booleans, arrays를 살펴본다

Numbers

Kotlin에서는 숫자들을 Java와 매우 유사하게 다루지만 완전히 동일한 방식은 아니다. 예를 들어 숫자 타입에 대한 암묵적 범위 확장을 지원하지 않고 리터럴도 몇몇 다른 경우가 있다

암묵적 확장 (implicit widening conversion) : int 타입의 변수와 long 타입의 변수를 비교할 때 암묵적으로 int타입의 변수를 long타입으로 변환한 후 비교하게 된다

Kotlin은 아래의 숫자를 표현하는 빌트인 타입을 지원한다 (자바와 매우 유사함)

Type Bit width
Double 64
Float 32
Long 64
Int 32
Short 16
Byte 8

Kotlin에서는 Character는 Number가 아님을 주의하라

상수 리터럴

  • 10진수: 123
    • Long 타입은 뒤에 대문자 L을 붙인다 : 123L
  • 16진수: 0x0F
  • 2진수: 0b00001011

8진수 리터럴은 지원하지 않음

  • Floating point
    • 기본은 Double : 123.5, 123.5e10
    • Float는 F 혹은 f를 뒤에 붙인다 : 123.5f

숫자 리터럴에 언더바 사용하기 (1.1 부터)

숫자 리터럴에 언더바를 사용해서 가독성을 높일 수 있다

val oneMillion = 1_000_000
val creditCardNumber = 1234_5678_9012_3456L
val socialSecurityNumber = 999_99_9999L
val hexBytes = 0xFF_EC_DE_5E
val bytes = 0b11010010_01101001_10010100_10010010

Representation

자바 플랫폼에서 null 레퍼런스를 사용하거나 제네릭을 사용하지 않는다면 숫자는 물리적으로 primtive type으로 저장된다. 후자의 경우 숫자는 boxing된다

숫자의 boxing은 아이덴티티를 반드시 유지(동일한 메모리 주소를 가짐)하는 것은 아니라는 점을 주의하자 ( === 연산자 사용 )

val a: Int = 10000
print(a === a) // Prints 'true'
val boxedA: Int? = a
val anotherBoxedA: Int? = a
print(boxedA === anotherBoxedA) // !!!Prints 'false'!!!

하지만 값은 동일하다 ( == 연산자 사용 )

val a: Int = 10000
print(a == a) // Prints 'true'
val boxedA: Int? = a
val anotherBoxedA: Int? = a
print(boxedA == anotherBoxedA) // !!!Prints 'true'!!!

명시적 변환

서로 다른 표현 방식으로 인해 작은 범위의 타입(이를테면 Int)은 큰 범위 타입(이를테면 Long)의 서브 타입이 아니다. 만약 그랬다면 아래와 같은 코드에서 문제가 발생할 것이다.

// 가상의 코드이다. 실제로는 컴파일되지 않는다
val a: Int? = 1     // boxed Int (java.lang.Integer)
val b: Long? = a    // 암묵적 conversion이 발생하여 boxed Long (java.lang.Long)이 된다
print(a == b)       // false가 출력된다. Long의 equals 메서드는 비교할 인자의 타입으로 Long을 기대하기 때문이다

따라서 아이덴티티 뿐만 아니라 값 비교에서도 문제가 발생한다

결과적으로 작은 범위의 숫자 타입은 큰 범위의 숫자 타입의 섭 타입이 아니다. 이것은 Byte 타입에 대입한 값을 명시적 변환 과정 없이 다시 Int 타입에 대입할 수 없다는 것을 의미한다.

val b: Byte = 1 // OK, literals are checked statically
val i: Int = b // ERROR

// 명시적 변환을 이용한 대입
val i: Int = b.toInt()      // OK

각 숫자 타입은 아래와 같은 명시적 변환을 지원한다

  • toByte()
  • toShort()
  • toInt()
  • toLong()
  • toFloat()
  • toDouble()
  • toChar()

암묵적 변환 과정의 부재는 거의 알아채기 힘든데, 그 이유는 타입은 컨텍스트를 통해 추론되며 수학적 연산은 적당한 과정으로 오버로딩되기 때문이다. 예를 들어

val l = 1L + 3      // Long + Int => Long

연산자

Kotlin 적절한 클래스의 멤버로서 선언된 숫자 타입 데이터들에 대해 표준 연산자 세트를 지원합니다. 연산자 오버로딩을 참조하세요

비트 연산에 대해서 특별한 기호는 없지만 중위 표현(infix notaion)을 사용해서 함수를 호출 할 수 있습니다. 예를 들어

val x = (1 shl 2) and 0x000FF000

아래는 비트 연산 함수들의 목록입니다. (Int 및 Long 타입만 지원)

  • shl(bits) – signed shift left (자바의 &lt;&gt;)
  • ushr(bits) – unsigned shift right (자바의 &gt;&gt;&gt;)
  • and(bits) – bitwise and
  • or(bits) – bitwise or
  • xor(bits) – bitwise xor
  • inv() – bitwise inversion

Character

캐릭터는 Char 타입으로 표현됩니다. Char 타입은 바로 숫자로 처리될 수 없습니다.

fun check(c: Char) {
    if (c == 1) {       // ERROR: incompatible type

    }
}

캐릭터 리터럴은 홑따옴표로 표현됩니다. ('1') 특수 문자는 역슬래쉬로 이스케이프합니다. \t, \b, \n, \r, \', \", \\, \$ 이스케이프 문자를 지원합니다. 다른 캐릭터를 인코딩하기 위해서는 유니코드의 이스케이프 시퀀스 문법을 사용합니다 (\uFF00)

캐릭터는 명시적으로 Int 타입으로 변환할 수 있습니다.

fun decimalDigitValue(c: Char): Int {
    if (c !in '0'..'9') {
        throw IllegalArgumentException("Out of Range")
    }
    return c.toInt() - '0'.toInt()      // 명시적으로 Int 타입으로 변환
}

숫자와 유사하게 캐릭터 역시 nullable 레퍼런스가 필요한 경우 boxing됩니다. boxing 연산 후 서로 다른 아이덴티티(변수의 메모리 주소)를 갖게 됩니다. (Identity is not perseved by boxing operation)

Booleans

booleans는 true/false 두 값만을 가진 Boolean 타입으로 표현됩니다.

Boolean 타입은 nullable 레퍼런스가 필요할 때 boxing 됩니다.

Boolean 타입에 대한 내장 연산자는 다음과 같습니다.

  • || – lazy disjunction
  • &amp;&amp; – lazy conjunction
  • ! – negation

배열

코틀린에서의 배열은 Array타입으로 표현됩니다. get, set, size 메소드를 사용할 수 있고 연산자 오버로딩 컨벤션에 의해 []로 아이템에 접근할 수 있습니다. 다음은 몇 가지 유용한 멤버 함수들입니다.

class Array<T> private constructor() {
    val size: Int
    operator fun set(index: Int): T
    operator fun set(index: Int, value: T): Unit

    operator fun iterator(): Iterator<T>
}

배열을 생성하려면 arrayOf라는 내장 함수에 값들을 넘겨 호출합니다. 따라서 arrayOf(1,2,3)은 배열 [1,2,3]을 생성합니다. 또한 arrayOfNulls 내장 함수를 이용하여 null로 채워진 배열을 생성할 수도 있습니다.

또 다른 방법으로는 배열의 크기와 초기 값을 리턴하는 함수를 인자로 받는 Factory 함수를 사용하는 것입니다.

// ["0", "1", "4", "9", "16"]의 배열을 생성
val as = Array(5, { i -> (i * i).toString() })

위에서 언급한대로 [] 연산자를 이용해 get, set 함수를 대신할 수 있습니다.

주의: 자바와는 다르게 코틀린에서의 배열은 불변(invariant)입니다. 이것은 런타임에 발생할 수 있는 오류를 방지하기 위해서 ArrayArray를 할당할 수 없다는 것을 의미합니다. (그러나 Array를 사용할 수는 있습니다. Type projections를 참조하세요

코틀린은 boxing 부하를 없이 primitive 타입의 배열을 사용하기 위해 몇 가지 특별한 클래스를 제공합니다 – ByteArray, ShortArray, IntArray 등등. 이 클래스들은 Array 클래스와 상속 관계가 아니나 동일한 메서드와 프로퍼티를 가집니다. 각 클래스는 대응되는 팩토리 함수도 가지고 있습니다.

val x: IntArray = intArrayOf(1,2,3)
x[0] = x[1] + x[2]

String

String 타입은 문자열은 표현합니다. String은 불변 객체입니다. 문자열의 각 글자는 []를 이용해서 인덱스로 접근할 수있습니다. for 루프에서 문자열을 사용할 수 있습니다.

for(c in str) {
    println(c)
}

문자열 리터럴

코틀린은 이스케이프 문자가 포함된 이스케이프 문자열과 일반 문자열 2가지 타입의 문자열 리터럴을 지원한다. 이스케이프 문자열은 자바와 매우 흡사하다

val s = "Hello, world!\n"

이스케이프 처리는 역슬래시를 이용한 일반적인 방법으로 이루어진다. 캐릭터 페이지에서 지원하는 이스케이프 문자를 확인할 수 있다

raw 문자열은 쌍따옴표 세개를 사용해서 구분하는데 이스케이프할 수 없고 개행 문자 및 다른 일반 문자들을 포함할 수 없다

val text = """
    for (c in "foo")
        print(c)
"""

맨 앞의 공백을 trimMargin() 함수로 제거할 수 있다

val text = """
    |Tell me and I forget.
    |Teach me and I remember.
    |Involve me and I learn.
    |(Benjamin Franklin)
    """.trimMargin()

margin prefix의 기본 문자는 |이지만 trimMargin("&gt;")처럼 다른 문자를 파라미터로 전달해서 사용할 수도 있다

문자열 템플릿

문자열은 템플릿 표현식을 가질 수 있다. 템플릿 표현식은 $문자로 시작되고 단순 이름을 포함한다

val i = 10
val s = "i = $i"         // i = 10으로 평가됨

혹은 중괄호 {}를 사용해서 연산 표현식을 사용할 수도 있다

val s = "abc"
val str = "$s.length is ${s.length}     // abc.length is 3

템플릿 표현식은 raw 문자열과 이스케이프된 문자열에서 모두 사용할 수 있다. $문자열 그대로를 raw 문자열에서 사용하고 싶다면 (\는 지원하지 않으므로) 다음처럼 사용할 수 있다

val price = """
${'$'}9.99
"""

Kotlin – 시작하기

공식 문서 요약/번역/정리 중

기본 문법

패키지 정의하기

패키지 정의는 소스 코드 맨 윗줄에 적는다. 패키지 경로와 소스 코드의 파일 경로는 꼭 일치하지 않아도 된다

package my.demo

import java.util.*

//...

함수 정의 하기

// Int 타입의 a, b 변수를 인자로 받아서 Int 타입을 리턴하는 함수의 정의
fun sum(a: Int, b:Int): Int {
    return a + b
}

// 리턴 타입의 추론
fun sum(a:Int, b:Int) = a + b

// Void == Unit, Unit은 생략할 수 있다
fun printSumb(a: Int, b: Int):Unit {
    print("sum of $a and $b is ${a+b}")
}

지역 변수의 선언

// val -> Immutable, var -> Mutable
val a: Int = 1
var b = 2       // Int 타입 추론
val c: Int      // val로 선언했는데 바로 초기화하지 않음 -> 오류

String template

var a = 1
val s1 = "a is $a"
a = 2
val s2 = "${s1.replace("is", "was")}, but now is $a"

Conditional expression

fun maxOf(a:Int, b:Int):Int {
    if(a>b) return a
    else return b
}

fun maxOf(a:Int, b:Int) = if(a>b) a else b

null

명시적으로 null 허용이라고 마킹을 해줘야 null을 assign할 수 있다

//문자열 str에 integer값이 포함되어 있지 않으면 null을 리턴하는 함수
fun parseInt(str: String): Int? {
    ...
}

함수에서 어떤 값도 리턴하지 않으면 null을 리턴한다

타입 체크와 자동 캐스팅

is 연산자 -> 특정 타입의 인스턴스인가?

immutable의 지역 변수가 어떤 타입인지 체크를 했다면 명시적으로 캐스팅할 필요가 없다

fun getStringLength(obj: Any): Int? {
    if(obj is String) {
        // 앞에서 String 타입임을 체크했기 때문에 
        // obj를 명시적으로 String으로 cast할 필요가 없다
        return obj.length;      
    }
    // if 블럭을 벗어났기 때문에 이 지점에서 obj의 타입은 Any다
    return obj
}

// 혹은...

fun getStringLength(obj: Any):Int? {
    if(obj !is String) return null

    // obj가 자동으로 String 타입으로 casting된다
    return obj.length
}

// 심지어는...
fun getStringLength(obj: Any):Int? {
    // 역시나 if 블럭에서는 obj가 String으로 자동 캐스팅된다
    if(obj is String && obj.length > 0) {
        return obj.length
    }
    return null
}

for loop

val items = listOf("apple", "banana", "kiwi")
for(item in items) {
    println(item)
}

// 또는

val items = listOf("apple", "banana", "kiwi")
for(index in items.indices) {
    println("item at $index is ${item[index]}")
}

while loop

// while loop
val items = listOf("apple", "banana", "kiwi")
var index = 0
while(index < item.size) {
    println("item at $index is ${item[index]}")
    index++
}

when expression

fun describe(obj: Any): String =
    when(obj) {
        1 -> "one"
        "Hello" -> "Greeting"
        is Long -> "Long"
        !is String -> "Not a string"
        else -> "Unknown"
    }

ranges

//어떤 숫자가 특정 범위에 포함되어 있는지 체크
val x = 10
val y = 9
if(x in 1..y+1) {           // x가 1 ~ y+1 사이의 숫자이면
    println("fit in range")
}

// 숫자가 범위에 포함이 안되면
val list = listOf("a", "b", "c")
if(-1 !in 0..list.lastIndex) {
    println("-1 is out of range")
}
if(list.size !in list.indices) {
    println("list size is out of valid list indices range too")
}

// for loop using range
for(x in 1..5) {
    print(x)        // print 1,2,3,4,5
}

// step
for(x in 1..10 step 2) {
    println(x)      // print 1,3,5,7,9
}

collections

// iteration
for(item in items) {
    println(item)
}

// collection에 아이템이 있는지 체크
when {
    "orange" in items -> println("juicy")
    "apple" in items -> println("apple is fine too")
}

// lambda function을 이용한 collection의 map과 filter
// it 라는 변수로 접근 가능
fruits
    .filter { it.startsWith("a") }
    .sortedBy { it }
    .map { it.toUpperCase() }
    .forEach { println(it) }

Idioms

DTO 생성 (data class)

data class Customer(val name: String, val email: String)

data 클래스는 다음과 같은 기능을 제공한다

  • 모든 프로퍼티에 대한 getters (var로 선언된 변수의 경우 setter도 제공)
  • equals()
  • hashCode()
  • toString()
  • copy()
  • 모든 프로퍼티에 대해 component1(), component2()

함수 파라미터의 기본 값

    fun foo(a: Int = 0, b: String = "") {
        ...
    }

리스트 필터

    val positives = list.filter { x -> x > 0}

    // 혹은 더 간단히

    val positives = list.filter { it > 0 }

문자열 인터폴레이션

    val name = "..."
    println("Name $name")

인스턴스 체크

    when(x) {
        is Foo -> ...
        is Bar -> ...
        else -> ...
    }

맵/리스트의 요소 탐색

    for((k, v) in map) {
        println("$k -> $v")
    }

Range

    for(i in 1..100) {...}          // closed range: 100 포함
    for(i in 1 until 100) {...}     // half open: 100 불포함
    for(x in 2..10 step 2) {...}
    for(x in 10 downTo 1) {...}
    if(x in 1..10) {...}

읽기 전용 리스트

    val list = listOf("a", "b", "c")

읽기 전용 맵

    val amp = mapOf("a" to 1, "b" to 2, "c" to 3)

맵에 접근

    println(map['key'])
    map['key'] = value

지연 초기화

    val p: String by lazy {
        ...초기화 연산
    }

확장 함수

    fun String.spaceToCamelCase() {
        ...
    }

    "Convert this to camelcase".sapceToCamelCase()

싱글톤

    object Resource {
        val name = "Name"
    }

if not null

    val files = File("Test").listFiles()
    println(files?.size)

if not null else

    val files = File("Test").listFiles()
    println(files?.size ?: "empty")

not null이면 실행(1)

    val data = ...
    val email = data["email"] ?: throw IllgalStateException("Email is missing!")

not null이면 실행(2)

    val data = ...
    data?.let {
        ... 실행할 코드
    }

when을 이용한 리턴

    fun transform(color: String): Int {
        return when(color) {
            "Red" -> 0
            "Green" -> 1
            "Blue" -> 2
            else -> throw IllegalArgumentException("Invalid color param")
        }
    }

try / catch 표현

    fun test() {
        val result = try {
            count()
        } cath(e: ArithmeticException) {
            throw IllegalArgumentException(e)
        }

        ... working with result
    }

if 표현식

    fun foo(param: Int) {
        val result = if(param == 1) {
            "one"
        } else if(param == 2) {
            "two"
        } else {
            "three"
        }
    }

Unit을 리턴하는 메서드의 빌더 스타일의 사용

    fun arrayOfMinusOnes(size: Int): IntArray {
        return IntArray(size).apply { fill(-1) }
    }

한 줄짜리 함수 표현

    fun theAnswer() = 42

    // 같은 함수
    fun theAnswer():Int = {
        return 42
    }

이 방식은 when 표현식 등과 함께 사용되어 더욱 효과적으로 사용할 수 있다

fun transformer(color: String): Int = when(color) {
    "Red" -> 0
    "Green" -> 1
    "Blue" -> 2
    else -> throw IllegalArgumentException("Invalid color param")
}

하나의 객체에 대해서 복수의 메서드를 호출하기 (with)

class Turtle {
    fun penDown()
    fun penUP()
    fun turn(degrees: Double)
    fun forward(pixels: Double)
}
val myTurtle = Turtle()
with(myTurtle) {
    penDown()
    for(i in 1..4) {
        forward(100.0)
        turn(90.0)
    }
    penUp()
}

Java 7의 try with resources

val stream = Files.newInputStream(Paths.get("/some/file.txt"))
stream.buffered().reader().use {
    reader -> println(reader.readText())
}

Generic 함수

//  public final class Gson {
//     ...
//     public <T> T fromJson(JsonElement json, Class<T> classOfT) throws JsonSyntaxException {
//     ...

inline fun <reified T: Any> Gson.fromJson(json): T = this.fromJson(json, T::class.java)

Nullable 변수의 이용

val b: Boolean? = ...           // b -> Nullable
if(b == true) {
    ...
} else {
    ... b가 null이거나 false일 때
}

Coding convention

Kotlin의 코딩 스타일에 대한 내용

Naming style

확실하지 않은 것은 기본적으로 자바의 코딩 스타일을 따른다. 예를 들면

  • camelCase 사용 (언더바 사용 지양)
  • 타입(클래스, 인터페이스)는 대문자로 시작한다
  • 메서드와 프로퍼티는 소문자로 시작한다
  • indent는 스페이스 4칸
  • 공개 함수는 Kotlin Doc등의 포맷으로 문서화 해야한다

콜론

타입과 슈퍼 타입을 구분하는 콜론에는 콜론 앞에 스페이스를 포함시키고 인스턴스와 타입을 구분하는 콜론에는 콜론 앞에 스페이스를 넣지 않는다

interface Foo<out T : Any> : Bar {      // T : Any, _: Bar
    fun foo(a: Int): T                  // a: Int
}

람다

람다 함수를 사용할 때 curly brace ({}) 앞 뒤로 스페이스를 사용하고 마찬가지로 람다 함수의 파라미터와 함수 본문을 구분하는 화살표에도 앞뒤로 스페이스를 사용한다. 필요하다면 언제라도 람다 함수는 괄호 밖으로 전달될 수 있어야 한다.

list.filter { it > 10 }.map { element -> element * 2 }

람다 함수가 짧고 중첩된 구조가 아니라면 파라미터 변수를 명확하게 선언하는 것 보다는 it 컨벤션을 따르는 것을 추천한다. 그러나 중첩된 람다 함수 내에서는 파라미터 변수의 이름을 명시적으로 사용해야 한다

클래스 헤더 포맷

인지가 많지 않은 클래스의 선언은 한 줄로 한다

class Person(id: Int, name: String)

긴 클래스 헤더는 포맷팅을 통해 primary constructor의 인자들이 줄바꿈된 상태로 사용해야 한다. 또한 닫는 괄호 역시 줄바꿈해서 입력한다. 만약 상속을 사용한 경우라면 부모 클래스의 생성자나 인터페이스의 이름을 닫는 괄호와 같은 줄에 적는다.

class Person(
    id: Int, 
    name: String,
    surname: String
) : Human(id, name) {
    // ...
}

여러 개의 인터페이스를 구현하는 클래스의 경우는 각 인터페이스 이름을 줄바꿈해서 입력한다

class Person(
    id: Int, 
    name: String,
    surname: String
) : Human(id, name),
    KotlinMaker {
    // ...
}

생성자의 인자들은 한 줄로 사용하거나 줄바꿈해서 사용할 수 있다

Unit

함수가 Unit을 리턴한다면 함수 정의에 리턴 타입을 생략할 수 있다

fun foo() {     // :Unit이 생략됨

}

함수 vs 프로퍼티

몇몇 경우에 인자 없는 함수는 읽기 전용 프로퍼티와 호환 가능하다. 의미는 유사하지만 때에 따른 스타일적인 선호가 존재한다

다음과 같은 경우 함수보다 프로퍼티를 사용하는 것이 선호된다

  • 예외를 던지지 않는 경우
  • O(1) 복잡도를 갖는 경우
  • 계산 비용이 낮을 때 (혹은 첫 번째 실행 결과를 캐시되었을 때)
  • 여러 번 호출해도 동일한 값을 리턴할 때

Redis / Redis-Commander

AWS의 Elasticache를 redis엔진으로 이용할 때 로컬에서 테스트할 redis 인스턴스를 설치하기로 했다

Elasticache를 redis로 이용할 때 AWS에서 지원하는 redis 버전이 여러 가지가 있는데 가장 최신 버전인 3.2.4를 이용할 것인데 docker hub에는 정확히 3.2.4 버전은 없기 때문에 3.2를 설치하였다

우선 docker redis 공식 이미지 중 3.2 버전으로 태그된 이미지를 다운받고 6379포트(redis 기본 통신 포트)를 연결한다

$ docker pull redis:3.2
$ docker run -d -p 6532:6532 redis:3.2

그 다음에 cradle dependency에 redis 클라이언트 라이브러리(Jedis)를 추가한다. 기반 프레임워크가 spring이기 때문에 spring-data-redis 라이브러리도 추가한다

dependencies {
    compile group: 'org.springframework.data', name: 'spring-data-redis', version: '1.8.3.RELEASE'
    compile group: 'redis.clients', name: 'jedis', version: '2.9.0'
}

간단하게 Jedis로 연결 테스트를 해본다

/**
 * Elasticache 테스트
 */
public class CacheTest {

    @Test
    public void jedis() {
        Jedis jedis = new Jedis(&quot;localhost&quot;);
        jedis.set(&quot;Hello&quot;, &quot;World&quot;);

        Debug.line(jedis.get(&quot;Hello&quot;));
    }

}

잘 연결이 된듯 하다

[====================, [World]] by net.ion.airkjh.aws.CacheTest:jedis[2017/06/04-09:02:07]

Process finished with exit code 0

Redis에 어떤 데이터들이 있는지 보고 싶었다. Web 기반 UI 툴을 찾다가 nodejs로 작성된 redis-commander를 찾았다

NPM으로 간단히 설치할 수 있다

$ npm install -g redis-commander
$ redis-commander

redis-commander는 8081 포트를 기본으로 사용한다. 브라우저로 들어가서 확인해 보자

관련 링크

https://redislabs.com/blog/so-youre-looking-for-the-redis-gui/
https://hub.docker.com/_/redis/