본문 바로가기
Programming/Golang

[golang/err] import cycle not allowed

by 라바킴 2019. 4. 6.

#현상

Cross-refering packages -> 각 패키지들이 상호 참조 (순환 종속)

  • package 간 종속성이 생겨버리면 단위 테스트 하기도 어려워지고
  • 한 package가 변경 될 때마다 종속성을 가지는 package까지 같이 컴파일 돼야하므로 비용 증가함 
  • 각 개체가 다른 개체를 유지하기 때문에 memory leak의 위험성도 높아짐

(+) package는 compile의 단위가 되는데 go는 빠른 compile 좋아하는 성향 탓인지 import cycle을 아예 막아버림

뭐 애초에 좋은 모델도 아니니까 ...

 

#예제코드

욕심이 과하면 화를 부르는 법

대애충 A에서도 B 출력하고 싶고 B에서도 A를 출력하고 싶어하는 상황이라고 가정해보자

 

[ pkg_a.go ]

package A

import (
	"fmt"
	"go-study/import-cycle-test/B"
)

type A struct{}

func NewA() *A {
	return &A{}
}

func (a *A) SayFromA() {
	fmt.Println("A say hello")
}

func (a *A) SayFromB() {
	tmp_b := B.NewB()
	tmp_b.SayFromB()
}

 

[ pkg_b.go ]

package B

import (
	"fmt"
	"go-study/import-cycle-test/A"
)

type B struct{}

func NewB() *B {
	return &B{}
}

func (b *B) SayFromB() {
	fmt.Println("B say hello")
}

func (b *B) SayFromA() {
	tmp_a := A.NewA()
	tmp_a.SayFromA()
}

 

[ debugging 결과 ]

package import-cycle-test/A
    imports import-cycle-test/B
    imports import-cycle-test/A: import cycle not allowed
exit status 1
Process exiting with code: 1

import cycle이 일어나는 경우 보통 한 부분만 특정하기 어려우므로 package 내 어떤 파일/로직에서 문제가 생기는진 안 알려줌

가끔 진짜 잘못짜두면 package 간 참조가 난리법석을 피우기 시작하고 특정하기가 굉장히 어려워지는데, 좀 가시적으로 볼 때 godepgraph 라는 툴이 도움이 될 수는 있겠다.

 

#해결방안

import cycle는 일반적으로 interface를 사용하면 대부분 해결된다.

다만 그전에 package가 서로 밀접하게 연관이 있어 cycle이 생길정도로 결속력이 있는거라면 걍 package를 하나로 합치는게 낫다. 잘 판단하도록 하자

 

여튼 이 상황에서 interface로 해결하는 방법은 요약하자면,

    1) cycle을 유발하는 함수를 후순위 B package에서 interface로 빼고

    2) 이 interface를 B struct 필드로 가지게 한 뒤

    3) 직접 A의 함수를 호출하지 않고 interface로 호출하게 구현하는 것이다

 

[ pkg_a.go ]

func (a *A) SayFromB() {
	// tmp_b := B.NewB()
	tmp_b := B.NewB(a)
	tmp_b.SayFromB()
}

 

[ pkg_b.go ]

package B

import (
	"fmt"
)

type virA interface {
	SayFromA()
}

//type B struct {}
type B struct {
	A virA
}

// func NewB() *B {
// 	return &B{}
// }
func NewB(arg_a virA) *B {
	return &B{
		A: arg_a,
	}
}

func (b *B) SayFromB() {
	fmt.Println("B say hello")
}

func (b *B) SayFromA() {
	//tmp_a := A.NewA()
	b.A.SayFromA()
}

 

즉 양쪽 다 직접 호출 하던걸

    - B에선 A 정보를 B 함수의 인자로만 받게 끔 수동적으로 바꿔준 뒤 

    - interface에 정의된 method로만 호출하게 한다는 소리다

 

 

이외에도 어떤 구조에선 공통 부분을 따로 빼서 제3의 package를 만드는 것도 유용한 방법이 될 수 있겠다.

(코드 중복이 일어나지 않게 잘 설계해야겠지만.....)

 

 

#결론

결국 import cycle도 architecturing의 범주

일반적으로 상위 로직의 필요에 의해 하위 로직이 호출되는 형태가 바람직하므로 코드의 흐름이 계층적으로 흐르게끔 설계하는 것도 중요할 것 같다. 하극상 일어나지 않게

 

 

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

[golang] interface와 reflect, 그리고 OOP  (0) 2021.03.28
go map 초기화  (0) 2020.10.18

댓글