#현상
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 |
댓글