[iOS] 팀 프로젝트 첫 코드 리뷰 (타입 안정성 개선)

TIL 6일차 - 타입 안정성 개선하기 

팀 프로젝트 마지막 발표준비되어 가는 과정 중에 튜터님이 오셔서 깜짝 코드리뷰를 해주셨다.

매도 일찍 맞는 게 좋다고 발표전에 완벽!!(?) 하게 정리하고 갈 수 있어서 너무 좋은 기회였던 것 같다 ㅎㅎ

 

 

이미지 변수 선언 과정에서  String 배열에서 UIImage? 배열로 변경하라고 리뷰해 주셨다.. 근데 왜일까?

// 이미지 파일 이름 문자열로 나열한 배열 (수정 전)
// "profile1" 같은 문자열(String) 만 저장
// 실제 이미지는 아직 메모리에 없음
private let imageNames = ["profile1", "profile2", "profile3"]
    
    
// 이미지 파일을 UIImage로 바로 로드해 저장한 배열 (수정 후)
// .profile1은 Asset Catalog에 등록된 이미지를 UIImage로 바로 로드한 배열
private let images: [UIImage?] = [.profile1, .profile2, .profile3]

 

수정 전 코드는 이미지를 나중에 직접 UIImage(named:)로 꺼내 써야 한다.

 

"Hard-coded String(문자열 직접 입력)" 방식 대신, Swift의 정적 속성(Static properties)이나 Asset Symbol을 사용하여 오타를 방지하고 자동 완성을 활용하라는 뜻이다.

그리고 현재 코드에서 imageNames = ["profile1", "profile2", "profile3"]처럼 문자열로 관리하면, 나중에 asset 이름이 바뀌거나 오타가 나도 컴파일 시점에 알 수 없고 런타임에서 nil 오류로 터진다.

또 코드 읽는 사람이 “이게 이미지인가?” 바로 알기 어려워 가독성이 똥💩이다.

 

수정 후의 코드

 

이미지가 이미 로드된 상태

오타 나면 컴파일 단계에서 바로 에러가 난다. 컴파일 에러😍>>>>>>>런타임 에러🤮

코드만 딱 봐도 아, 이미지 배열이구나!!! 하고 바로 이해되고 반복 사용하기 훨씬 깔끔하다.

그리고 이미지 자체를 처음부터 저장하므로 사용 시 바로 imageView에 할당이 가능하다.

 

 

 

그런데 여기서 드는 추가 의문.. 왜?? 왜 타입이 [UIImage?] 일까?

[UIImage?]

 

이유는 하나다.

-> Asset에 이미지가 없을 수도 있음 즉. profile1→ nil 이 가능하므로 옵셔널로 처리해야 한다.

참 배우면서 느끼는 게 스위프트 정말 철저하고 깐깐하다.. 애플다움

 

 

따라서 수정 후엔 UIImage(named: ) 안 해줘도 되고 바로 슈가 대입해 주면 된다..

//수정 전: for name in imageNames { ... imageView.image = UIImage(named: name) }
for image in images {
    let imageView = UIImageView()
    imageView.image = image // 이미 UIImage 객체이므로 image바로 대입!!!!
    // ... 이하 동일
}

 

 

추가로 버튼 만드는 함수도 같은 논리로 수정하였다.

//  버튼 만드는 함수 만들기 (수정 전)
    private func makeIconButton(
        imageName: String, 
        // String을 이용한 문자열 기반 방식
        action: @escaping () -> Void
    ) -> UIButton {
    
        let button = UIButton(type: .system)
        button.setImage(
            // 문자열로 저장했으므로 UIImage(named:) 로 꺼내써야함
            UIImage(named: imageName)?.withRenderingMode(.alwaysOriginal),
            for: .normal
        )
// 버튼 생성 함수 (수정 후)
private func makeIconButton(
    image: UIImage?,
	//String 대신 UIImage를 받아 컴파일 단계에서 오류를 방지함
    action: @escaping () -> Void
) -> UIButton {

// system 타입 UIButton 생성
    let button = UIButton(type: .system)
    button.setImage(
    	// 바로 할당 가능
    	image?.withRenderingMode(.alwaysOriginal), for: .normal)
    	// ... 이하 동일
}

 

 

 

이제 위에서 만든 함수를 이용한 단계에서도 수정해 주면 끝난다.

       // 함수 이용해 깃허브 버튼 만들기 (수정 전)
        let githubButton = makeIconButton(imageName: "githubLogo") {
            if let url = URL(string: "https://github.com/coduhee") {
                UIApplication.shared.open(url)
            }
        }
        // 함수 이용해 깃허브 버튼 만들기 (수정 후)
        let githubButton = makeIconButton(image: .githubLogo) {
            if let url = URL(string: "https://github.com/coduhee") {
                UIApplication.shared.open(url)
            }
        }

 

왜 이렇게 하는지? (코드리뷰의 핵심 이유)

  1. 안정성: "profile1"을 "proflie1"(오타)로 써도 컴파일러가 잡아주지 못하지만,. profile1은 오타가 나면 즉시 에러를 띄워준다.
  2. 가독성: 코드를 작성할 때 점(.)만 찍어도 사용 가능한 이미지 목록이 쫙 나와서 훨씬 편해진다.
  3. 유지보수: 이미지 이름이 바뀌었을 때 extension 부분 한 곳만 수정하면 전체 코드가 다 적용된다.