저는 이런 상황에서 1번, 타입이 명시적이지 않아서 코드를 읽기 힘들다는 말이 선뜻 이해가 되지 않았습니다.
코드가 읽기 힘든 건 변수명이 모호할 때가 주 이유고 (여러가지가 있겠지만… ^^;)
지금 읽고 잇는 코드의 정확한 타입이 무엇인지 몰라 힘든 경우는 잘 없을 것이라고 생각했었습니다.
또한, 타입이 알고 싶을 때 변수명에 마우스를 올려두면 친철하게 정확한 타입을 알 수도 있습니다.
2번의 경우에는 댓글에도 말씀이 나온 Effective C#에서도 주의가 필요한 경우라고 했던거로 기억납니다.
그래서 저는 위에 말씀드린대로 기본타입은 타입을 명확히 선언하고 그 외는 var로 선언한다는 나름의 결론을 내려서 실천 중입니다. ㅋㅋ
new 키워드 뿐만 아니라 함수 호출 표현식에서도 함수 이름과 인자를 감싸는 괄호 사이에 공백 문자를 하나 두는 코딩 스타일(예: Foo (bar))이 있는데, 대체로 Mono나 Mono + Gtk#을 써서 만든 리눅스용 GUI 앱들의 코드가 그렇습니다.
Mono 쪽 사람들 중에 GNOME 프로젝트와 관계된 사람이 많고(Mono 창업자인 미겔 데 이카사가 GNOME 창업자이기도 하고 Xamarin 창업자이기도 하고 그렇습니다), GNOME 커뮤니티에서 Mono 와 C#으로 리눅스용 GUI 앱을 많이들 만들었기 때문에 GNOME C 코딩 스타일을 많이 따른 듯 합니다.
아, 그런데 궁극적으로 함수 이름과 여는 괄호 사이에 공백을 넣는 관행 자체는 아마도 GNU 코딩 표준이 GNOME C 코딩 스타일에도 영향을 준 것 같습니다.
메모장으로 코딩하는 일은 없겠지만, 현실적으로는 GitHub 등에서 코드 리뷰를 할 때는 IDE처럼 타입 정보를 띄워주는 툴팁이 제공이 안 되는 문제가 있어서, 저희 팀에서는 타입이 명확하게 드러나는 케이스(예: var foo = new Foo())가 아니면 var을 쓰지 않는 쪽으로 코딩 스타일을 맞추고 있습니다. (그 외에도 가끔 Vim으로 간단한 코드 수정을 하기도 하고요.)
요즘에는 코드 리뷰도 IDE 안에서 할 수 있게 진화하고 있어서 이 문제는 점점 완화될 것으로 보이지만, 개인적으로 간단한 코드 리뷰는 모바일에서도 할 때가 많아서 당분간은 이 스타일을 좀더 고수하고자 합니다.
var를 많이 사용하는 입장에서 var가 개발자가 예상하지 않는 타입으로 추론될 수 있다는 것을
이해한순간이 제겐 더 강한 임팩트가 있었나봅니다… ㅋㅋㅋ
다른건 잘 생각이 안나네요 ㅋㅋㅋㅋㅋ
오늘 퇴근하면 오랜만에 Effective C#에서 그 챕터를 다시 읽어봐야겠네요! ㅎㅎ
개발자가 예측하지 못하는 타입으로 추론하는 경우는 저는 우선 경험을 못했고,
var 써놓고 alt + enter로 자동완성 사용하시면 만사해결…? 입니다. (안정적인 타입 + 명시적인 타입 + 가독성은 취향)
전 자동완성 강추…!
전 이펙티브 C#은 안봤는데 var로 인해 성능이 문제가 될 수 있다고 하는 이유 중 예시를 하나 들은 것은 있는데,
IQueryable 타입으로 리턴하는 메서드에 대해 개발자가 명시적으로 IEnumerable 타입으로 받았을 경우 문제가 있다는 점인데요.
이 경우에도 var로 받아서 자동완성해주면 IQueryable로 잘 바꿔줍니다. ← 이것은 경험담
@suwoo 님, @Vincent 님이 말씀해주신 것처럼 LINQ 결과가 복잡한 케이스에서 IEnumerable로 리턴되든 IQueryable로 리턴되든 var로 퉁(?)칠수 있어서 편하게 사용하고 있는데요. (자동완성까지는 생각을 못해 봤네요~)
@hongminhee 님의 말씀처럼 리뷰어를 배려하거나 코드 가독성을 유지하는 차원에서 명시적으로 타입을 선언하는 것도 필요할 것 같다는 생각이 듭니다.
var v = new C() 방식이 C v = new() 방식보다 먼저 나온 것으로 기억하는데, 그래서 좀더 친숙한 것 같기도 하고요.
개인적으로는 데이터 처리를 할때 결과가 아닌 중간 과정의 타입으로 익명 타입을 쓰곤 하는데, var로 선언하면 IDE의 인텔리센스 도움을 받아가면서 불필요한 타입을 정의하지 않아도 되는 장점이 있는 것 같아요.
저는 개인적으로 깃헙 같은 데에서 코드 리뷰를 할 때,
타입이 안 보여서 답답했던 적은 딱히 없었던 거 같숨다.
어차피 디버깅 하면서 BP 찍어 볼 거 아닌데 소스코드 보는 타이밍에 타입에 대한 정보가 그렇게까지 중요한가 싶어요.
(극단적으로 말하자면, 타입 이름은 메서드 시그니처와 반환 타입 정도에 표시 되는거면 충분… 나머지는 딱히 필요 없…;;; )
오히려 타입 정보 보다는 변수 이름과 메서드 이름, 그리고 그것이 어떻게 구성되어 흘러가는 가를 한 눈에 알아볼 수 있게 정리하는 것이 훨씬 더 중요하다고 생각하는 편입니닷.
다시 한 번 곰곰히 생각해봐도,
변수로 선언되는 타입 이름이 안 보여서 답답했던 적은 없는 거 같은데…
(시그니처나 반환 타입을 맞추는 것을 제외하고, 변수 선언이나 할당에서 타입 이름이 안 보여서 답답한 적은 없는 거 같숨다.)
오히려 타입 정보 보다는 변수 이름과 메서드 이름, 그리고 그것이 어떻게 구성되어 흘러가는 가를 한 눈에 알아볼 수 있게 정리하는 것이 훨씬 더 중요하다고 생각하는 편입니닷.
이것도 하고 타입명시도 해주는 것이지요.
제가 명시적인 것을 좋아하는 성격이라 그런 걸 수도 있습니다.
제가 싫어하는 구문이
var result = objectMethod.GetXXX();
와 같이 어떤 메서드에 대해서 리턴형이 제대로 보이지 않을 경우입니다.
물론 지금 논점인 var c = new asdf(); 와 asdf c = new(); 와는 논점이 빗나가지만, 저는 이런 경우때문에 var를 그냥 쓰지말자고 스스로 컨벤션을 정하는 편입니다.
저는 var때문에 타입이 안보이면 좀 답답하고 막 다른 사람 소스 허락맡고 var를 자동완성으로 타입을 명시시키는 방식으로 수정한 적도 있습니다.
저는 단순히 쓰기 편해서 var v = new C(); 를 선호했습니다. 그러다 GitHub로 협업을하니 알게된 의견 입니다. C v = new();로 쓰자, “GitHub로 리뷰 할 때 var을 쓰면 IDE도움을 받을 수 없어 정확히 어떤 타입인지 알기 힘들다” 등등…이 였습니다. 즉 가능한 var을 자제하고 명시적으로 표현하자 입니다.
var isString? = IDontKnow; var what? = YouShouldFigureout
Azure SDK나 일부 닷넷 SDK의 경우, implicit operator로 정의해둔 부분 때문에 var 키워드로 변수를 받는지 아닌지에 따라 변수의 타입이 갈리는 경우가 생기는걸 최근 경험하고 있습니다.
var 키워드로는 implicit operator가 불리지 않는다는 "호 부작용"과 "불호 부작용"이 공존하는 특이한 상황이 연출됩니다.
"호"는 implicit operator가 의도치 않게 불리는 것을 방지하는 "명시성"을 보장하는 반면, "불호"는 implicit operator를 부르지 않았기 때문에 목적지에 가려면 굳이 다른 캐스팅을 또 시도해야 한다는 "비효율성"이 강조되는 아이러니가 있는 것 같습니다. 이런건 특히 웹 API 결과를 컨테이너로 래핑한 유형의 모델에서 잘 발생하는 문제인 듯 합니다.
Azure SDK의 사례를 만나기 전까지는 implicit operator와 var 키워드 사이의 관계를 깊이 생각하지 못했는데, 막상 경험하고나니 생각할 점이 많아지는 부분이 있는 것 같습니다. ㅎㅎ
아, 그러고보니 이건 바꿔 말해서 implicit operator의 부작용이라고도 볼 수 있는 부분이 되겠네요!
예제 1
var vmssInstance = await client.GetVirtualMachineScaleSetVmResource(ResourceIdentifier.Parse(list.FirstOrDefault().Key)).GetAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
VirtualMachineScaleSetVmResource vmssInstanceResource = await client.GetVirtualMachineScaleSetVmResource(ResourceIdentifier.Parse(list.FirstOrDefault().Key)).GetAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
var isSameType = vmssInstance.GetType() == vmssInstanceResource.GetType(); // false
예제 2
var sw1 = StringWrapper.CreateWrapper();
string sw2 = StringWrapper.CreateWrapper();
var isSameType = sw1.GetType() == sw2.GetType();
Console.WriteLine(isSameType); // false
public class StringWrapper
{
public static StringWrapper CreateWrapper() => new StringWrapper { Value = DateTime.UtcNow.ToShortDateString(), };
public string Value { get; init; } = string.Empty;
public static implicit operator string(StringWrapper sw) => sw.Value;
}
(수정) 예제 2에서 @BigSquare 님의 피드백으로 코드 내 오타가 있었던 부분을 정정했습니다. sw1과 sw2 변수 간의 비교가 원래 의도입니다.