iOS 개발에서 쉽게 도전해볼만한 리팩토링 접근법 8가지 #98
thinkySide
started this conversation in
Idea
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
안녕하세요, 3기 주니어 러너 한톨입니다! 😇
오늘은 Tech Forum 기술 글자랑 대회의 마지막 주제인
‘더 나은 코드, 리팩토링’ 관련 게시글을 작성해보려 합니다.
개발을 하다보면 ‘리팩토링’ 이라는 단어를 자주 접하게 됩니다.
(보통은 일정이 촉박할 때 많이 사용하긴 하지만요.)저는 관성적으로 리팩토링을 조금씩 하고 있기는 하지만
구체적으로 어떤 목적으로, 어떻게 진행하고 있는지
깊게 고민해본 적은 없는 것 같아 반성하게 되는 것 같습니다.
그래서 이를 정리하고 공유해보고자,
iOS 개발에서 제가 직접 경험해본 것들을 바탕으로
여러가지 리팩토링 접근법에 대해 설명해보겠습니다.
리팩토링의 의미와 목적
리팩토링은 소프트웨어 공학에서 말하길, 다음과 같습니다.
조금 더 쉬운 말로 풀어보자면,
기존 코드의 동작을 유지하면서 코드 구조를 개선하는 작업입니다.
이어서 리팩토링의 목적은 맥락에 따라 조금씩 달라질 수 있으나,
근본적으로 지향하는 것들은 다음과 같습니다.
아래 정리할 리팩토링 접근법은 위와 같은 의미와 목적 아래
시도할 수 있다는 맥락으로 읽어주시길 바랍니다!
1. 의도가 전해지는 네이밍
개발자의 가장 어려운 고민 중 하나는 ‘이름 짓기’입니다.
변수, 함수, 객체, 프로토콜 등,,, 찰떡 같은 이름 짓기는 고역 그 자체입니다.
하지만 이름을 잘 짓는 것만큼 의도를 효과적으로 드러낼 수 있는 방법이 없습니다.
애플의 API Design Guidelines를 살펴보며, 아래와 같이 리팩토링 해볼 수 있습니다.
PascalCase
사용 (각 단어의 첫 글자를 대문자로 작성)lowerCamelCase
사용 (첫 단어는 소문자로 시작하고, 그 뒤의 단어의 첫 글자는 대문자로 작성)A. 변수 네이밍
is
,has
접두사 사용B. 함수 네이밍
명사
로 지정명령형 동사
로 지정영어 문장의 형태
가 되도록 네이밍C. 객체 네이밍
D. 프로토콜 네이밍
적절한 명사
를 찾고, 찾을 수 없으면 접미사able
혹은ible
사용2. 함수 재설계 하기
개인적으로 함수를 제대로 설계하는 것이 리팩토링의 가장 작은 기본 단위이지 않을까 생각합니다.
좋은 함수를 설계하는 방법은 다양하지만, 제가 경험한 가장 직관적이고 적용하기 쉬운 개념을 설명드리려 합니다.
에릭 노먼드의 ‘함수형 코딩’에서는 코드를 3가지 분류로 나눌 수 있다고 이야기합니다.
액션은 전역 변수, 특정 UI 등에 의존해 실행되기 때문에
side-effect가 발생하고, 테스트와 관리가 어렵습니다.
하지만 액션 없이 프로그램이 돌아가길 기대한다는 것은, 꿈 같은 이야기입니다.
(변화하지 않는 소프트웨어를 상상해보세요!)
그렇기에 에릭 노먼드는 이야기합니다.
이를 적용하기 위해 우리는 암묵적 입출력을 명시적 입출력으로 변환해야 합니다.
액션을 계산으로 잘 변환했는지의 기준은, 암묵적 입출력이 존재하는지의 여부로 판단할 수 있습니다.
액션을 계산으로 바꾸면 다음과 같은 효과를 기대할 수 있게 됩니다.
위 개념을 적용한 리팩토링 예시를 소개드립니다.
(제가 소개드린 개념은 함수형 코딩 내용 중 극히 일부입니다. 요 책만을 가지고 쓰고 싶은 글이 정말 많을 정도로
좋은 내용이 무지막지하게 많으니, 꼭! 한번 읽어보시길 추천드립니다! 😉)
A. 액션을 계산으로 바꾸기
3. 옵셔널 바인딩 확인하기
옵셔널은 Swift 언어에서 가장 중요하고, 가장 많이 사용되는 개념 중 하나입니다.
하지만 때때로, 잘못된 옵셔널 처리는 런타임 에러를 발생시키거나,
예측 하기 어려운 오류를 발생시키기도 합니다.
(함수가 실행은 됐는데 왜 아무것도 동작 안했지,,? 와 같은,,,)저 또한 “일단 사용하기 쉽게 처리해 놓자!” 와 같은 안일한 마음으로
옵셔널을 사용해 후회했던 경험이 나는 것 같습니다,, 🥲
일반적으로 자주 실수했던 패턴을 정리해보자면,
!
키워드 사용으로 인한 런타임 에러if let
,guard let
옵셔널 바인딩 후 별도의 처리 없이return
⭐️⭐️⭐️nil-coalescing
연산자 사용으로 인한 예상치 못한 기본값 반환사실 강제 언래핑의 경우, 대부분의 상황에서 사용을 지양한다는 말이 유명(?)하기도 하고,
nil-coalescing
의 경우도 기본값 반환이 일반적으로 큰 문제를 야기하진 않습니다.결국 가장 많이 놓치는 경우는 옵셔널 바인딩으로 당장 눈에 보이지 않아 더 주의를 기울여야합니다.
4. 명확한 에러 처리
위에서 옵셔널 바인딩 처리 후 예외 케이스에 대해 에러를 던져주었습니다.
하지만 던지는 사람이 있으면 받는 사람도 있어야겠죠? ⚾
에러를
throw
하는 것만큼 중요한 것은, 에러를catch
하는 것입니다.다양한 케이스에 대한 유연한 에러 처리는 긍정적인 사용자 경험 설계의 필수 요소입니다.
개발자 입장에서 디버깅, 테스트 등을 용이하게 만들기도 하죠.
일반적으로 놓칠 수 있는 에러 처리 포인트를 정리해보겠습니다.
Task
클로저 내부에서 에러 처리를 해주지 않는 경우특히,
Task
클로저 내부에서 에러를 던지는 함수를 호출하고 에러 처리를 하지 않아도별다른 컴파일 에러를 내지 않기 때문에(Task 내부로 에러를 던짐) 가장 많이 놓치는 지점 중 하나입니다.
(보통 비동기 함수에서 에러를 던지기 때문에 자주 발생하기도 합니다.)
이에 대한 해결책으로
throws
키워드로 에러를 던지는 것 대신Result
타입을 이용해 명시적으로 에러 처리를 할 수 있게 만들 수 있습니다.A. 여러 개의 에러 처리
B. Task 클로저 내부에서 에러 처리
5. 열거형 활용하기
한정된 케이스를 사용함과 동시에 원시값(
RawValue
)과 연관값(Associated Value
)을 이용한풍부한 표현력, 프로토콜 채택 가능, 값타입의 이점까지 Swift의 열거형
enum
은 다양한 맥락에서 활용이 가능합니다.열겨형에 가장 기본적으로 활용되는 ‘일반적인 상태 정의’를 제외하고
제가 자주 활용하는 열거형 사용 패턴을 정리하자면,
상수 및 리소스 관리를 열거형으로 하게 될 경우의 이점은
반복되는 상수 및 리소스 사용에 대한 중앙 집중화로,
등의 효과를 기대할 수 있습니다.
디자인 컴포넌트의 경우 디자이너와 미리 정의된 케이스의 한해 컴포넌트를 표현할 수 있고,
(필요한 값만 받아올 수 있음) 이에 따른 관리 및 가독성 향상 등을 기대할 수 있습니다.
이외에도, 상황에 따라
String
을enum
으로 사용함으로써 메모리 효율을 개선시킬 수도 있습니다.(위 내용은 Tech Forum의 아이작의 황금 고블린 게시글에서 자세히 논의되었습니다. 꼭 한번 읽어보세요!)
A. 상수 및 리소스 관리
B. 디자인 컴포넌트 내 Variation 정의
6. 접근 제어자 설정하기
적절한 접근 제어자 설정은 캡슐화, 은닉화에 있어 중요한 역할을 수행합니다.
라이브러리를 사용하는데 모든 요소가
public
처리되어있는 모습을 상상할 수 있으신가요?속성을 사용해도 되는건지, 함수가 어떤 이펙트를 불러올지
예측하기 어려워지고 이는 예기치 못한 상황을 발생시킬 수 있습니다.
(
.
을 찍었을 때 나타나는 수많은 리스트는 덤이랍니다.)접근제어자 설정의 핵심은, 객체 혹은 모듈 간의 통신에서
어떤 정보를 노출하고 은닉할지 선택 기준을 수립하는 것에서 시작됩니다.
저는 크게 2가지 기준으로 접근제어자를 설정합니다.
A. 일반적으로 사용할 수 있는 접근제어자
B. SwiftUI의 @State 변수의 접근제어자 설정
7. 의존성 관계 개선하기
의존성 관계는 애플리케이션 구조 혹은 비즈니스 요구사항이 복잡해질수록 중요해지는 키워드입니다.
좋은 의존성 관계를 가진 프로젝트는 변화에 드는 비용을 줄일 수 있게 됩니다.
변화에 드는 비용을 줄일 수 있다는 것은, 비즈니스 가치 창출로 이어지는 맥락이기 때문에
많은 소프트웨어 개발 팀이 이를 개선하기 위해 노력합니다.
의존성 관계 개선의 핵심은, 추상적인 것과 구체적인 것을 나누는 데서 시작할 수 있습니다.
클린 아키텍처에서 사용되는 용어를 빌려 표현해보자면, 아래는 추상적인 것으로 분류할 수 있습니다.
Entity
UseCase
그에 반해 아래는 구체적인 것으로 분류할 수 있습니다.
View
Data
다시 돌아와 의존성 관계 개선의 목적은 변화에 드는 비용을 줄임이었습니다.
그에 따라 자연스럽게 변화하기 쉬운 것들이 변화하기 어려운 것들에 의존해야 함을 직감할 수 있습니다.
(반대로,
Entity
혹은UseCase
가 자주 변화할 것으로 예상된다면,양방향 의존도 고려해볼 수 있습니다! - 이에 대한 좋은 예시가 Tech Forum의
구체적이지 않은 요구사항으로 개발하기- 확장성을 고려한 모델 설계 게시글에서 살펴볼 수 있습니다!)
본 단락의 목적은 클린 아키텍처에 대한 이해가 아닌,
소프트웨어를 설계할 때 의존 관계를 어떻게 가져갈까에 대한
기준을 제시하고자 하는 것입니다.
위를 포함한 2가지 기준으로 의존성 개선 방법 예시를 정리해보겠습니다.
protocol
을 이용한 의존성 역전8. GCD 방식을 Swift Concurrency로 마이그레이션 하기
전통적으로 Swift에서는
GCD
(Grand-Central-Dispatch) 방식을 사용해비동기 및 동시성 처리를 구현했습니다.
하지만
GCD
방식은 다양한 문제점이 있었고, (가독성 및 안정성 등의 문제)이를 개선하기 위해 애플은 WWDC21에서
Swift Concurrency
를 소개했습니다.여전히
GCD
를 사용하는 것은 큰 문제없이 동작할 수 있지만,등의 너무나 많은 이점을 뒤로하는 것이 점점 어려워지는 것 같습니다.
또한 기존
Delegate
메서드를Swift Concurrency
방식으로 점진적 마이그레이션 또한 가능 하기에일관적인 비동기 및 동시성 처리 방법을 사용하는 것은 프로젝트 관점에서도 유효할 것입니다.
A. 비동기 GCD 코드 Swift Concurrency로 마이그레이션
B. Delegate 코드 Swift Concurrency로 마이그레이션
마치며
리팩토링에 대한 키워드를 간단하게 나열해보려 했는데 작성하다보니 너무 길어진 것 같습니다,,! 🤔
(사실 2가지가 더 있었는데,, 작은 내용이기도 한 것 같아 빼버렸습니다)
어려운 내용은 아니었지만, iOS 개발을 진행하며 한번쯤 생각해볼만한 지점들을 정리할 수 있는 것에 의미를 두고자 합니다.
(또 누군가에게 조금이나마 도움이 될 수 있다면 더 기쁠 것 같네요 😀)
언제나 피드백 및 질문에 열려있습니다! 같이 이야기하며 성장할 수 있었으면 합니다.
긴 글 읽어주셔서 감사합니다!
Beta Was this translation helpful? Give feedback.
All reactions