2020년 12월 13일 일요일

다른 Assembly에게 internal을 열어주는 방법 (feat. CLR via C#)

 internal은 해당 클래스/함수/멤더 등등을 같은 어셈블리 안에서는 public으로, 다른 어셈블리에게는 private로 접근을 제한한다. 즉 같은 어셈블리 안에서는 자유롭게 접근할 수 있지만 다른 어셈블리에서는 private처럼 접근할 수 없다는 얘기다.

대체로 큰 문제는 없고, 때때로 좋은 점이 있기도 하다. 같은 어셈블리 안에서는 public과 같이 동작하기 때문에 편한 점도 있다.

그렇다면 이것을 굳이 다른 assembly에게 열어줘야 할 필요가 있을까? 나는 대부분의 경우 불필요하다고 생각한다. 혹시나 assembly가 너무 커서 하나의 dll로 관리하기에는 문제가 될 경우가 아니라면 거의 쓸 일이 없지 않을까 싶다.

다만, 내가 이 기능을 보자마자 생각한 경우가 하나 있다. 단위 테스트. 특히 white-box 단위 테스트의 경우.

단위 테스트는 대부분 특정 단위 기능을 테스트해야 하는데, 어떤 경우에는 이러한 기능이 어셈블리 밖으로 나오지 않는 경우가 있다. 예를 들어 정렬을 하는 함수가 있고, 이 함수가 대상에 따라서 스스로 알아서 정렬 알고리즘을 결정하는 경우를 생각해보자. 이 함수는 내부적으로 (물론 그럴리는 없겠지만) 버블 소트, 셀렉션 소트, 퀵소트 등등 오만가지 소트 알고리즘을 가지고 있을 것이다. 하지만 외부에는 오직 Sort() 함수만을 공개한다. 다른 알고리즘은 굳이 공개할 필요가 없다.

이런 경우 단위 테스트는 어떻게 작성해야 할까? 특정 알고리즘이 동작할 수있도록 만들어진 데이터 셋을 만들어서? 그렇게 하면 데이터 셋을 선택하는 알고리즘에 의존적이 된다. 가장 간단한 것은 해당 정렬 알고리즘을 직접 호출하는 것이지만 그럴 수 없다. 

단위 테스트가 어떻게 일반 사용자보다 더 나은 권한을 갖게 할 수 있을까? CLR via C# 6장, 181페이지에 나와 있다. 

해당 알고리즘을 internal로 정의하고, 다음과 같이 특정 assembly에게 내 internal을 볼 수 있도록 하면 된다.

[assembly:InternalsVisibleTo("MyUnitTest, PublicKey=1234...1234")]

해보니까 PublicKey는 option이다. MyUnitTest만 있으면 internal을 public처럼 사용할 수 있다. 물론 보안의 문제가 생길 수 있으니 주의해야 할 필요가 있지만 단위 테스트 용도로는 유용하게 쓸 수 있지 않을까 싶다. 


참고: 제프리 리처의 CLR via C# 4판, Microsoft,

2020년 12월 8일 화요일

우리 시대의 영웅

 나 뿐일지는 모르겠지만,

1990년대의 영웅의 전형적인 형태는 숀 코네리가 아니었을까 싶다. 물론 이 시점에서 몇 년전 혹은 몇 년 후의 대한민국의 대학생들을 떠올리면 과연 숀 코네리따위가 영웅 축에 들기나 할까 싶기도 하지만... 

뭐, 사실이 그렇지 않을까 싶다. 당시 나는 인간 쓰레기였고, 지금도 용기따윈 개뿔도 없는 평범한 개새끼니까. 

하여, 그래서, 그런고로, 내가 희망했던? 열광했던? 추종했던? 캐릭터는 대체로 숀 코네리였다. (그런 사람이 적지는 않았으리라) ....... 에............ 영화 이름을 알기 위해 검색이 필요하다는 것은 참 슬픈 일이긴 하지만... 하, 졸라 슬프다. 하여튼 하이랜더, 인디아나 존스 시리즈, 더락 등에서 보여줬던 그의 연기는 뭐랄까... 로저 젤라즈니의 소설에 나오는 주인공 같은 느낌이다. 영생을 가진, 마초 캐릭터. 거의 모든 것을 알고, 거의 모든 것을 이해하며, 그럼에도 주인공의 조연으로 살아가는 쓸쓸한 캐릭터...?

(아 쓔발, FiLite 캔은 졸라 맛 없네... 네개나 샀는데...)

여튼, 뭐... 숀 코네리도 그립고, 로저 젤라즈니도 그립다는 뜻이다. 졸라 로저 젤라즈니 전담 번역가 박상훈 아저씨는 뭐하나 모르겠다. 로저 젤라즈니 전집 펴내야 하는거 아닌가?


return False, Assertion 그리고 Exception

 c/c++을 쓰고 있을 때에는 주로 리턴 값을 사용했다. 예외는 사용하기 귀찮았고 -try, catch 때문에 다섯 줄 정도는 더 필요했고, 무엇보다도 존나게 느렸다. 게다가 들여쓰기가 한 번 더 있어야 했다.

Assertion도 잘 쓰지 않았는데, 일단은 보통 Assertion이 c 함수였고, 그래서 좀 구려보였다. 그리고 별로 할 수 있는 것도 없었다. Assertion이 발생하면 그냥 프로그램이 끝나니까. 에러를 처리할 수 있는 방법이 었었으니, 이런 코드는 사용하면 안되는 거였다. 에러가 나도 어떻게든 끌고 가는게 당시 프로그래밍이었으니까.

그래서 주로 사용하는 것이 리턴값을 이용한 에러 처리였다. 처음에는 FALSE를 에러 상태로 사용했다. 에러가 발생하면 윈도 API처럼 GetLastError()같은 함수를 써서 에러의 원인을 제공하는 기능을 만들기도 했다. 

그러다가 그게 병신짓임을 알게 되었다. 그 뒤로는 0을 OK로 사용했고, 0이 아닌 모든 값을 에러 코드로 사용했다. 흠. 보기 좋았다. 물론 사용하기도 좋았다. 아, 솔직히 보기 좋았다는 것은 좀 거짓말이다. 0이 OK인 것은 정말 적응하기 힘든 코드다. 이것은 지금도 그렇다.

c#을 시작한 후 좀 달라졌다. 물론 이게 c# 때문인 것은 아니다. 생각이라는 걸 하기 시작했다는 뜻일지도 모르겠다. 

일단 리턴 코드에 의한 에러 처리는 상당히 줄었다. 아니, 에러 처리를 잘 안하기 시작했다. 에러가 나면 복구하려는 의지가 별로 없다. 꼭 그래야 하는 경우가 아니라면 에러가 발생하는 경우 그냥 일을 접는다. 그러니 에러 처리 코드가 많을 이유가 없다. 대신 에러 검출 코드가 대폭 늘기 시작했다. 에러 처리가 없이 쭉 진행되는 코드라서 중간에 에러가 발생하면 엿된다. 그래서 중간 중간 에러인지 확인하는 코드는 많이 들어간다. 

에러 처리는 없는데 에러를 확인해서 뭐하냐고? 둘중에 하나다. Assert 아니면 Exceptioin.

Assert는 말하자면 이런 뜻이다. '병신아 이건 이렇게 쓰라고 만든게 아닐텐데?'. 그렇다. Assert가 떴다면 그건 니가 잘못한거다. 친절한 나-디버그 모드-니까 이런거 잡아주는 거지, 남-릴리즈 모드-은 이렇게 친절하지 않단다. 더 엿되기 전에 좀 고쳐보는게 장수에 도움이 될꺼야.

반면에 Exception은 이런거다. '어머, 엿됐네'. 예상 가능했던 에러가 발생한 경우다. 아, 물론 너 말고 그 라이브러리 만든 사람이 예상했던 에러라는 뜻이다. 그걸 예상 못한 너는 좀 미련한거지. ㅡ,.ㅡ; 혹은 니가 예상했던 바로 그 일이 일어났다는 뜻이다. 뭐, 해결책은 이미 알고 있겠지. 너가 예상 못했던 일이라면 엿된거고, 예상했던 일이라면 적당히 해결하면 되는 거다. 

Exception을 쓰는 좀 나쁘지만 어쩔 수 없는 경우는 겁나 깊은 depth의 코드의 최외곽에 보호막으로 사용하는 것이다. 이건 뭘 하자는 것은 아니고, 그냥 최후의 보루다. 그냥 사용자가 프로그램을 종료할 기회를 주는 정도? 혹은 갑자기 프로그램이 사라져서 빡친 사용자가 나한테 고래고래 소리지르며 전화를 하지 않도록, 그러니까 뭔가 궁금함을 유발시키는 신기한 메시지에 흥미로우면서 비파괴적인 전화를 할 수 있도록 유도하는 용도다. 이 기능은 종종 유용하지만, 자주 발생하면 더 욕을 먹는다. 

리턴 값에 의한 에러 처리는 점점 줄어들고 있다. 일단 에러 처리는 겁나 귀찮은 기능이다. 예상할 수 있는 에러는 당연히 에러가 아니라 버그다. 그건 때려 잡아야 할 대상이고 그걸 못 때려잡는 다는 거는 이 바닥에서 밥먹고 살기 좀 힘들다는 얘기다. 누구 말마따나 '돌아가는 거는 누구나 한다'지 않는가? 그러니 예상할 수 없는 사태가 발생하는 것이 에러일 거고, 예상할 수 없는 사태를 극복한다는 것은 어불성설이다. 고장난 비행기면 하드 랜딩이면 다행인거지, 공중에서 비행기를 고쳐서 원래 목적지로 날아간다는 거는 자살행위인 셈이다. 

술이나 먹자.