openlang

using 선언 리팩터링은 Dispose를 Publish() 뒤로 미룬다

문법은 짧아지고 수명은 길어져요. C#의 using 선언은 자원을 감싸는 스코프가 끝날 때 해제해요. 리팩터링이 단순 정리처럼 보일 때는 이 스코프를 놓치기 쉬워요.

중첩된 using 블록에서 input을 얻고, Read(input)을 호출하고, 블록을 나온 뒤 Publish()를 호출하는 메서드가 있다고 해 볼게요. 에디터가 이 블록을 메서드 스코프의 using 선언으로 바꾸자고 제안해요. 받아들이면 Publish()가 input 해제보다 먼저 실행돼요. Publish()가 예외를 던지면 해제는 제어가 메서드를 빠져나갈 때 일어나요. 원래 코드에서는 이미 끝난 뒤였죠.

에디터는 패턴을 정확히 알아봤어요. 이 메서드가 Publish() 동안에도 자원을 쥐고 있어도 되는지는 다른 질문인데, 미리 보기에는 그 단서가 없어요. 지금은 중괄호가 사라지고 들여쓰기가 바뀌는 것만 보여 줘요.

그럼 변경을 받아들이는 사람이 스코프가 옮겨졌다는 걸 알아볼 수 있으려면, 미리 보기는 무엇을 표시해야 할까요?

이어지는 대화 5

서로의 근거를 따라
  1. 고유 링크

    Publish()를 이제 해제보다 먼저 실행되는 문장으로 강조해 보여 주면 돼요. 문법을 알아보는 건 쉬운 부분이고, 이 메서드가 길어진 수명을 감당할 수 있는지는 에디터가 그걸로 알 수 없어요.

  2. 고유 링크

    표시해 주는 건 출발점이에요. Publish()가 input이 해제된 뒤에만 호출돼야 한다면 더 재미있어져요. 메서드 범위로 옮기는 수정은 그 조건을 깨는데, 텍스트 변경만 보면 아주 평범해 보이거든요.

    후보를 하나 더 만들어 볼까요? 원래 안쪽 블록은 그대로 두고 그 안에 using 선언을 넣는 거예요. 그러면 해제 시점은 여전히 그 블록 끝이에요. 이걸 별도 선택지로 내놓고, 영향을 받는 문장을 미리 보기에 보여 주면 돼요. 정말 중요한 선택은 어느 범위를 남기느냐인데, 지워진 중괄호만 보여 주는 미리 보기로는 그걸 판단하기 어려워요.

  3. 고유 링크

    그 표시가 무엇을 약속하는지는 조심해야 해요. Dispose가 언제 실행되는지는 알려 줄 수 있어요. input을 쓰는 쪽이 모두 끝났다고까지 말할 수는 없어요.

    Read(input)이 input을 참조하는 콜백을 저장해 둔다면 어떨까요? 리팩터링을 하든 안 하든 원래 블록에도 같은 문제가 있어요. 도구가 빠져나가는 콜백을 찾을 수 있으면 표시해 주고, 리팩터링 판단은 해제 범위에만 묶어 두는 게 좋겠어요. 해제된 객체에 콜백이 여전히 닿을 수 있다고 해서 그 객체를 써도 되는 건 아니에요.

  4. 고유 링크

    미리 보기를 더 어렵게 만드는 경우가 있어요. input을 IAsyncDisposable로 바꾸면 블록은 await using이 돼요. 이제 해제는 스코프 끝에서 await하는 DisposeAsync라서, 스코프를 옮기면 await도 같이 옮겨져요. Publish()도 await하는 메서드라면 메서드 스코프로 바꾼 버전은 Publish()를 해제보다 앞에 두고, 메서드가 양보하는 지점도 바꿔요. 코드에 ConfigureAwait(false)가 있으면 해제가 예전과 다른 스레드에서 돌 수도 있고요. 이 중 어떤 것도 지워진 중괄호의 diff에는 안 보여요. 미리 보기는 "해제가 이 await들 뒤로 옮겨집니다"라고 딱 그렇게 말해야 해요.

  5. 고유 링크

    Visual Basic에는 선언 형태가 끝내 들어오지 않아서, 이런 리팩터링은 제안조차 될 수 없습니다. Using input As New StreamReader(path)와 End Using이 유일한 표기이고, 범위는 언제나 글로 적혀 있습니다. 처음 코드를 읽는 사람에게는 작은 친절이라고 생각합니다.

같은 질문의 다른 갈래

  1. advice-add 전에 동적 let부터 써 보세요

    advice는 실행 중인 에디터를 확장할 때 제일 좋은 기능이면서, 남의 init 파일에서는 제일 못 믿는 기능이에요. (제 것도 포함해서요.) advice-add에 :around를 주면 정의는 건드리지 않고 함수를 감쌀 수…