본문 바로가기

Development/Golang

[Effective Go] Control Structures

Golang에서의 제어구문(Control Structures)은 C와 굉장히 밀접하면서도, 다르다. do, while 반복문은 사용하지 않고, for 만을 사용한다. switch는 좀 더 유연히 사용된다. switch를 포함해 select와 같은 multiway communications mulitplexer를 제공하기도 한다.

If

 Golang에서 if 는 아래와 같이 사용된다.

if x > 0 {
	return y
}

 if 와 switch 에서 지역 변수에 대한 초기화 구문을 허용한다.

if err := file.Chmod(0644); err != nil {
	log.Print(err)
	return err
}

 Go Libraries에서, if 구문이 다음의 명령문(코드)로 수행되지 않게 사용하는 스타일을 많이 보게 될 것이다. (개인적으로도 이런 스타일의 코드를 좋아한다.) if 문의 body는 보통 break, continue, goto 또는 return으로 끝나는 경우가 많다. 불필요한 else는 사용하지 않도록...

f, err := os.Open(name)
if err != nil {
	return err
}
d, err := f.Stat()
if err != nil {
	f.Close()
	return err
}
codeUsing(f, d)

Redelcaration and Reassignment

 직전의 예시 구문에서 := 를 사용해 변수를 선언했다.

f, err := os.Open(name)

 이 구문은 두개의 변수(f, err)를 선언했다. 그리고 몇줄 뒤에 아래와 같이 d와 err를 선언한다.

d, err := f.Stat()

 :=(Short Declaration) 선언에서 변수는 이미 선언된 경우에도 나타날 수 있다. 이 때, err은 두개의 구문에서 같이 선언된 것을 알 수 있다. 이러한 중복은 Golang에서 허용되는 형식이다. err은 첫번째 구문에서 선언되었으나, 두번째 구문에서 re-assigned되었다. 조금 더 상세하게 말하면, err 변수가 f.Stat() 에서 먼저 선언되었고, 두번째 구문에서는 단순히 새로운 값을 할당받은 것이다.

  1. 이 선언은 기존에 선언한 f와 동일한 scope에 포함되어 있다. (만약 f가 이미 외부 번위에서 선언된 경우라면 새 변수를 생성한다.)
  2. 초기화에 따른 값은 f에 할당할 수 있다.
  3. 선언에 의해 생성된 다른 변수가 하나 이상 존재할 수 있다.

 이러한 Golang의 속성은 순수한 실용주의에서 비롯되었다고 한다. Golang에서 함수의 매개변수 및 반환 값의 scope는 function body로 제한된다. (함수 외부에서 동일한 명칭으로 나타나더라도)

For

 Golang에서의 For 반복문은 C와 비슷하지만 다르다. C에서의 for, while 을 통합한 형태이고, do-while 은 더이상 사용하지 않는다. 아래 3가지 형식이 있다.

// Like a C for
for init; condition; post { }

// Like a C while
for condition { }

// Like a C for(;;)
for { }

 Short Declaration을 이용해 반복문의 index 변수를 간단히 선언해 아래처럼 사용할 수도 있다.

sum := 0
for i := 0; i < 10; i++ {
	sum += i
}

 array, slice, string, map 또는 channel로부터의 reading에서 range 구문을 이용할 수도 있다.

for key, value := range oldMap {
	newMap[key] = value
}

 key(첫번째 변수)만 필요하다면, value(두번째 변수)는 생략해도 된다.

for key := range m {
	if key.expired() {
		delete(m, key)
	}
}

 반대로 value(두번째 변수)만 필요하다면 blank identifier(_)을 이용해 사용하지 않도록 선언할 수 있다.

sum := 0
for _, value := range array {
	sum += value
}

 문자열의 경우 range 구문은 굉장히 유용하다. Golang에서는 String은 (기본 charset으로) UTF-8을 사용해 파싱하고, range를 사용하면 개별 Unicode code size 단위로 분리하게 된다. 인코딩에 문제가 있다면 한 바이트씩 떼네어 rune 형식으로 전환한다.(rune은 U+FFFD Golang에서 사용하는 기본 제공 type이다.)

for pos, char := range "日本\\x80語" { // \\x80 is an illegal UTF-8 encoding
	fmt.Printf("character %#U starts at byte position %d\\n", char, pos)
}

---

character U+65E5 '日' starts at byte position 0
character U+672C '本' starts at byte position 3
character U+FFFD '�' starts at byte position 6
character U+8A9E '語' starts at byte position 7

 Golang에서는 다른 언어에서 흔히 사용하는 ++ 와 -- 는 연산이 아닌 구문의 형식으로 제공한다. 만약 여러 변수를 for에서 사용하고 싶다면 아래처럼 병렬로 할당하면 된다.

// Reverse a
for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 {
	a[i], a[j] = a[j], a[i]
}

Switch

 Golang의 switch 구문에서는 조건의 표현식이 상수나 정수일 필요가 없고, 구문의 top-down으로 매칭하게 된다. 만약 switch 에 값이 없다면 true로 판단하게 된다. 이를 이용해 if-else-if-else 체인을 switch로 작성하는 것이 가능하다.

func unhex(c byte) byte {
	switch {
	case '0' <= c && c <= '9':
		return c - '0'
	case 'a' <= c && c <= 'f':
		return c - 'a' + 10
	case 'A' <= c && c <= 'F':
		return c - 'A' + 10
	}
	return 0
}

 또한 조건을 하나씩 나열할 필요 없이 comma-separated list형식으로 표현 가능하다.

func shouldEscape(c byte) bool {
	switch c {
	case ' ', '?', '&', '=', '#', '+', '%':
		return true
	}
	return false
}

 C와 다르게 switch 내부에서 break구문은 사용하지 않아도 된다. 간혹 반복문 내부에서 반복문 자체를 break하기 위해서라면 아래와 같이 사용 가능하다. 물론 loop 내부에서 continue도 사용 가능하다.

for n := 0; n < len(src); n += size {
	switch {
	case src[n] < sizeOne:
		if validateOnly {
			break
		}
		size = 1
		update(src[n])

	case src[n] < sizeTwo:
		if n+1 >= len(src) {
			err = errShortInput
			break Loop
		}
		if validateOnly {
			break
		}
		size = 2
		update(src[n] + src[n+1]<<shift)
	}
}
// Compare returns an integer comparing the two byte slices,
// lexicographically.
// The result will be 0 if a == b, -1 if a < b, and +1 if a > b
func Compare(a, b []byte) int {
	for i := 0; i < len(a) && i < len(b); i++ {
		switch {
		case a[i] > b[i]:
			return 1
		case a[i] < b[i]:
			return -1
		}
	}
	switch {
	case len(a) > len(b):
		return 1
	case len(a) < len(b):
		return -1
	}
	return 0
}

Type Switch

 switch 는 interface 변수의 동적 타입을 판단하기 위해 사용되기도 한다.

var t interface{}
t = functionOfSomeType()
switch t := t.(type) {
default:
	fmt.Printf("unexpected type %T\\n", t)     // %T prints whatever type t has
case bool:
	fmt.Printf("boolean %t\\n", t)             // t has type bool
case int:
	fmt.Printf("integer %d\\n", t)             // t has type int
case *bool:
	fmt.Printf("pointer to boolean %t\\n", *t) // t has type *bool
case *int:
	fmt.Printf("pointer to integer %d\\n", *t) // t has type *int
}

'Development > Golang' 카테고리의 다른 글

[Effective Go] functions  (0) 2021.11.30
[Effective Go] Names  (0) 2021.11.20