[The Go Programming Language] 2장 프로그램 구조 - 2.7 범위

in #kr-dev6 years ago

modolee_logo
안녕하세요. 개발자 모도리입니다.
The Go Programming Language 라는 책으로 Go를 공부하고 있으며, 해당 책의 내용을 요약 정리해서 올리려고 합니다. 저는 번역본을 구매해서 공부하고 있습니다.
게시물에 예제코드 라고 나오는 것들은 https://github.com/modolee/tgpl.git 에서 다운 받으실 수 있습니다.

지난 게시물


2장 프로그램 구조

2.7 범위

선언과 수명

  • 선언의 범위는 소스코드 안에서 선언된 이름으로 해당 선언을 참조할 수 있는 구역입니다.
  • 선언의 범위는 프로그램 텍스트의 영역이며, 컴파일 시의 속성입니다.
  • 수명은 실행 중에 변수가 프로그램의 다른 구역에서 참조할 수 있는 시간의 범위입니다.
  • 변수의 수명은 실행 시의 속성입니다.

예제

내부 선언이 외부 선언을 가리는 경우

func f() {}

var g = "g"

func main() {
  f := "f"
  fmt.Println(f) // "f"; 지역 변수 f가 패키지 수준 함수 f를 가린다.
  fmt.Println(g) // "g"; 패키지 수준 변수
  fmt.Println(h) // 컴파일 오류; 선언되지 않음: h
}

각기 다른 어휘 블록에 동일한 이름의 변수가 선언되어 있는 경우

func main() {
  x := "hello!"
  for i := 0; i < len(x); i++ {
    x := x[i]
    if x != '!' {
      x := x + 'A' - 'a'
      fmt.Printf("%c", x) // "HELLO" (반복마다 글자 1개)
    }
  }
}
  • 서로 다른 x가 세 개 사용되고 있습니다.
  • x[i], x + 'A' - 'a'는 각각 다른 외부 x 선언을 참조합니다.

묵시적인 블록 생성

if x := f(); x == 0 {
  fmt.Println(x)
} else if y := g(x); x == y {
  fmt.Println(x, y)
} else {
  fmt.Println(x, y)
}
fmt.Println(x,y) // 컴파일 오류: 여기서는 x, y를 볼 수 없습니다.
  • 두번 째 if문은 첫 번째 if문과 중첩돼 있으므로 첫 번째 문장의 초기화 과정에서 선언된 변수는 두 번째 문장에서도 볼 수 있습니다.

if 블록에서 오류 처리 시

if f, err := os.Open(fname); err != nil { // 컴파일 오류: 사용되지 않음: f
  return err
}
f.Stat() // 컴파일 오류: 정의되지 않은 f
f.Close() // 컴파일 오류: 정의되지 않은 f
  • f의 범위는 if문에 한정되므로 이후 문장에서는 f에 접근할 수 없어서 컴파일 오류가 발생합니다.
  • 컴파일러에 따라 지역 변수 f가 사용되지 않았다는 추가 오류 보고를 받을 수도 있습니다.
// f를 조건문 전에 선언해서 이후에 이용할 수 있게 합니다.
f, err := os.Open(fname)
if err != nil {
  return err
}
f.Stat()
f.Close()
// Stat와 Close 함수 호출을 else 블록 안으로 옮겨서 f와 err를 외부 블록에 선언하지 않습니다.
if f, err := os.Open(fname); err != nil {
  return err
} else {
  // f와 err는 여기서도 볼 수 있습니다.
  f.Stat()
  f.Close()
}

짧은 변수 선언에서의 범위

  • init 함수에서 현재 디렉토리를 가져와서 패키지 수준의 변수를 업데이트 하는 프로그램을 작성하려고 합니다.
var cwd string

func init() {
  cwd, err := os.Getwd() // 컴파일 오류: 사용되지 않음: cwd
  if err != nil {
    log.Fatalf("os.Getwd failed: %v", err)
  }
}
  • init 함수의 블록에서는 사전에 선언된 cwderr가 없으므로 := 구문이 둘 다 지역 변수로 선언합니다.
  • cwd의 내부 선언이 외부 선언으로의 접근을 막기 때문에 이 문장은 의도대로 패키지 수준의 cwd 변수를 갱신하지 않습니다.
var cwd string

func init() {
  cwd, err := os.Getwd() // NOTE: 오류!
  if err != nil {
    log.Fatalf("os.Getwd failed: %v" err)
  }
  log.Printf("Working directory = %s", cwd)
}
  • 첫번째 코드에서 cwd를 사용하지 않기 때문에 컴파일 오류가 발생했는데, 두번째 코드 처럼 cwd 변수를 사용하는 코드가 포함되어 있다면 어디에서 문제가 발생하는지 찾기 어려울 수 있습니다.
var cwd string
func init() {
  var err error
  cwd, err = os.Getwd()
  if err != nil {
    log.Fatalf("os.Getwd failed: %v", err)
  }
}
  • 이런 잠재적인 문제를 해결하는 방법은 여러가지가 있는데, 가장 직접적인 방법은 세번째 코드와 같이 err를 별도의 var 선언으로 분리하고 :=를 사용하지 않는 것입니다.

이어보기

3.1 장 포스팅 후 추가 예정

Sort:  

@modolee, I gave you a vote!
If you follow me, I will also follow you in return!