<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>HEROHJK Document</title>
    <link>https://herohjk.tistory.com/</link>
    <description>원툴맨이 되고 싶은 잡식 개발자</description>
    <language>ko</language>
    <pubDate>Fri, 29 May 2026 00:02:36 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>HEROHJK</managingEditor>
    <image>
      <title>HEROHJK Document</title>
      <url>https://t1.daumcdn.net/cfile/tistory/270ED04C57BB425A08</url>
      <link>https://herohjk.tistory.com</link>
    </image>
    <item>
      <title>Xcodes를 사용할 때, 간단하게 터미널로 심볼릭 링크를 만들어주는 파이썬 스크립트.</title>
      <link>https://herohjk.tistory.com/77</link>
      <description>&lt;pre id=&quot;code_1682414091684&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import os
import sys
from pathlib import Path

# 사용 가능한 Xcode 버전을 검색하고 목록으로 반환하는 함수
def find_xcode_versions():
    app_dir = Path('/Applications')
    xcode_versions = [entry for entry in app_dir.iterdir() if entry.is_dir() and entry.name.startswith('Xcode-')]
    return xcode_versions

# 심볼릭 링크를 생성하는 함수
def create_symlink(version):
    xcode_symlink = Path(&quot;/Applications/Xcode.app&quot;)

    if xcode_symlink.is_symlink():  # 기존 심볼릭 링크가 존재하면 삭제
        xcode_symlink.unlink()

    os.system(f&quot;ln -s /Applications/{version} /Applications/Xcode.app&quot;)  # 새 심볼릭 링크 생성
    print(f&quot;\033[32m심볼릭 링크가 /Applications/{version}로 설정되었습니다.\033[0m&quot;)

    # 기본 Xcode 버전을 설정
    os.system(f&quot;sudo xcode-select -s /Applications/{version}/Contents/Developer&quot;)
    print(f&quot;\033[32m기본 Xcode 버전이 {version}로 설정되었습니다.\033[0m&quot;)


def version_tuple(version_str):
    return tuple(map(int, (version_str.split(&quot;.&quot;))))

# 입력된 버전 문자열과 일치하는 가장 최신 버전을 반환하는 함수
def find_latest_matching_version(input_version, xcode_versions):
    matching_versions = [version for version in xcode_versions if version.name.startswith(f&quot;Xcode-{input_version}&quot;)]
    if not matching_versions:
        return None

    latest_version = max(matching_versions, key=lambda x: version_tuple(x.name[6:-4]))
    return latest_version.name


def get_current_xcode_version():
    result = os.popen(&quot;xcode-select -p&quot;).read().strip()
    if &quot;/Applications/&quot; in result:
        xcode_path = result.split(&quot;/Contents/Developer&quot;)[0]
        current_version = os.path.basename(xcode_path)
        return current_version
    else:
        return None


def select_xcode_version():
    xcode_versions = find_xcode_versions()
    current_xcode_version = get_current_xcode_version()

    if not xcode_versions:
        print(&quot;\033[31mXcode 버전을 찾을 수 없습니다.\033[0m&quot;)
        return

    print(&quot;사용 가능한 Xcode 버전:&quot;)
    for idx, version in enumerate(xcode_versions, start=1):
        if version.name == current_xcode_version:
            print(f&quot;\033[32m{idx}: {version.name} (현재 설정된 버전)\033[0m&quot;)
        else:
            print(f&quot;\033[32m{idx}: {version.name}\033[0m&quot;)

    print(&quot;\033[31m0: 돌아가기\033[0m&quot;)

    while True:
        try:
            user_choice = int(input(&quot;사용할 Xcode 버전 번호를 선택하세요: &quot;))
            if 1 &amp;lt;= user_choice &amp;lt;= len(xcode_versions):
                selected_version = xcode_versions[user_choice - 1].name
                create_symlink(selected_version)
                break
            elif user_choice == 0:
                exit()
            else:
                print(&quot;\033[31m목록에 없는 번호입니다. 다시 시도해주세요.\033[0m&quot;)
        except ValueError:
            print(&quot;\033[31m올바른 숫자를 입력해주세요.\033[0m&quot;)


if __name__ == &quot;__main__&quot;:
    print(&quot;\n\n\n\n\n====================Xcode-Version-Selector====================&quot;)
    if len(sys.argv) &amp;gt; 1:
        input_version = sys.argv[1]
        xcode_versions = find_xcode_versions()
        xcode_version_names = [xcode_version.name for xcode_version in xcode_versions]

        matching_version = find_latest_matching_version(input_version, xcode_versions)

        if matching_version:
            create_symlink(matching_version)
        else:
            print(&quot;\033[31m입력한 버전과 일치하는 Xcode를 찾을 수 없습니다. 사용 가능한 버전 목록을 표시합니다.\033[0m&quot;)
            select_xcode_version()
    else:
        select_xcode_version()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.xcodes.app/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Xcodes&lt;/a&gt; 앱은 Xcode를 설치할 때, 이렇게 설치를 해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1610&quot; data-origin-height=&quot;222&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Rbyo0/btscGLKfVeD/9REwbrBzWp7hAjclOzhUI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Rbyo0/btscGLKfVeD/9REwbrBzWp7hAjclOzhUI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Rbyo0/btscGLKfVeD/9REwbrBzWp7hAjclOzhUI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRbyo0%2FbtscGLKfVeD%2F9REwbrBzWp7hAjclOzhUI1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1610&quot; height=&quot;222&quot; data-origin-width=&quot;1610&quot; data-origin-height=&quot;222&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, 빌드머신이나 스크립트로 ~/Applications/xcode.app 을  설정한 경우 경로를 찾을수 없어 난감해지는 경우가 있는데요,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;심볼릭 링크를 만들고, xcode-select 커맨드를 이용하여 기본 버전으로 만들어 주어, 이를 해결해주는 파이썬 스크립트입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 ~/.zshrc에 다음과같이 추가하여 사용하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;alias xcs='python3 / input_script_path/xcodeSelector.py'&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 터미널에 이렇게 입력하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;xcs&lt;br /&gt;혹은&lt;br /&gt;xcs 14.3&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;xcode의 버전은 네개로 나뉘어집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;14.3을 입력하면 14.3.x버전대의 가장 높은 버전을..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;14를 입력하면 14.x.x 버전대의 가장 높은 버전을 선택 해 줍니다.&lt;/p&gt;</description>
      <category>공통/Python</category>
      <author>HEROHJK</author>
      <guid isPermaLink="true">https://herohjk.tistory.com/77</guid>
      <comments>https://herohjk.tistory.com/77#entry77comment</comments>
      <pubDate>Tue, 25 Apr 2023 18:19:18 +0900</pubDate>
    </item>
    <item>
      <title>테스트를 위한 리팩토링</title>
      <link>https://herohjk.tistory.com/76</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;드디어 마지막장입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 예시 코드를 테스트가 가능한 구조로 리팩토링하고, 그것을 평가하는 시간을 가져보겠습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;ERP 시스템 - 이메일 변경 기능&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이메일 변경 기능&lt;/li&gt;
&lt;li&gt;이메일이 회사 도메인일 경우 직원, 아니면 고객으로 분류.&lt;/li&gt;
&lt;li&gt;직원수를 체크&lt;/li&gt;
&lt;li&gt;모든 사용자정보는 DB에 저장&lt;/li&gt;
&lt;li&gt;변경 후 외부에 알림&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1678166338106&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class User {
    private(set) var id: Int
    private(set) var email: String
    private(set) var type: UserType

    // 생성자 생략
}


extension User {
    public func changeEmail(newEmail: String, company: Company) {
        if self.email == newEmail { return }
        
        // 이메일 검증 과정, 위에서 구현한 Company에서 처리 (책임 분산)
        let newType = company.isEmailCorporate(newEmail) ? .employee : .customer

        if self.type != newType {
            let delta = newType == .employee ? 1 : -1
            // 직원수 또한 Company에서 처리 (책임 분산)
            company.changeNumberOfEmployees(delta)
        }

        self.email = newEmail
        self.type = newType
    }
}

class Company {
    private(set) var domainName: String
    private(set) var numberOfEmployees: Int
    
    // 생성자 생략
}

extension Company {
    public func changeNumberOfEmployees(delta: Int) {
        Precondition.Requires(self.numberOfEmployees + delta &amp;gt;= 0)

        // 직원 수는 회사에서 관리
        self.numberOfEmployees += delta
    }

    public func isEmailCorporate(email: String) -&amp;gt; Bool {
        let emailDomain = email.split(&quot;@&quot;)[1]
        
        // 회사 이메일 도메인 검증 또한 회사에서 관리
        return self.domainName == emailDomain
    }
}

public class UserFactory {
    public class func create(data: Data) -&amp;gt; User {
        if !dataCheck(data) { fatalError(&quot;Invalid User Data&quot;) } // 해당 데이터가 User의 데이터가 맞는지 검사.
        
        let email = String(data[1])
        let type = UserType(data: data[2])
        
        return User(email, type)
    }
    
}

public class CompanyFactory {
    public class func create(data: Data) -&amp;gt; Company {
        if !dataCheck(data) { fatalError(&quot;Invalid Company Data&quot;) } // 해당 데이터가 Company의 데이터가 맞는지 검사.
        
        let domainName = String(data[0])
        let numberOfEmployees = Int(data[1])
        
        return Company(domainName, numberOfEmployees)
    }
}

class UserController { // 험블 컨트롤러
    private let database: DataBase
    private let messageBus: MessageBus
    
    // 생성자 생략

    public func changeEmail(userID: Int, newEmail: String) {
        // DB를 모델링하는 과정, 리팩토링하며 만든 팩토리 클래스가 수행.
        let user = UserFactory.create(data: self.database.getUserById(userID))
        let company = CompanyFactory.create(data: self.database.getCompany())

        // 이메일을 바꾸는 과정. 아래 재작성한 User가 수행
        user.changeEmail(newEmail: newEmail, company: company)

        // 외부 협력자를 호출
        self.database.saveCompany(company)
        self.database.saveUser(user)
        self.messageBus.sendEmailChangedMessage(userID, newEmail)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 코드가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조금 더 구체적으로 이 코드는 리팩토링을 진행한 코드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드만 두고 봤을때는, 당연하다고 생각이 될지도 모르지만, 많은 개발자들은 일정이 그렇게 여유있지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트와 프로젝트 관리가 서툴다면 이렇게까지 세밀하게 나누어지는 경우는 드물다고 생각합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(예시코드이긴 하지만, 당장 코드만 보면 그렇게 복잡한 코드도 아니거든요..)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통은 아래의 코드와 같은 상태일것이라고 예상합니다. (혹은 그것보다 더 안좋거나..)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1677723347921&quot; class=&quot;reasonml&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class User {
    private(set) var id: String
    private(set) var email: String
    private(set) var type: UserType

    public func changeEmail(userID id: Int, newEmail: String) {
        /// 이 코드처럼 도메인 클래스가 스스로 DB를 검색하고 저장하는 방식을 활성 레코드 패턴이라고 함.
        /// 코드 베이스가 커지면 커질수록, 확장에 애로사항이 생김.
        let data = Database.getUserById(id) // 외부 협력자
        self.id = id
        self.email = String(data[1])
        self.type = UserType(data: data[2])

        if self.email == newEmail { return }

        let companyData = Database.getCompany() // 외부 협력자
        let companyDomainName = String(companyData[0])
        let numberOfEmployees = Int(companyData[1])

        let emailDomain = newEmail.split(&quot;@&quot;)[1]
        let isEmailCorporate = emailDomain == companyDomainName
        let newType: UserType = isEmailCorporate ? .employee : .customer

        if self.type != newType {
            let delta = newType == .employee ? 1 : -1
            let newNumber = numberOfEmployees + delta
            Database.saveCompany(newNumber) // 외부 협력자
        }

        self.email = newEmail
        self.type = newType

        Database.saveUser(self) // 외부 협력자 
        MessageBus.sendEmailChangedMessage(self.id, newEmail) // 외부 협력자
    }

    public enum UserType {
        case customer
        case employee
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;cased1.png&quot; data-origin-width=&quot;975&quot; data-origin-height=&quot;509&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7lfec/btr2ugU6tpS/kbD5FaiwWTeEz0KwT12j11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7lfec/btr2ugU6tpS/kbD5FaiwWTeEz0KwT12j11/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7lfec/btr2ugU6tpS/kbD5FaiwWTeEz0KwT12j11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7lfec%2Fbtr2ugU6tpS%2FkbD5FaiwWTeEz0KwT12j11%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;975&quot; height=&quot;509&quot; data-filename=&quot;cased1.png&quot; data-origin-width=&quot;975&quot; data-origin-height=&quot;509&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB와 통신을 하고, 이메일을 변경하는 비즈니스로직이 합쳐져있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB와 메시지버스는 외부 의존성입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;협력자수도 많고, 애플리케이션의 핵심 로직이므로 지나치게 복잡한 코드로 분류가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 암시적 의존성을 명시적으로 만들기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아이디와 이메일은 명시적입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 DB와 메시지버스는 암시적 의존성으로 분류할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 클래스 내 DB와 메시지버스의 인터페이스를 추가합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트시에는 Mock으로 처리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(DB는 저장, 메시지버스는 전송 측면이므로 둘 다 명령의 기능을 가지고 있습니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 애플리케이션 서비스 계층 도입&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1번의 연장인데, 외부 서비스와 직접 통신하기 위해 User에서 험블 컨트롤러를 따로 만들어, 책임을 나누어줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1677725341515&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class UserController { // 험블 컨트롤러
    private let database
    private let messageBus
    
    public init(database: DataBase, messageBus: MessageBus) {
    	// 추후 추가적인 요구사항이 생긴다면 이또한 팩토리 클래스로 나눠볼만 하다.
    	self.database = database
        self.messageBus = messageBus
    }

    public func changeEmail(userID: Int, newEmail: String) {
        // DB데이터를 모델링 하는건 서비스에 속하면 안됨.
        let data = self.database.getUserById(userID)
        let email = String(data[1])
        let type = UserType(data: data[2])
        var user = User(userID, email, type) 
        
        // 위와 마찬가지
        let companyData = self.database.getCompany()
        let companyDomainName = String(companyData[0])
        let numberOfEmployees = Int(companyData[1])

        let newNumberOfEmployees = user.changeEmail(newEmail, companyDomainName, numberOfEmployees)

        self.database.saveCompany(newNumberOfEmployees)
        self.database.saveUser(user)
        self.messageBus.sendEmailChangedMessage(userID, newEmail) // 이메일이 이전과 다른지 검사하지 않고, 무조건 메시지를 전송.
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고, 협력자가 제거된 User 클래스를 만들어봅니다.&lt;/p&gt;
&lt;pre id=&quot;code_1677725471506&quot; class=&quot;haxe&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class User {
    // 위 험블 컨트롤러를 반영한 후 수정된 changeEmail.
    // 더이상 협력자를 필요로 하지 않지만, 여전히 많은 책임을 지고 있다.
    public func changeEmail(newEmail: String, companyDomainName: String, numberOfEmployees: Int) -&amp;gt; Int {
        if self.email == newEmail { return numberOfEmployees }

        var numberOfEmployees = numberOfEmployees

        let emailDomain = newEmail.split(&quot;@&quot;)[1]
        let isEmailCorporate = emailDomain == companyDomainName
        let newType: UserType = isEmailCorporate ? .employee : .customer

        if self.type != newType {
            let delta = newType == .employee? 1 : -1
            let newNumber = numberOfEmployees + delta
            numberOfEmployees = newNumber
        }

        self.email = newEmail
        self.type = newType

        return numberOfEmployees
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 User Class 자체는 외부 협력자와 격리되었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;case2.png&quot; data-origin-width=&quot;975&quot; data-origin-height=&quot;509&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwXe6a/btr2vUqwriO/mKd7KP3SLbBeboBcsdoeK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwXe6a/btr2vUqwriO/mKd7KP3SLbBeboBcsdoeK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwXe6a/btr2vUqwriO/mKd7KP3SLbBeboBcsdoeK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcwXe6a%2Fbtr2vUqwriO%2FmKd7KP3SLbBeboBcsdoeK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;975&quot; height=&quot;509&quot; data-filename=&quot;case2.png&quot; data-origin-width=&quot;975&quot; data-origin-height=&quot;509&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 서비스 복잡도 낮추기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 복잡도를 낮추기 위해 Company 클래스를 만들어봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고, User와 Company를 팩토리 패턴을 사용하여, 모델링을 위한 생성자를 만듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 ORM을 용이하게 사용하기 위함입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1677725966217&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Company {
	var domainName: String
    var numberOfEmployees: Int
    
    // 생성자 생략..
}

public class UserFactory {
	public class func create(data: Data) -&amp;gt; User {
    	if !dataCheck(data) { fatalError(&quot;Invalid User Data&quot;) } // 해당 데이터가 User의 데이터가 맞는지 검사.
        
        let email = String(data[1])
        let type = UserType(data: data[2])
        
        return User(email, type)
    }
    
}

public class CompanyFactory {
	public class func create(data: Data) -&amp;gt; Company {
    	if !dataCheck(data) { fatalError(&quot;Invalid Company Data&quot;) } // 해당 데이터가 Company의 데이터가 맞는지 검사.
        
    	let domainName = String(data[0])
        let numberOfEmployees = Int(data[1])
        
        return Company(domainName, numberOfEmployees)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후, Company에서 이메일 검사와 직원수 변동에 대한 처리 로직을 작성합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1677726179669&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;extension Company {
	public func changeNumberOfEmployees(delta: Int) {
        Precondition.Requires(self.numberOfEmployees + delta &amp;gt;= 0)

        // 직원 수는 회사에서 관리
        self.numberOfEmployees += delta
    }

    public func isEmailCorporate(email: String) -&amp;gt; Bool {
        let emailDomain = email.split(&quot;@&quot;)[1]
        
        // 회사 이메일 도메인 검증 또한 회사에서 관리
        return self.domainName == emailDomain
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리된 코드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리된 코드를 전체적으로 살펴보면 이렇습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1677726355915&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class User {
    private(set) var id: Int
    private(set) var email: String
    private(set) var type: UserType

    // 생성자 생략
}


extension User {
    public func changeEmail(newEmail: String, company: Company) {
        if self.email == newEmail { return }
        
        // 이메일 검증 과정, 위에서 구현한 Company에서 처리 (책임 분산)
        let newType = company.isEmailCorporate(newEmail) ? .employee : .customer

        if self.type != newType {
            let delta = newType == .employee ? 1 : -1
            // 직원수 또한 Company에서 처리 (책임 분산)
            company.changeNumberOfEmployees(delta)
        }

        self.email = newEmail
        self.type = newType
    }
}

class Company {
    private(set) var domainName: String
    private(set) var numberOfEmployees: Int
    
    // 생성자 생략
}

extension Company {
    public func changeNumberOfEmployees(delta: Int) {
        Precondition.Requires(self.numberOfEmployees + delta &amp;gt;= 0)

        // 직원 수는 회사에서 관리
        self.numberOfEmployees += delta
    }

    public func isEmailCorporate(email: String) -&amp;gt; Bool {
        let emailDomain = email.split(&quot;@&quot;)[1]
        
        // 회사 이메일 도메인 검증 또한 회사에서 관리
        return self.domainName == emailDomain
    }
}

public class UserFactory {
    public class func create(data: Data) -&amp;gt; User {
        if !dataCheck(data) { fatalError(&quot;Invalid User Data&quot;) } // 해당 데이터가 User의 데이터가 맞는지 검사.
        
        let email = String(data[1])
        let type = UserType(data: data[2])
        
        return User(email, type)
    }
    
}

public class CompanyFactory {
    public class func create(data: Data) -&amp;gt; Company {
        if !dataCheck(data) { fatalError(&quot;Invalid Company Data&quot;) } // 해당 데이터가 Company의 데이터가 맞는지 검사.
        
        let domainName = String(data[0])
        let numberOfEmployees = Int(data[1])
        
        return Company(domainName, numberOfEmployees)
    }
}

class UserController { // 험블 컨트롤러
    private let database: DataBase
    private let messageBus: MessageBus
    
    // 생성자 생략

    public func changeEmail(userID: Int, newEmail: String) {
        // DB를 모델링하는 과정, 리팩토링하며 만든 팩토리 클래스가 수행.
        let user = UserFactory.create(data: self.database.getUserById(userID))
        let company = CompanyFactory.create(data: self.database.getCompany())

        // 이메일을 바꾸는 과정. 아래 재작성한 User가 수행
        user.changeEmail(newEmail: newEmail, company: company)

        // 외부 협력자를 호출
        self.database.saveCompany(company)
        self.database.saveUser(user)
        self.messageBus.sendEmailChangedMessage(userID, newEmail)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;case3.png&quot; data-origin-width=&quot;975&quot; data-origin-height=&quot;509&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AqLP7/btr2wuyBdnI/SMqfhoEfm1kz2Fy29Lj6b0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AqLP7/btr2wuyBdnI/SMqfhoEfm1kz2Fy29Lj6b0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AqLP7/btr2wuyBdnI/SMqfhoEfm1kz2Fy29Lj6b0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAqLP7%2Fbtr2wuyBdnI%2FSMqfhoEfm1kz2Fy29Lj6b0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;975&quot; height=&quot;509&quot; data-filename=&quot;case3.png&quot; data-origin-width=&quot;975&quot; data-origin-height=&quot;509&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금의 예시코드는 간단한 코드이기에, Factory 패턴이 필요가 없긴 하지만, 우선은 넣어보았습니다..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 처음에 보여진 코드의 형태로 완성되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;도메인 모델 및 알고리즘 테스트&lt;/h2&gt;
&lt;pre id=&quot;code_1677726582602&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void func test_고객에서_직원으로() {    
    let company = Company(&quot;mycorp.com&quot;, 1)
    var sut = User(1, &quot;user@gmail.com&quot;, .customer)

    sut.changeEmail(&quot;new@mycorp.com&quot;, company)

    XCTAssertEqual(2, company.numberOfEmployees)
    XCTAssertEqual(&quot;new@mycorp.com&quot;, sut.email)
    XCTAssertEqual(UserType.employee, sut.type)
}

void func test_직원에서_고객으로() {}
void func test_타입은_변하지않을때() {}
void func test_이메일이_바뀌지않을때() {}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로 도메인 모델, 알고리즘 사분면에 속한 코드들에 대한 테스트를 진행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 이후에 컨트롤러도 통합테스트의 영역에서 테스트가 필요합니다만, 현재는 &lt;b&gt;단위테스트의 영역만 다루겠습니다&lt;/b&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;단위테스트의 4대 요소에 대한 평가.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. &lt;b&gt;회귀 방지&lt;/b&gt;: 코드의 양이 많아지거나 복잡해지는 경우 발생할 가능성이 높습니다. 복잡한 코드를 전과 비교해서 작은 단위로 나누었기 때문에 회귀방지에 어느정도 도움이 된다고 볼 수있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. &lt;b&gt;리팩터링 내성&lt;/b&gt;: 거짓양성이 발생하는 케이스입니다. 이후 UserController를 테스트할때는 추가적인 조치가 필요하겠지만, 현재의 경우 구현 세부사항 자체를 제가 생략(ㅡ,ㅡ)했기 때문에, 예시코드에서는 보기가 어렵지만.. 저기에 들어갈 세부적인 로직을 분리하면 됩니다..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. &lt;b&gt;빠른 피드백&lt;/b&gt;: 이 부분도 UserController로 의존성을 옮겼기 때문에, 상대적으로 피드백이 빨라졌다고 할수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. &lt;b&gt;유지보수성&lt;/b&gt;: 계층이 나누어져서 전체적으로는 코드가 많아졌지만. &quot;단위테스트&quot;의 측면에서는 테스트하려는 기능이 나뉘어졌기 때문에 짧아졌고, 이해하기가 쉬워졌습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;끝으로..&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게나마, 정리가 필요해보이는 코드를 나름의 근거를 가지고 어떻게 나누고, 평가해야할지 알아보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 통합테스트, 컨트롤러의 테스트 및 추가적인 처리사항등 공유할내용이 많습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이는 단위테스트의 영역을 벗어나기 때문에, 추후 기회가 되어 한번 더 다루었으면 좋겠습니다.&lt;/p&gt;</description>
      <category>영웅칼럼</category>
      <author>HEROHJK</author>
      <guid isPermaLink="true">https://herohjk.tistory.com/76</guid>
      <comments>https://herohjk.tistory.com/76#entry76comment</comments>
      <pubDate>Thu, 2 Mar 2023 12:28:43 +0900</pubDate>
    </item>
    <item>
      <title>단위테스트를 위한 리팩토링 준비하기</title>
      <link>https://herohjk.tistory.com/75</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;앞에서는 단위테스트를 어떻게 평가해야하는가? 에 대해 알아봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 그럼 리팩토링단계로 넘어가서, 어떻게 리팩토링해야 단위테스트를 수월하게 할수 있는지를 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 아키텍처&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1.1 함수형 아키텍처&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1166&quot; data-origin-height=&quot;902&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JbiLU/btr0sBFCggk/QACL0IpKVLMQ7v2a1GOQ0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JbiLU/btr0sBFCggk/QACL0IpKVLMQ7v2a1GOQ0k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JbiLU/btr0sBFCggk/QACL0IpKVLMQ7v2a1GOQ0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJbiLU%2Fbtr0sBFCggk%2FQACL0IpKVLMQ7v2a1GOQ0k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1166&quot; height=&quot;902&quot; data-origin-width=&quot;1166&quot; data-origin-height=&quot;902&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결정을 내리는 로직 코드와, 결정에 따라 작용하는 코드(주로 입력과 출력)를 나누는 일종의 마이크로서비스 패턴입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 가변 셀이 모든 입력을 수집후 코어로 보내고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 코어는 입력받은 데이터를 토대로 로직을 수행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 계산된 값들은 다시 가변 셀로나와 사이드이펙트로 변환됩니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1.1.1 함수형 프로그래밍?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수형 아키텍처 이전에, 함수형 프로그램을 먼저 이해하는게 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예전에 제가 Rx Swift에 관한 영상을 제작한적이 있는데요, 도입부를 보다보면 함수형 프로그래밍에 관한 설명이 나옵니다.&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=3_dyVj4HcYk&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/mkPxY/hyRIxL8osC/i07CRCtWqj4mRsAZSRMdTk/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/3_dyVj4HcYk&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필요한 부분만 요약 하자면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 함수를 일급 객체로 취급. (= 수학적 순수 함수)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 함수가 불변성을 가짐. (전역 상태에 의존하지 않아, 참조 투명성을 가짐)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpZNWl/btr0o7rS59z/UugisJK9jGs0CLJUfw7va0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpZNWl/btr0o7rS59z/UugisJK9jGs0CLJUfw7va0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpZNWl/btr0o7rS59z/UugisJK9jGs0CLJUfw7va0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpZNWl%2Fbtr0o7rS59z%2FUugisJK9jGs0CLJUfw7va0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;720&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 설명드렸던 출력 기반 테스트와 궤가 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1.1.3 단점..&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기능을 최대한 쪼개어 사용하기 때문에 코드베이스가 커집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 구현시간도 늘어나며, 잘 설계가 되지 않을 때 그만큼 중첩이 되고, 성능에 영향이 갑니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 현실적으로, 모든 코드를 함수형 아키텍처로 전환하기는 어렵습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1.2 육각형 아키텍처&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;491&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/miijs/btr0oljVhty/fAklkzHcV5l0ViJJYrbBf1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/miijs/btr0oljVhty/fAklkzHcV5l0ViJJYrbBf1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/miijs/btr0oljVhty/fAklkzHcV5l0ViJJYrbBf1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fmiijs%2Fbtr0oljVhty%2FfAklkzHcV5l0ViJJYrbBf1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;491&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;491&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 또한 비즈니스 로직을 중심으로 계층화하는 마이크로서비스 패턴입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구현세부사항 같은 코어 로직과 도메인계층을 분리하는데요,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도메인계층 -&amp;gt; 코어 로직으로 들어갈 때에는 단방향으로 흐릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부의 서비스와 통신을 할때에는 도메인계층에서만 통신이 되구요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1.2.1 함수형 아키텍처와 비교?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;'입력 -&amp;gt; 처리 -&amp;gt; 출력'이라는 단방향의 흐름은 같습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분리의 경우..&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;함수형 아키텍쳐는 Core, 즉 &lt;b&gt;메인 로직과 메인 로직이 아닌것을 분리&lt;/b&gt;합니다.&lt;/li&gt;
&lt;li&gt;육각형 아키텍처는 &lt;b&gt;도메인 계층, 서비스 계층 모두에서 사이드 이펙트를 구분하여 분리&lt;/b&gt;합니다. (인터페이스)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 리팩토링할 코드 식별하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자 이제 테스트를 위한 아키텍처도 알아보았으니, 현재 코드를 어떻게 리팩토링해야할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 어떻게 고쳐야할까를 생각해봐야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단.. 어떤걸 리팩토링해야하는지 식별하는게 중요합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.1 코드의 네가지 유형&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크게, '복잡도 및 도메인 유의성'과, '협력자 수'를 기준으로 식별하여 분리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;해당 코드가 프로젝트에서 개선이 필요한 코드의 도메인과 얼마나 연관이 있는가&lt;/b&gt;를 판단합니다. 그것을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;도메인 유의성&lt;/b&gt;이라고 하며, 그것을 확인하다보면 코드가 정리가 안되 뭉쳐져 있는 경우가 있습니다. 따라서 한꺼번에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;복잡도 및 도메인 유의성&lt;/b&gt;으로 구분합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부의 의존성을 제외한 가변 의존성과, 외부 의존성의 경우 테스트 비용이 증가합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;협력자가 외부 협력자일 경우도 테스트 비용이 증가합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1094&quot; data-origin-height=&quot;656&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/87RjF/btr0qZf8P3C/S0GiW8RMH16uEcxSbow9Ik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/87RjF/btr0qZf8P3C/S0GiW8RMH16uEcxSbow9Ik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/87RjF/btr0qZf8P3C/S0GiW8RMH16uEcxSbow9Ik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F87RjF%2Fbtr0qZf8P3C%2FS0GiW8RMH16uEcxSbow9Ik%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1094&quot; height=&quot;656&quot; data-origin-width=&quot;1094&quot; data-origin-height=&quot;656&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;도메인 모델과 알고리즘&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이부분이 단위테스트에서 테스트할 항목입니다. 적은 비용으로 가장 큰 가치를 얻을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;간단한 코드&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 생성자, 한줄짜리 속성등 한눈에 이해가 가능한 아주 간단한 코드들입니다. 비용을 생각해서, 특별한 테스트가 필요하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;컨트롤러&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부 서비스를 조정하거나, 내부 비즈니스로직들을 조합하여 서비스를 컨트롤하는 부분입니다. 이부분은 단위테스트가 아닌 통합테스트가 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;지나치게 복잡한 코드&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이부분이 리팩토링이 필요한 부분입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어서 iOS 개발의 ViewController과 같이, 지나치게 Massive한 코드이며, 이 코드를 위 3가지 항목으로 구분하여 최대한 작게 분리하는게 중요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 컨트롤러에서 로직이 포함되면, 이 항목인 &lt;b&gt;지나치게 복잡한 코드가&lt;/b&gt; 되기 쉽습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.2 험블 객체 패턴&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지나치게 복잡한 코드는 여러 기능들이 섞여있는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;'테스트를 하기 위해 리팩토링'을 하는 관점에서&lt;/b&gt; 볼때는 단순하게 &lt;b&gt;'테스트 하기 쉬운 행위', '테스트 하기 어려운 행위'로 나눌수 있겠습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때, &lt;b&gt;'테스트 하기 어려운 행위'를 '테스트 하기 쉬운 행위'로 분리하기 위해 로직과 인터페이스들을 단일 책임 원칙에 따라가도록 리팩토링&lt;/b&gt;을 하게 되는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것을 &lt;b&gt;험블 객체 패턴&lt;/b&gt;이라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아주 간단하게 나누는 방법을 설명하자면..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 인터페이스와 같이 외부와 연결되는 것들은 테스를 하기가 어렵습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 중요한 로직과 그 외의 것들을 분리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 위에서 소개한 함수형 아키텍처, 육각형 아키텍처 처럼 모양이 잡힙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 그림처럼요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;675&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/egXWjh/btr0o6fNC5y/JJoMyKhqa0qBD7Y0C12kqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/egXWjh/btr0o6fNC5y/JJoMyKhqa0qBD7Y0C12kqK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/egXWjh/btr0o6fNC5y/JJoMyKhqa0qBD7Y0C12kqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FegXWjh%2Fbtr0o6fNC5y%2FJJoMyKhqa0qBD7Y0C12kqK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;675&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;675&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱에서는 이런식으로 구분된 디자인 패턴들이 많죠? MVC, MVP, MVVM등..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 패턴들도 잘 생각해보면.. 뷰, 모델, 로직을 분리하는데 특화되어있고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국은&amp;nbsp;&lt;b&gt;지나치게 복잡한 코드&lt;/b&gt;를 분리하는것으로 귀결됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지가 테스트를 수월하게 도입하기 위한 리팩토링 방법 준비에 대한 설명인데요,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 마지막 포스트로, 위의 가이드대로 리팩토링을 한번 진행해보고, 그것을 앞서 말한 평가 방법으로 평가하는 것까지 설명드리겠습니다~&lt;/p&gt;</description>
      <category>영웅칼럼</category>
      <author>HEROHJK</author>
      <guid isPermaLink="true">https://herohjk.tistory.com/75</guid>
      <comments>https://herohjk.tistory.com/75#entry75comment</comments>
      <pubDate>Thu, 23 Feb 2023 10:58:31 +0900</pubDate>
    </item>
    <item>
      <title>단위테스트, 어떤 항목을 평가해야 할까?</title>
      <link>https://herohjk.tistory.com/74</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;앞 포스트에서 단위테스트가 무엇이고, 왜 해야하는지 알아봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단기성 프로젝트가 아닌 이상, 지속적인 유지보수를 해야하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리팩토링은 대표적인 유지보수 방법입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 그 리팩토링이 과연 효과가 있었는가? 를 확인하기 위해 단위테스트를 이용한다고 설명을 했었는데요,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 단위테스트는 어떻게 만들고 어떻게 평가를 해야할까? 에 대해 포스팅합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 단위테스트의 구조&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1.1 AAA 패턴&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단위테스트는 대표적으로 AAA패턴을 사용하여 진행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AAA 패턴이란, 준비(Arrange) - 실행(Act) - 검증(Assert)의 순서로 진행되는 패턴입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1677075655928&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class CalculatorTests {
    public void test_두_수의_합을_검증() { // 알아보기 쉽게, 읽기 쉽게 작성하는걸 추천하기에, 한글 사용을 권장합니다.
        /// 준비
        let first: Double = 10
        let second: Double = 20
        var calculator = Calculator()

        /// 실행
        let result = calculator.sum(first, second)

        /// 검증
        XCAssertEqual(30, result)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;준비 구절&lt;/b&gt;에서는 테스트를 진행하려는 &lt;b&gt;테스트 대상 시스템&lt;/b&gt;과, 의존성들을 원하는 상태로 세팅합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(이후 테스트 대상 시스템은 &lt;b&gt;SUT(System Under Test)&lt;/b&gt;라고 표현하도록 합니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실행 구절&lt;/b&gt;에서는 SUT에서 테스트가 필요한 메서드를 호출하고, 미리 준비한 의존성을 전달하여 출력이 있는 경우 출력값을 저장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;검증 구절&lt;/b&gt;에서는 저장된 출력값과 예상하는 값을 비교하여 검증합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;이 때, AAA 항목들이 여러개로 나누어지면, 단위테스트가 아닌 통합테스트가 됩니다.&lt;br /&gt;따라서 &lt;b&gt;여러개의 동작단위를 검증하는것은 지양&lt;/b&gt;하도록 합니다.&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1.2 구절의 크기?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트코드에서&lt;b&gt; if문을 사용한다면, 위에서 말한 여러개의 동작 단위를 검증하게 될 수도 있기에 지양&lt;/b&gt;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;준비구절이 보통 가장 큽니다만, &lt;b&gt;너무 커진다면 오브젝트 마더, 테스트 데이터 빌더 등 빌더 패턴을 사용&lt;/b&gt;하여 테스트 코드에서 분리하도록 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실행 구절은 되도록이면 한번만 실행&lt;/b&gt;하며, &lt;b&gt;한번이 넘어간다면 베이스코드를 의심해 리팩토링을 준비&lt;/b&gt;하는게 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'하나의 기능'만 테스트한다는 가정 하에, &lt;b&gt;검증구절은 몇개가 있어도 상관 없습니다 만&lt;/b&gt;..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그래도 많아진다 싶으면 리팩토링을 준비&lt;/b&gt;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(추상화, 비교 메서드 등을 점검 해 봅시다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 좋은 단위 테스트의 4대 요소&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단위테스트를 평가하는 네가지 지표가 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.1 회귀 방지&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 내가 리팩토링을 통해 코드를 수정했는데, &lt;b&gt;의도대로 작동하지 않는 경우 다시 원상복귀를 시키는 경우&lt;/b&gt;가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것을 회귀라고 하며, 단위테스트를 평가할 때 해당 코드가 회귀를 해야하나도 평가하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;코드의 양이 많아지거나, 코드가 복잡해지는 경우&lt;/b&gt; 회귀가 발생할 가능성이 높습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더군다나 &lt;b&gt;복잡한 비즈니스 로직은 시스템의 핵심과도 같은데, 여기서 발생하는 버그는 시스템에 가장 큰 피해를 입힙니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 SUT의 &lt;b&gt;해당 코드가 얼마나 도메인에 적합한지도 확인&lt;/b&gt;을 해봐야 합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.2 리팩터링 내성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단위테스트를 진행하다보면, 분명 리팩터링을 한 &lt;b&gt;코드는 잘 작동하는데.. 단위테스트에서 실패하는 경우&lt;/b&gt;가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것을 &lt;b&gt;거짓 양성(false positive)&lt;/b&gt;이라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SUT가 구현의 세부사항과 강하게 결합&lt;/b&gt;이 되어있을 때 주로 발생합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적으로 이 리팩터링 내성, 거짓양성이 가장 악질적인 케이스라고 생각을 하는데요.. 제가 생각하는 이유는 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두가지 케이스가 있는데요..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 일단은 일정이 중요하다. 마감이 얼마 남지 않았고, 기능작동에는 문제가 없으니 테스트는 제거하고 나중에 다시 들여다보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 아니다.. 테스트에 통과하지 못하는것은 내가 아직 발견하지 못한 무언가의 이유가 있을것이다.. 일단 코드를 원상복귀 시킨 후 나중에 다시 들여다보자..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네.. 이렇게 둘이서 러시안 룰렛을 하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 둘중 하나만 살아남는(테스트 코드 or 리팩터링된 코드) 비극이 발생합니다..&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.3 빠른 피드백&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;더 많은 테스트를, 더 자주 수행할수 있기 때문&lt;/b&gt;에 테스트는 빠를수록 좋습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.4 유지보수성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;테스트코드는 이해하기 쉬울수록 좋습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가독성을 부숴트리고 라인을 억지로 줄인다거나.. 변수명을 억지로 짧게만드는등 &lt;b&gt;억지로 압축을 하지 않는 선에서 간결하고 잘게 쪼개서 진행&lt;/b&gt;하도록 합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.5 이상적인 테스트?&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;329&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cSQmvm/btr0nNm0UtO/73kwtuvE0WbFhc1L5694v0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cSQmvm/btr0nNm0UtO/73kwtuvE0WbFhc1L5694v0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cSQmvm/btr0nNm0UtO/73kwtuvE0WbFhc1L5694v0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcSQmvm%2Fbtr0nNm0UtO%2F73kwtuvE0WbFhc1L5694v0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;329&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;329&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 네가지 요소를 지키면서 리팩터링을 진행하고, 테스트코드를 작성하는건 이상에 가깝습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 리팩터링 내성을 최대한 유지하면서, 회귀 방지와 빠른 피드백사이에서 절충하는 방법을 일반적으로 추천하더라구요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 대역?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트를 진행하는데 SUT가 아닌 SUT에 필요한 의존성들은 &lt;b&gt;대역&lt;/b&gt;으로 처리하는 경우가 많습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;상황에 따라 단위테스트 코드안에 모든 의존성들을 준비하기가 어렵기 때문&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.1 종류들&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단위테스트에 관심이 있다면 Mock, 목 이라는 말을 많이 들어보셨을텐데요..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목은 대역의 일부입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 아래처럼 세분화가 가능합니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot; rowspan=&quot;2&quot;&gt;목 (Mock)&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;목&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;스파이&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot; rowspan=&quot;3&quot;&gt;스텁 (Stub)&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;스텁&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;더미&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;페이크&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3.1.1 목&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목은 SUT에서 &lt;b&gt;외부로 나가는&lt;/b&gt; &lt;b&gt;상호작용을 대체&lt;/b&gt; 하는데 도움이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 명령의 역할을 가지고 있습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3.1.2 스텁&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스텁은 외부에서 SUT의 &lt;b&gt;내부로 들어오는 상호작용을 모방&lt;/b&gt;하는데 도움이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 조회의 역할을 가지고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1677078044232&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/// Mock의 예시
func test_이메일전송() {
    var mock = Mock&amp;lt;EmailGateway&amp;gt;()
    var sut = Controller(mock.object)

    sut.greetUser(&quot;user@email.com&quot;)

    // SUT에서 사이드 이펙트를 발생시킬 때(이메일 전송), 미리 만들어둔 Mock 대역으로 사용.
    XCTAssertEqual(mock.sendGreetingsEmail(&quot;user@email.com&quot;), true) 
}

/// Stub의 예시
func test_report_생성() {
    var stub = Mock&amp;lt;Database&amp;gt;()

    // dummy의 개념으로, 미리 10명의 유저를 생성
    stub.setUp { $0.GetNumberOfUsers() }.returns(10)

    var sut = Controller(stub.object)

    let report = sut.createReport()

    XCTAssertEqual(10, report.numberOfUsers)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두가지는 보통 &lt;b&gt;단위테스트 라이브러리에서 묶여있는 경우가 많습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이를 잘 구분할줄 알아야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 스타일&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4.1 출력 기반&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;435&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/v2NSs/btr0n8Eu2Hi/Y7oFUWI9kIRck7auylpek1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/v2NSs/btr0n8Eu2Hi/Y7oFUWI9kIRck7auylpek1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/v2NSs/btr0n8Eu2Hi/Y7oFUWI9kIRck7auylpek1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fv2NSs%2Fbtr0n8Eu2Hi%2FY7oFUWI9kIRck7auylpek1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;435&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;435&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력을 넣고 생성되는 출력을 점검하며, 불변성을 가진 함수형 프로그래밍에서 검증하기가 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 간결하고 명확하지만, 조건을 만들기가 쉽지는 않습니다..&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4.2 상태 기반&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1226&quot; data-origin-height=&quot;474&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rsF2N/btr0qsvoJQp/IFyBpaO0KigDAKGverctvk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rsF2N/btr0qsvoJQp/IFyBpaO0KigDAKGverctvk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rsF2N/btr0qsvoJQp/IFyBpaO0KigDAKGverctvk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrsF2N%2Fbtr0qsvoJQp%2FIFyBpaO0KigDAKGverctvk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1226&quot; height=&quot;474&quot; data-origin-width=&quot;1226&quot; data-origin-height=&quot;474&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 실행 후 시스템의 상태들을 변화를 점검합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4.3 통신 기반&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1118&quot; data-origin-height=&quot;484&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sFkyk/btr0meyBiS3/1o2gr7K6297axvHKsJfTnK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sFkyk/btr0meyBiS3/1o2gr7K6297axvHKsJfTnK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sFkyk/btr0meyBiS3/1o2gr7K6297axvHKsJfTnK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsFkyk%2Fbtr0meyBiS3%2F1o2gr7K6297axvHKsJfTnK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1118&quot; height=&quot;484&quot; data-origin-width=&quot;1118&quot; data-origin-height=&quot;484&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목을 사용하여 SUT와 협력자(목)간의 통신을 검증합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4.4 단위테스트의 4대 요소를 기반으로 비교.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 설명한 좋은 단위테스트의 4대요소를 기반으로 스타일마다 어떤 특성이 있는가를 비교해봅니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4.4.1 회귀방지, 빠른 피드백의 지표로 비교&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실행되는 코드의 양, 도메인 유의성
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;셋 모두 관계 없습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;코드 복잡도
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테스트 시 SUT외의 모든것을 Mock으로 사용할 가능성이 있지만..&lt;/li&gt;
&lt;li&gt;이는 각 스타일의 문제점이 아닌 테스트 코드를 좋지 않게 작성한 경우입니다.&lt;/li&gt;
&lt;li&gt;따라서 복잡도도 크게 관계가 없습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;빠른 피드백
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;외부 의존성과 연결되어있지 않는 한, 세 스타일 모두 유의미한 차이는 없습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, &lt;b&gt;테스트코드를 정상적으로 작성한다는 가정&lt;/b&gt; 하에, 회귀방지와 빠른 피드백의 경우 &lt;b&gt;세 스타일 모두 큰 차이는 보이지 않습니다.&lt;/b&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4.4.2 리팩터링 내성 지표로 비교&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리팩터링 내성은 곳 거짓 양성이 얼마나 적은가에 대한 척도입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구현 세부사항과 결합이 강할수록 거짓 양성의 발생 확률이 올라가는데요..&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;출력 기반
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코드 자체가 불변성을 띄고있기 때문에, 거짓 양성 방지에 가장 우수합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;상태 기반
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테스트와 SUT코드의 결합도가 높을수록, 구현 세부사항과 얽매일 가능성이 높아, 상대적으로 위험해집니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;통신 기반
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Mock을 상대적으로 많이 사용하기 때문에, 세가지 스타일중 거짓양성 발생에 가장 취약합니다.&lt;/li&gt;
&lt;li&gt;따라서 Mock이 되는 협력 의존성을 잘 설계할 필요가 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4.4.3 유지보수성 지표로 비교&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유지보수성은 얼마나 크고 얼마나 복잡한가를 확인하는데요..&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;출력 기반
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;대부분의 코드가 짧고 간결하기에, 유지보수가 가장 쉽습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;상태 기반
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테스트를 위해 더 많은 코드를 짜야하기 때문에.. 출력 기반에 비해 상대적으로 유지보수가 어렵습니다.&lt;/li&gt;
&lt;li&gt;헬퍼 메서드를 사용해 완화할수는 있지만, 결국 이로 인해 비용이 더 증가하게 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;통신 기반
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Mock과의 상호작용 검증이 필요하므로, 가장 유지보수가 어렵습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4.4.4 결론&lt;/h4&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;지표&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;출력 기반&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;상태 기반&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;통신 기반&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;리팩터링 내성을 지키기 위한 노력&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;낮음&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;중간&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;중간&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;유지보수 비용&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;낮음&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;중간&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;높음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 출력 기반 -&amp;gt; 상태기반 -&amp;gt; 통신기반의 순으로 4대요소에 부합하게 됩니다.&lt;/p&gt;</description>
      <category>영웅칼럼</category>
      <author>HEROHJK</author>
      <guid isPermaLink="true">https://herohjk.tistory.com/74</guid>
      <comments>https://herohjk.tistory.com/74#entry74comment</comments>
      <pubDate>Wed, 22 Feb 2023 22:45:51 +0900</pubDate>
    </item>
    <item>
      <title>단위테스트가 무엇이고, 왜 해야할까?</title>
      <link>https://herohjk.tistory.com/73</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;(단위테스트 책 스터디 후 발표자료를 공유하기 위해 작성했습니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 10월 ~ 1월까지 &lt;a href=&quot;https://enterprisecraftsmanship.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;블라디미르 코리코프&lt;/a&gt;의 &lt;a href=&quot;http://www.yes24.com/Product/Goods/104084175&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;단위테스트&lt;/a&gt; 책을 가지고 북스터디를 진행했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;단위테스트(Unit Test)란 무엇일까요?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소프트웨어 테스트 방식 중 하나로, 독립적으로 작동하는 코드 단위 중 가장 작은 코드를 테스트하는 것 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 기능을 최대한 빠르고 가장 정확하게 테스트 하는 것을 목표로 하며, 이것들이 보장되어야 후에 진행할 통합테스트가 수월해집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;테스트피라미드.png&quot; data-origin-width=&quot;381&quot; data-origin-height=&quot;329&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ca3pgu/btrYrnpI6WJ/NqNntHlb9xuYtEkx5r1AGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ca3pgu/btrYrnpI6WJ/NqNntHlb9xuYtEkx5r1AGK/img.png&quot; data-alt=&quot;엔드투엔드 -&amp;amp;gt; 통합 -&amp;amp;gt; 단위로 이루어진 테스트 피라미드&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ca3pgu/btrYrnpI6WJ/NqNntHlb9xuYtEkx5r1AGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fca3pgu%2FbtrYrnpI6WJ%2FNqNntHlb9xuYtEkx5r1AGK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;381&quot; height=&quot;329&quot; data-filename=&quot;테스트피라미드.png&quot; data-origin-width=&quot;381&quot; data-origin-height=&quot;329&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;엔드투엔드 -&amp;gt; 통합 -&amp;gt; 단위로 이루어진 테스트 피라미드&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;그러면 왜 단위 테스트가 필요할까요? - 실패한 리팩토링 시나리오&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 제품이 지속될수록 점점 길어지는 일정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단위 테스트 '만' 놓고 본다면 중요성은 그다지 크지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작은 코드 '만' 놓고 본다면, 실수할 여지도 크지 않고, 그저 발견된다면 고치면 되니까요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어차피 테스트를 해주는 테스터 팀도 있고, 여태까지 우리 회사의 제품은 잘 굴려져 왔기에, 이번에도 어떻게든 잘 굴러갈거라고 믿어 의심치 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분명 우리 제품의 초창기에는 기능의 추가나 수정요청이 들어오면.. 기능의 추가나 수정요청에 대한 일정이 합리적이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소프트웨어를 개발하는 개발자도 납득이되고, 제품 개발에 참여하는 다른 팀원분들도 충분히 납득이 되는 시간과 기능입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발을 하고, 어디 빠진부분은 없나 제법 꼼꼼하게 체크도 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중간중간 아주 사소한 마찰이 있을수는 있겠지만.. 아주 사소한 마찰일 뿐 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 생각해보면 뭔가 이상합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1년이 지나고 2년이 지나고.. 마이너, 메이저버전이 올라가면 올라갈수록, 비슷한 규모의 기능 추가 / 수정이 개발자 입장에서는 점점 납득이 되지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이걸 설명하기는 귀찮습니다. 일단은 요구사항대로 개발을 진행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;애로사항.jpeg&quot; data-origin-width=&quot;472&quot; data-origin-height=&quot;297&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2DKsY/btrYquXAVBy/9An4DgHEdbbP2u0NrmbXkk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2DKsY/btrYquXAVBy/9An4DgHEdbbP2u0NrmbXkk/img.jpg&quot; data-alt=&quot;시켜서 하긴 하는데.. 자꾸 여기저기 걸리는부분이 많습니다..&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2DKsY/btrYquXAVBy/9An4DgHEdbbP2u0NrmbXkk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2DKsY%2FbtrYquXAVBy%2F9An4DgHEdbbP2u0NrmbXkk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;472&quot; height=&quot;297&quot; data-filename=&quot;애로사항.jpeg&quot; data-origin-width=&quot;472&quot; data-origin-height=&quot;297&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;시켜서 하긴 하는데.. 자꾸 여기저기 걸리는부분이 많습니다..&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중간중간 애로사항이 많이 꽃핍니다. 하지만 그래도 개발을 계속 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기 설계에서 염두에두지 못한 기능이라 설계를 우회해야하고, 지금 당장 이 코드를 어디에 분류할지 몰라서 일단 바깥쪽에 둡니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;미래의 내가 어떻게든 해줄겁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것은 절대 제가 게을러서 그런게 아닙니다. 시간이 모자란겁니다. 시간으로부터, 사회로부터 억까를 당하고 있는겁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 기능개발을 또 하나 둘씩 마칩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 회고를 해 보니..&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발을 마치고 회고를 해봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(거창하게 생각할것 없이 그냥 왜 이런가 생각만이라도 해볼수는 있으니까요)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더이상 미룰 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조 개선을 해야만 할 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조금 더 지금 상황에 유연하게 대처가 가능한 구조를 짜고, 미뤄왔던 코드들(기술부채)을 정리하기로 결심합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 리팩토링?!&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리팩토링에 대한 시간을 보장받기는 상당히 어렵습니다. 그래도 반드시 필요한작업입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통은 개발 일정을 길게 잡아 남는 시간에, 혹은 정말 어렵게 어렵게 시간을 보장받아 진행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;후자라면 다행이겠지만.. 전자라면 그것 마저도 시간이 모자랍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대충 머릿속에서 생각해왔던 방식으로 구조 개선을 진행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 코드를 깔짝깔짝 건드리면서 리팩토링을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 그럴싸하게 됩니다. 깔짝깔짝 건드리지만 나름 합리적으로 보입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동작에도 크게 문제는 없어보이네요. 다른코드도 하나둘 건드려 봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내 생각과 다른부분이 조금씩 발견되지만.. 크게 이상한부분은 없어보이고, 여기까지 와서 중단하기도 좀 그렇습니다..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러므로 계속해서 리팩토링을 진행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.1 진행한 리팩토링이 가치가 있었을까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의심되는 부분이 있었지만 어쨋든 리팩토링을 마쳤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 업무를 벗어난, 뭔가 코드에 대한 퀄리티를 높이는 작업이었기에 나름 성장했다는 기분도 들고 아주 뿌듯합니다 ㅎㅎ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 리팩토링의 가치는 무엇일까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드가 유연해졌기에 추후 작업에 대한 일정을 단축시킬수 있을까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조적 문제로 생긴 버그가 줄어들었을까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다 끝내고 보니, 업그레이드 되어있어야할 코드가 옆그레이드가 된건 아닐까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과연 그것을 어떻게 판단할 수 있을까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 리팩토링 이후에..&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 추가 개발건이 들어옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리팩토링한 코드를 검증할수 있는기회입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;'시간을 얼마나 줄일수 있을까?' &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;'얼마나 간단하게 추가할 수 있을까?' &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;'얼마나 깔끔하게 추가할 수 있을까?'&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 요구사항을 들어보니 내 바램과 달라도 너무 다릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아.. 내가 개선한 코드가 빛을 보지 못합니다 ㅠㅠ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일정에 변화는 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여전히 이상하게 걸리는 부분들이 있고, 기술부채를 힘들게 해결한게 엊그제인데,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해결하자마자 또다른 기술부채가 쌓이게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 어떻게든 되겠지.. 라고 위로하며 여차저차 개발 마무리 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;힘겹고 고통스러운 개발 이후, 테스트 기간때 기둥 너머로 테스터분들의 눈치를 살핍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;표정이 안좋습니다. 얼굴이 울긋불긋 해집니다. 하필 그 타이밍에 저를 쳐다봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;미어캣 합성.jpg&quot; data-origin-width=&quot;690&quot; data-origin-height=&quot;391&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVu43i/btrYrTPBgIY/k2mn8LHMiOa69NYGKoqkE1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVu43i/btrYrTPBgIY/k2mn8LHMiOa69NYGKoqkE1/img.jpg&quot; data-alt=&quot;동료들과는 완만하게 지내는게 좋습니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVu43i/btrYrTPBgIY/k2mn8LHMiOa69NYGKoqkE1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVu43i%2FbtrYrTPBgIY%2Fk2mn8LHMiOa69NYGKoqkE1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;690&quot; height=&quot;391&quot; data-filename=&quot;미어캣 합성.jpg&quot; data-origin-width=&quot;690&quot; data-origin-height=&quot;391&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;동료들과는 완만하게 지내는게 좋습니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 메시지로 전달해 줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;'기존 기능이 작동을 안하네요~'&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음에는 자리에 와서 아주 상냥하게 여쭤봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;'이 기능이 이렇게 바뀌었는데 왜 이러는걸까요~? ^^'&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자리에 찾아오는 빈도가 높아집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나도, 테스터도 퇴근하는 시간이 점점 늦어집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;말투도 건조해져 갑니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;'아직이에요?'&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;'네..'&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;'언제 돼요?'&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;'...방 돼요...'&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;'뭐라고요? 크게좀 말해봐요'&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;'.... 금방 돼요...'&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;김정은.jpeg&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;523&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btmVzg/btrYr95PDRE/lU0UMgtd7kwHD2xSASfwKk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btmVzg/btrYr95PDRE/lU0UMgtd7kwHD2xSASfwKk/img.jpg&quot; data-alt=&quot;계획에 없는 야근때문에 예민해진 테스터와, 최대한 눈을 안마주치려고 노력하는 개발자, 그리고 무슨일인가 싶어 구경하는 동료 개발자들.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btmVzg/btrYr95PDRE/lU0UMgtd7kwHD2xSASfwKk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtmVzg%2FbtrYr95PDRE%2FlU0UMgtd7kwHD2xSASfwKk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;523&quot; data-filename=&quot;김정은.jpeg&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;523&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;계획에 없는 야근때문에 예민해진 테스터와, 최대한 눈을 안마주치려고 노력하는 개발자, 그리고 무슨일인가 싶어 구경하는 동료 개발자들.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;네.. 이번 리팩토링은 망했습니다. &lt;/b&gt;&lt;b&gt;개발 일정 단축도, 버그를 잡는것도 모두 실패했습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;무엇이 문제였으며, 이게 테스트와 무슨 상관이 있을까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;힘들게,, 어쩌면 개인 시간까지 투자해가며 리팩토링을 진행하는경우가 상당한데, 그 리팩토링이 과연 효율적인가? 성공적인가? 를 판단해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 리팩토링의 성공 확률을 올려야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;단위테스트와 통합테스트를 이용한 개발 및 유지보수는 그것에 대한 아주 좋은 방법입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;테스트를 포함한 개발을 처음 한다면, 절대로 쉽지 않습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;단위테스트를 위한 작은 구조를 만들어야 하고,&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;지금 작성한 코드가 단위테스트의 영역인지, 통합테스트의 영역인지 구분해야 합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구분이 안가면 분리해야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음의 높은 러닝 커브(Learning Curve, 학습 곡선)를 감내하더라도, 초기 개발 이후 제품이 지속되는 단계에서는 그 초기 투자 비용이 빛을 볼수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적으로 생각하는 테스트를 포함한 개발은 어떤 이점이 있는지 생각 해 봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;1. 단위테스트, 통합테스트, Mockable 등을 구분하기 위해 코드를 명확하게 분리 하는것을 항상 염두에 둔다. 따라서 코드의 구분이 상대적으로 명확해진다.&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;2. 코드의 구분이 명확해지므로 이후 추가 개발, 수정에 대한 부담이 상대적으로 줄어든다.&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;3. 아주 기본적인 테스트를 개발자단계에서 진행하기에, 제품 자체를 테스트하는 테스터들의 부담이 그만큼 줄어들게 된다.&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;4. 따라서 테스트 기간이 짧아지며, 이는 제품 전체의 개발기간 단축으로 이어진다.&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;그래서, 개발할 때 테스트를 해야돼?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론부터 얘기해서, 해서 손해볼게 그다지 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음부터 완벽한 코드를 만들수는 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드는 지속적으로 리팩토링을 해야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이왕 리팩토링 하는거, 잘 하는게 중요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞에서 말했듯이, 테스트를 포함한 개발은 그것에 대한 명확하고 효과적인 방법중 하나입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;일정이 줄어든다고 생각이 되시나요?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;줄어들기 전의 일정은 원래부터 빠듯한 일정이었습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;코딩 측면에서는 모르겠지만, 최소한 제품 개발의 측면에선 그렇습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(아래는 만약에 PT자료나 영상을 만들게 되면 쓸 내용입니다. 아직 미완성..)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 좋아하는 시티즈 스카이라인이라는 게임으로 예시를 들어보겠습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;시스카.jpeg&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;576&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/71mFf/btrYtSWNfLf/9fZyWM6kFDZnJAN858caQ1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/71mFf/btrYtSWNfLf/9fZyWM6kFDZnJAN858caQ1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/71mFf/btrYtSWNfLf/9fZyWM6kFDZnJAN858caQ1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F71mFf%2FbtrYtSWNfLf%2F9fZyWM6kFDZnJAN858caQ1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1024&quot; height=&quot;576&quot; data-filename=&quot;시스카.jpeg&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;576&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;시티즈 스카이라인은 도시를 설계하는 게임입니다. 특히 도로 계획, 교통에 따른 트래픽에 많은 중점을 두고있는 꽤나 현실적인 게임입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(실제로 우리나라 몇몇 시/도청에서 저 게임으로 도로계획 공모전을 실시한적이 있습니다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게임을 하는 누구나 저 위에 스크린샷처럼 멋진 도시를 구현하고 싶어합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;시스카2.jpeg&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRteuX/btrYvmcmJeI/LjiEr8sVDUeg2fCSA15s9k/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRteuX/btrYvmcmJeI/LjiEr8sVDUeg2fCSA15s9k/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRteuX/btrYvmcmJeI/LjiEr8sVDUeg2fCSA15s9k/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRteuX%2FbtrYvmcmJeI%2FLjiEr8sVDUeg2fCSA15s9k%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;1080&quot; data-filename=&quot;시스카2.jpeg&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 이렇게 소박하게 시작을 합니다. 작게나마 도로를 만들고, 주거지역을 설정하고, 상가를 설정하고, 산업지역도 설정을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까진 괜찮습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 지속적으로 인구가 유입이 됩니다. 그러면 도시 확장을 해야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도로를 파고 다시 구획을 나눠야 하죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;미리 생각해둔 계획이 없거나.. 미리 생각해둔 계획이 있더라도 뭐든지 일들은 내 예상과 바램대로 흘러가지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;시스카3.jpeg&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZddLC/btrYqyMoJai/4rLBPs8tFyS9fRDe5vY33k/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZddLC/btrYqyMoJai/4rLBPs8tFyS9fRDe5vY33k/img.jpg&quot; data-alt=&quot;게임이기 때문에, 운석, 홍수같은 자연재해를 일으켜 다 없애 버리고 새로 시작해도 되긴 합니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZddLC/btrYqyMoJai/4rLBPs8tFyS9fRDe5vY33k/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZddLC%2FbtrYqyMoJai%2F4rLBPs8tFyS9fRDe5vY33k%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;1080&quot; data-filename=&quot;시스카3.jpeg&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;게임이기 때문에, 운석, 홍수같은 자연재해를 일으켜 다 없애 버리고 새로 시작해도 되긴 합니다.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;순식간에 교통이 정체가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;교통이 정체된다면 공공서비스가 중단이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소방차, 경찰차도 제시간에 운행되지 못하고 쓰레기차또한 마찬가지입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 잘못된 도시계획으로 가정이 무너지고 사회가 무너지게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
      <category>영웅칼럼</category>
      <author>HEROHJK</author>
      <guid isPermaLink="true">https://herohjk.tistory.com/73</guid>
      <comments>https://herohjk.tistory.com/73#entry73comment</comments>
      <pubDate>Wed, 8 Feb 2023 12:11:15 +0900</pubDate>
    </item>
    <item>
      <title>트랙패드 핀치(줌인/아웃) 버그</title>
      <link>https://herohjk.tistory.com/72</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;트랙패드를 사용하다보면 가끔 핀치(줌인, 줌아웃)기능이 안될때가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이럴때는 트랙패드를 껐다가 켜거나, 환겅설정 -&amp;gt; 트랙패드 -&amp;gt; 핀치 기능을 비활성화했다가 활성화를 해야지 버그가 풀립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;몇년전부터 모든 맥 환경에서 마우스를 없애고 트랙패드를 사용하기 시작했는데 스트레스가 이만저만이 아니었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 조금 찾아보다가, 터미널 커맨드로 해당 기능을 ON / OFF할 수 있는 방법을 찾아 스크립트와 알프레드 워크플로를 공유합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;
&lt;script src=&quot;https://gist.github.com/HEROHJK/17d2528ca69d298cd5c996c6964a8235.js&quot;&gt;&lt;/script&gt;
&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;fileblock&quot; data-ke-align=&quot;alignCenter&quot;&gt;&lt;a href=&quot;https://blog.kakaocdn.net/dn/dMmoJz/btrGxiteDye/mfsVOfxIAoNRNBokfC2R11/Fucking%20Trackpad.alfredworkflow?attach=1&amp;amp;knm=tfile.alfredworkflow&quot; class=&quot;&quot;&gt;
    &lt;div class=&quot;image&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;desc&quot;&gt;&lt;div class=&quot;filename&quot;&gt;&lt;span class=&quot;name&quot;&gt;Fucking Trackpad.alfredworkflow&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;size&quot;&gt;0.01MB&lt;/div&gt;
&lt;/div&gt;
  &lt;/a&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>영웅칼럼</category>
      <author>HEROHJK</author>
      <guid isPermaLink="true">https://herohjk.tistory.com/72</guid>
      <comments>https://herohjk.tistory.com/72#entry72comment</comments>
      <pubDate>Wed, 6 Jul 2022 09:46:35 +0900</pubDate>
    </item>
    <item>
      <title>알고리즘 풀 때 자주 쓰는 몇가지 테크닉</title>
      <link>https://herohjk.tistory.com/71</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 배열 순회중, 오버플로우 체크&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1652677570258&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (0 ..&amp;lt; arr.count).contains(i) {
  arr[i] = 
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. N ~ M까지 순회가 필요할 때&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1652677607918&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;(N ... M).forEach { 
  $0
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 나머지가 0인지 아닌지 판별할 때&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소수 판별이나 비슷한 문제에서, N까지 순회하지 마시고, N / 2 까지만 순회하세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;N/2를넘어가게되면 나머지값이 0이 나올 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 개인차긴한데, 0혹은 1부터 시작하지말고, 2부터 시작하는게 자신이 생각하는 논리에 조금 더 근접하다고 느껴집니다.&lt;/p&gt;
&lt;pre id=&quot;code_1652677798422&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func isPimeNumber(at: Int) -&amp;gt; Bool {
  for number in 2 ... at / 2 {
    if at % number == 0 { return false }
  }
  
  return true
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. 2개의 배열을 연관지어 처리할 때&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;zip 함수를 이용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1652678032228&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func mergeArray(arr1: [String], arr2: [String]) -&amp;gt; [String] {
    return zip(arr1, arr2).map { &quot;\($0)\($1)&quot; } // [&quot;A1&quot;, &quot;B2&quot;, &quot;C3&quot;, &quot;D4&quot;, &quot;E5&quot;]
}

print(mergeArray(arr1: [&quot;A&quot;, &quot;B&quot;, &quot;C&quot;, &quot;D&quot;, &quot;E&quot;], arr2: [&quot;1&quot;, &quot;2&quot;, &quot;3&quot;, &quot;4&quot;, &quot;5&quot;]))&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5. 배열들을 합칠 때&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;reduce를 이용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1652678101809&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

print(arr.reduce(0, +)) //55&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;6. 배열의 일부만 이용하고자 할 때&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1652678791370&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func arraySum(_ arr: [Int], _ left: Int, _ right: Int) -&amp;gt; Int {
  return arr[left ... right].reduce(0, +)
}

let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print(arraySum(arr, 2, 5)) // 18&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;7. 진법 변환&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 코테에서 이걸 허락할지는 모르겠는데, 아주 간단하게 변환이 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(연습을 하는 목적이라면 직접 구현하는 코드를 테스트하는 의도이기 때문에 추천하지는 않습니다..)&lt;/p&gt;
&lt;pre id=&quot;code_1652678304288&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;(2 ... 16).forEach {
    print(&quot;\($0)진법:&quot;)
    print(String(10, radix: $0))
}
/*
 2진법:
 1010
 3진법:
 101
 4진법:
 22
 5진법:
 20
 6진법:
 14
 7진법:
 13
 8진법:
 12
 9진법:
 11
 10진법:
 10
 11진법:
 a
 12진법:
 a
 13진법:
 a
 14진법:
 a
 15진법:
 a
 16진법:
 a
 */&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;8. 배열을 Stack or Queue 처럼 사용하기.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1652680166006&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

// 스택처럼 사용 (후입선출)
if let last = arr.popLast() { // 배열에 인자가 없을 경우 nil 반환
    arr.append(last)
}

// 큐처럼 사용 (선입선출)
let first = arr.removeFirst()
arr.append(first)


/// 하지만 인자가 없을 때 removeFirst를 하면 오버플로우가 발생하므로
/// 코딩테스트에서는 count 체크를, 실제 코딩에서는 extension을 만들어 사용하는것을 추천

if arr.count &amp;gt; 0 {
    let first = arr.removeFirst()
    print(first)
}

extension Array {
    mutating func safeRemoveFirst() -&amp;gt; Element? {
        if self.count &amp;gt; 0 {
            return self.removeFirst()
        } else {
            return nil
        }
    }
}

arr = [1]

if let first = arr.safeRemoveFirst() {
    print(first)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생각나는대로 또 나중에 적어볼께요&lt;/p&gt;</description>
      <category>IOS/Swift</category>
      <category>알고리즘</category>
      <author>HEROHJK</author>
      <guid isPermaLink="true">https://herohjk.tistory.com/71</guid>
      <comments>https://herohjk.tistory.com/71#entry71comment</comments>
      <pubDate>Mon, 16 May 2022 14:23:38 +0900</pubDate>
    </item>
    <item>
      <title>맥 설치 후 세팅</title>
      <link>https://herohjk.tistory.com/70</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;최근에 Karabiner가 말썽을 일으켜서..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이참에 세팅을 좀 정리하기로 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 키보드 세팅&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;a. 키보드 배열 변경&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-05-16 오전 10.20.26.png&quot; data-origin-width=&quot;780&quot; data-origin-height=&quot;708&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9oc5M/btrB6jJLwEr/ou0iaaL2zfElv7upQVm36k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9oc5M/btrB6jJLwEr/ou0iaaL2zfElv7upQVm36k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9oc5M/btrB6jJLwEr/ou0iaaL2zfElv7upQVm36k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9oc5M%2FbtrB6jJLwEr%2Fou0iaaL2zfElv7upQVm36k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;780&quot; height=&quot;708&quot; data-filename=&quot;스크린샷 2022-05-16 오전 10.20.26.png&quot; data-origin-width=&quot;780&quot; data-origin-height=&quot;708&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Command &amp;lt;-&amp;gt; Option 위치 변경&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;b. 한영키 윈도우처럼 맞추기&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;fileblock&quot; data-ke-align=&quot;alignCenter&quot;&gt;&lt;a href=&quot;https://blog.kakaocdn.net/dn/r9A8R/btrCiuwba5a/JMrn3ELsjWaBBqV2J3CXu0/com.changing.KeyRemapping.plist?attach=1&amp;amp;knm=tfile.plist&quot; class=&quot;&quot;&gt;
    &lt;div class=&quot;image&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;desc&quot;&gt;&lt;div class=&quot;filename&quot;&gt;&lt;span class=&quot;name&quot;&gt;com.changing.KeyRemapping.plist&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;size&quot;&gt;0.00MB&lt;/div&gt;
&lt;/div&gt;
  &lt;/a&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1652664148868&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt; 
&amp;lt;!DOCTYPE plist PUBLIC &quot;-//Apple//DTD PLIST 1.0//EN&quot; &quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;&amp;gt; 
&amp;lt;plist version=&quot;1.0&quot;&amp;gt; 
    &amp;lt;dict&amp;gt; 
        &amp;lt;key&amp;gt;Label&amp;lt;/key&amp;gt; 
            &amp;lt;string&amp;gt;com.changing.KeyRemapping&amp;lt;/string&amp;gt; 
        &amp;lt;key&amp;gt;ProgramArguments&amp;lt;/key&amp;gt; 
            &amp;lt;array&amp;gt; 
                &amp;lt;string&amp;gt;/usr/bin/hidutil&amp;lt;/string&amp;gt; 
                &amp;lt;string&amp;gt;property&amp;lt;/string&amp;gt; 
                &amp;lt;string&amp;gt;--set&amp;lt;/string&amp;gt; 
                &amp;lt;string&amp;gt;{&quot;UserKeyMapping&quot;:[ {&quot;HIDKeyboardModifierMappingSrc&quot;:0x7000000E7, &quot;HIDKeyboardModifierMappingDst&quot;:0x70000006D} ]}&amp;lt;/string&amp;gt;
            &amp;lt;/array&amp;gt; 
        &amp;lt;key&amp;gt;RunAtLoad&amp;lt;/key&amp;gt; 
            &amp;lt;true/&amp;gt; 
    &amp;lt;/dict&amp;gt; 
&amp;lt;/plist&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 파일을 받거나, 코드를 긁어서 ~/Library/LaunchAgents에 저장.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 이상하게 제가 쓰는 키보드는 좌우가 바뀌는지..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;HIDKeyboardModifierMappingSrc&quot; 코드를 E7이 아닌 E3로 해줘야 되더라구요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;c. 입력 소스 변경&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-05-16 오전 10.24.39.png&quot; data-origin-width=&quot;780&quot; data-origin-height=&quot;708&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYY7BV/btrCajQJaxH/6X38Rf2Ec5LGsqMkUkfNO0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYY7BV/btrCajQJaxH/6X38Rf2Ec5LGsqMkUkfNO0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYY7BV/btrCajQJaxH/6X38Rf2Ec5LGsqMkUkfNO0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYY7BV%2FbtrCajQJaxH%2F6X38Rf2Ec5LGsqMkUkfNO0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;780&quot; height=&quot;708&quot; data-filename=&quot;스크린샷 2022-05-16 오전 10.24.39.png&quot; data-origin-width=&quot;780&quot; data-origin-height=&quot;708&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변경했으면 사진처럼 F18로 변경~&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지 하면, Command, Option은 맥 기본 키배열과 동일하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오른쪽 Command는 한영키로 대체합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;d. 데스스토커 2를 위한 karabiner 설정&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아.. 카라비너를 안쓰기로 다짐했건만.. 키보드를 레이저의 데스스토커2로 바꿨는데.. 키매핑이 어려워 어쩔수없이 다시 사용중입니다.. ㅠ_ㅠ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 제 개인적인 매핑을 저장하기 위해 기록합니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;볼륨up 스크롤을 command, + 조합으로 대체 (텍스트 크기 조절용)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;볼륨down 스크롤을 command, - 조합으로 대체 (텍스트 크기 조절용)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재생 버튼을 command, r 조합으로 대체 (xcode 빌드용)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;볼륨버튼을 command, ctrl, q 조합으로 대체 (시스템 잠금용)&lt;/p&gt;
&lt;pre id=&quot;code_1730872416204&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
    &quot;description&quot;: &quot;데스스토커 전용&quot;,
    &quot;manipulators&quot;: [
        {
            &quot;conditions&quot;: [
                {
                    &quot;identifiers&quot;: [
                        {
                            &quot;is_keyboard&quot;: true,
                            &quot;is_pointing_device&quot;: true,
                            &quot;product_id&quot;: 663,
                            &quot;vendor_id&quot;: 1678
                        }
                    ],
                    &quot;type&quot;: &quot;device_if&quot;
                }
            ],
            &quot;from&quot;: { &quot;key_code&quot;: &quot;volume_increment&quot; },
            &quot;to&quot;: [
                {
                    &quot;key_code&quot;: &quot;equal_sign&quot;,
                    &quot;modifiers&quot;: [&quot;left_gui&quot;],
                    &quot;repeat&quot;: false
                }
            ],
            &quot;type&quot;: &quot;basic&quot;
        },
        {
            &quot;conditions&quot;: [
                {
                    &quot;identifiers&quot;: [
                        {
                            &quot;is_keyboard&quot;: true,
                            &quot;is_pointing_device&quot;: true,
                            &quot;product_id&quot;: 663,
                            &quot;vendor_id&quot;: 1678
                        }
                    ],
                    &quot;type&quot;: &quot;device_if&quot;
                }
            ],
            &quot;from&quot;: { &quot;key_code&quot;: &quot;volume_decrement&quot; },
            &quot;to&quot;: [
                {
                    &quot;key_code&quot;: &quot;hyphen&quot;,
                    &quot;modifiers&quot;: [&quot;left_gui&quot;],
                    &quot;repeat&quot;: false
                }
            ],
            &quot;type&quot;: &quot;basic&quot;
        },
        {
            &quot;conditions&quot;: [
                {
                    &quot;identifiers&quot;: [
                        {
                            &quot;is_keyboard&quot;: true,
                            &quot;is_pointing_device&quot;: true,
                            &quot;product_id&quot;: 663,
                            &quot;vendor_id&quot;: 1678
                        }
                    ],
                    &quot;type&quot;: &quot;device_if&quot;
                }
            ],
            &quot;from&quot;: { &quot;key_code&quot;: &quot;play_or_pause&quot; },
            &quot;to&quot;: [
                {
                    &quot;key_code&quot;: &quot;r&quot;,
                    &quot;modifiers&quot;: [&quot;left_gui&quot;],
                    &quot;repeat&quot;: false
                }
            ],
            &quot;type&quot;: &quot;basic&quot;
        },
        {
            &quot;conditions&quot;: [
                {
                    &quot;identifiers&quot;: [
                        {
                            &quot;is_keyboard&quot;: true,
                            &quot;is_pointing_device&quot;: true,
                            &quot;product_id&quot;: 663,
                            &quot;vendor_id&quot;: 1678
                        }
                    ],
                    &quot;type&quot;: &quot;device_if&quot;
                }
            ],
            &quot;from&quot;: { &quot;key_code&quot;: &quot;mute&quot; },
            &quot;to&quot;: [
                {
                    &quot;key_code&quot;: &quot;q&quot;,
                    &quot;modifiers&quot;: [&quot;left_gui&quot;, &quot;left_control&quot;],
                    &quot;repeat&quot;: false
                }
            ],
            &quot;type&quot;: &quot;basic&quot;
        }
    ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 독 딜레이 없애기&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1656373135998&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;defaults write com.apple.dock autohide -bool true &amp;amp;&amp;amp; defaults write com.apple.dock autohide-delay -float 0 &amp;amp;&amp;amp; defaults write com.apple.dock autohide-time-modifier -float 0 &amp;amp;&amp;amp; killall Dock&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. 개발 툴 세팅&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;a. Xcodes 설치&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-05-16 오전 10.27.30.png&quot; data-origin-width=&quot;1012&quot; data-origin-height=&quot;562&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HHMOh/btrB5AZARbj/fm0YhSwWJuKkja421Cs5p0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HHMOh/btrB5AZARbj/fm0YhSwWJuKkja421Cs5p0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HHMOh/btrB5AZARbj/fm0YhSwWJuKkja421Cs5p0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHHMOh%2FbtrB5AZARbj%2Ffm0YhSwWJuKkja421Cs5p0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1012&quot; height=&quot;562&quot; data-filename=&quot;스크린샷 2022-05-16 오전 10.27.30.png&quot; data-origin-width=&quot;1012&quot; data-origin-height=&quot;562&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래는 AppStore에서 Xcode 따박따박 받아서 썼었는데, 업데이트할때마다 속도가 너무 느리기도 하고..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최신 Xcode에서 오류가 나는경우, Legacy 버전을 설치해서 써야하는 경우가 종종있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 Xcodes라는 툴을 설치하여 Xcode를 관리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/RobotsAndPencils/XcodesApp&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/RobotsAndPencils/XcodesApp&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1652664444294&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - RobotsAndPencils/XcodesApp: The easiest way to install and switch between multiple versions of Xcode - with a mouse cli&quot; data-og-description=&quot;The easiest way to install and switch between multiple versions of Xcode - with a mouse click. - GitHub - RobotsAndPencils/XcodesApp: The easiest way to install and switch between multiple version...&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/RobotsAndPencils/XcodesApp&quot; data-og-url=&quot;https://github.com/RobotsAndPencils/XcodesApp&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/lu5iL/hyOpMdKhQf/SBpvtluvLZAPEFcSJbLoL0/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640&quot;&gt;&lt;a href=&quot;https://github.com/RobotsAndPencils/XcodesApp&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/RobotsAndPencils/XcodesApp&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/lu5iL/hyOpMdKhQf/SBpvtluvLZAPEFcSJbLoL0/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - RobotsAndPencils/XcodesApp: The easiest way to install and switch between multiple versions of Xcode - with a mouse cli&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The easiest way to install and switch between multiple versions of Xcode - with a mouse click. - GitHub - RobotsAndPencils/XcodesApp: The easiest way to install and switch between multiple version...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;b. 텍스트 에디터 설치&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;속도비교.gif&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;450&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dNBCV3/btrB5WgYmrR/W72PfhqAG1GB9fVINtjBO1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dNBCV3/btrB5WgYmrR/W72PfhqAG1GB9fVINtjBO1/img.gif&quot; data-alt=&quot;Visual Studio Code와 Sublime Text의 속도 비교&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dNBCV3/btrB5WgYmrR/W72PfhqAG1GB9fVINtjBO1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/dNBCV3/btrB5WgYmrR/W72PfhqAG1GB9fVINtjBO1/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;450&quot; data-filename=&quot;속도비교.gif&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;450&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Visual Studio Code와 Sublime Text의 속도 비교&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예전에 윈도우 개발할때는 NotePad++를 쓰다가,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맥을 사용하다보니 대체제를 찾기가 애매 하더라구요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 Visual Studio Code를 썼는데.. NotePad++처럼 뭔가 바로바로 팟! 하고 열리는게 아니라.. 로딩시간이 좀 걸려서 찾아보니,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SublimeText라는 좋은 툴이 있더라구요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무료버전을 이용하면, 구매하라는 팝업이 종종 뜨는걸로 알고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 간단한 텍스트 수정(plist, json 등등)할때 이만한 툴이 없네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.sublimetext.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.sublimetext.com/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1652664890273&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Sublime Text - the sophisticated text editor for code, markup and prose&quot; data-og-description=&quot;Available on Mac, Windows and Linux&quot; data-og-host=&quot;www.sublimetext.com&quot; data-og-source-url=&quot;https://www.sublimetext.com/&quot; data-og-url=&quot;https://www.sublimetext.com/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cWAOrW/hyOpNRgmkm/goaBVFAV6g1dKdeCdP20yk/img.jpg?width=2400&amp;amp;height=1256&amp;amp;face=0_0_2400_1256,https://scrap.kakaocdn.net/dn/dBayX4/hyOpGkhJvI/3BoQd0vCyNIMhB04L71jiK/img.jpg?width=2400&amp;amp;height=1256&amp;amp;face=0_0_2400_1256&quot;&gt;&lt;a href=&quot;https://www.sublimetext.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.sublimetext.com/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cWAOrW/hyOpNRgmkm/goaBVFAV6g1dKdeCdP20yk/img.jpg?width=2400&amp;amp;height=1256&amp;amp;face=0_0_2400_1256,https://scrap.kakaocdn.net/dn/dBayX4/hyOpGkhJvI/3BoQd0vCyNIMhB04L71jiK/img.jpg?width=2400&amp;amp;height=1256&amp;amp;face=0_0_2400_1256');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Sublime Text - the sophisticated text editor for code, markup and prose&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Available on Mac, Windows and Linux&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.sublimetext.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;c. git GUI Tool 설치&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-05-16 오전 10.35.35.png&quot; data-origin-width=&quot;2032&quot; data-origin-height=&quot;1167&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dgMv1X/btrB9tMOucn/vyTKerzP0k5yrEh1Tz8nIK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dgMv1X/btrB9tMOucn/vyTKerzP0k5yrEh1Tz8nIK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dgMv1X/btrB9tMOucn/vyTKerzP0k5yrEh1Tz8nIK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdgMv1X%2FbtrB9tMOucn%2FvyTKerzP0k5yrEh1Tz8nIK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2032&quot; height=&quot;1167&quot; data-filename=&quot;스크린샷 2022-05-16 오전 10.35.35.png&quot; data-origin-width=&quot;2032&quot; data-origin-height=&quot;1167&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래는 xcode 순정을 사용 했었는데, 프로젝트를 벗어난 관리가 필요하거나, 여러가지 Branch를 컨트롤하거나 할때 종종 에러가 나서 이것저것 찾아보다가, UI가 예뻐서 정착했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유료입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.gitkraken.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.gitkraken.com/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1652664962697&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;GitKraken Legendary Git Tools | GitKraken&quot; data-og-description=&quot;Meet GitKraken, the creator of legendary Git tools for developers and teams - like the GitKraken Client, with Git GUI and CLI, Git Integration for Jira, and GitLens for VS Code.&quot; data-og-host=&quot;www.gitkraken.com&quot; data-og-source-url=&quot;https://www.gitkraken.com/&quot; data-og-url=&quot;https://www.gitkraken.com&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/J6MNY/hyOpL6ZO5m/KSyyJHzU8X3Lt26GRFVTU1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/8oicq/hyOpGxQviM/lPdE1jwlBCb5aUpjgq7dU0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://www.gitkraken.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.gitkraken.com/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/J6MNY/hyOpL6ZO5m/KSyyJHzU8X3Lt26GRFVTU1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/8oicq/hyOpGxQviM/lPdE1jwlBCb5aUpjgq7dU0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitKraken Legendary Git Tools | GitKraken&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Meet GitKraken, the creator of legendary Git tools for developers and teams - like the GitKraken Client, with Git GUI and CLI, Git Integration for Jira, and GitLens for VS Code.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.gitkraken.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;d. gitignore 설정&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;깃을 관리할 때마다 같이 commit되는 쓸모없는 파일들이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런것들은 그냥 git 전역 설정으로 설정을 하는게 편한데요,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적으로 .DS_Store 파일이 있죠?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저같은경우는 추가로 JetBrains 제품들도 사용을 하기 때문에, .idea 디렉토리도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런것들은 글로벌로 ignore 처리를 해줘도 무방한데요, 다음과같이 설정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. ~/ 디렉토리에 .gitignore 파일 생성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. .gitignore 파일에 ignore 설정 추가&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1662359467855&quot; class=&quot;asciidoc&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.DS_Store
.idea&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. git에 적용&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1662359491182&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;git config --global core.excludesFile ~/.gitignore&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 설정해주면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일명, 디렉토리 명은 자신이 원하는대로 설정해도 됩니다. &lt;b&gt;반드시 ~/ 디렉토리의 .gitignore일 필요가 없습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. 기타&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;a. Alfred 설치&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-05-16 오전 10.38.30.png&quot; data-origin-width=&quot;695&quot; data-origin-height=&quot;668&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/D8v3Z/btrCbo5if6t/H5nLsFYgrwGkV3eaY0a7mK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/D8v3Z/btrCbo5if6t/H5nLsFYgrwGkV3eaY0a7mK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/D8v3Z/btrCbo5if6t/H5nLsFYgrwGkV3eaY0a7mK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FD8v3Z%2FbtrCbo5if6t%2FH5nLsFYgrwGkV3eaY0a7mK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;695&quot; height=&quot;668&quot; data-filename=&quot;스크린샷 2022-05-16 오전 10.38.30.png&quot; data-origin-width=&quot;695&quot; data-origin-height=&quot;668&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WorkFlow로 이것저것 활용이 가능하다해서 구매를 했었는데.. 솔직히 쓸모는 없네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 쉘 스크립트로 대부분의 작업을 커버합니다..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 산게 아까워서 + 제가 레이저 장비를 좋아해서 뭔가 감성적으로 사용하는 프로그램 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;b. Magnet 설치&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-05-16 오전 10.41.22.png&quot; data-origin-width=&quot;308&quot; data-origin-height=&quot;673&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cXiqRD/btrB5AL0cPv/nWqBNtbREyLCh7JBRIKEY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cXiqRD/btrB5AL0cPv/nWqBNtbREyLCh7JBRIKEY0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cXiqRD/btrB5AL0cPv/nWqBNtbREyLCh7JBRIKEY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcXiqRD%2FbtrB5AL0cPv%2FnWqBNtbREyLCh7JBRIKEY0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;308&quot; height=&quot;673&quot; data-filename=&quot;스크린샷 2022-05-16 오전 10.41.22.png&quot; data-origin-width=&quot;308&quot; data-origin-height=&quot;673&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;윈도우의 윈도우 분할기능을 해주게 하는 프로그램입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://apps.apple.com/kr/app/magnet-%EB%A7%88%EA%B7%B8%EB%84%B7/id441258766?mt=12&quot;&gt;https://apps.apple.com/kr/app/magnet-%EB%A7%88%EA%B7%B8%EB%84%B7/id441258766?mt=12&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1652665340662&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;&amp;lrm;Magnet 마그넷&quot; data-og-description=&quot;&amp;lrm;하나의 앱에서 다른 앱으로 컨텐츠를 이동하거나, 데이터를 나란히 비교하거나, 기타의 방법으로 복수 과제를 수행할 때, 윈도우를 이에 따라 정렬해야합니다. 마그넷은 이 절차를 깨끗하게, &quot; data-og-host=&quot;apps.apple.com&quot; data-og-source-url=&quot;https://apps.apple.com/kr/app/magnet-%EB%A7%88%EA%B7%B8%EB%84%B7/id441258766?mt=12&quot; data-og-url=&quot;https://apps.apple.com/kr/app/magnet-%EB%A7%88%EA%B7%B8%EB%84%B7/id441258766?mt=12&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/GeO9p/hyOpKttFd9/ldupJxSRfOXKoj2p8MW3sk/img.png?width=630&amp;amp;height=630&amp;amp;face=0_0_630_630,https://scrap.kakaocdn.net/dn/owdnl/hyOpFeCfMh/m1N1dqLDz27sfkOQ7tiMw0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://apps.apple.com/kr/app/magnet-%EB%A7%88%EA%B7%B8%EB%84%B7/id441258766?mt=12&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://apps.apple.com/kr/app/magnet-%EB%A7%88%EA%B7%B8%EB%84%B7/id441258766?mt=12&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/GeO9p/hyOpKttFd9/ldupJxSRfOXKoj2p8MW3sk/img.png?width=630&amp;amp;height=630&amp;amp;face=0_0_630_630,https://scrap.kakaocdn.net/dn/owdnl/hyOpFeCfMh/m1N1dqLDz27sfkOQ7tiMw0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;&amp;lrm;Magnet 마그넷&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;lrm;하나의 앱에서 다른 앱으로 컨텐츠를 이동하거나, 데이터를 나란히 비교하거나, 기타의 방법으로 복수 과제를 수행할 때, 윈도우를 이에 따라 정렬해야합니다. 마그넷은 이 절차를 깨끗하게,&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;apps.apple.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;b.2. RectAngle 설치&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마그넷의 대안인 RectAngle입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 예전부터 결제한김에 마그넷을 사용했는데요.. 트랙패드 핀치랑 오류가 많아서 바꿧슴다 ㅠ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://rectangleapp.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://rectangleapp.com/&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;c. AIDante 설치&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 노트북 환경일때만 설치하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배터리 최대 충전량을 줄여주는건데요, 저는 60%로 설정해놨습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;몇년전에 스웰링(배터리가 부풀어서 맥북이 빵빵해지는..) 현상을 겪은 경험이 있어서, 설치는 하는데 효과는 아직 모르겠네요..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/davidwernhart/AlDente&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/davidwernhart/AlDente&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1652665203460&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - davidwernhart/AlDente: macOS tool to limit maximum charging percentage&quot; data-og-description=&quot;macOS tool to limit maximum charging percentage. Contribute to davidwernhart/AlDente development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/davidwernhart/AlDente&quot; data-og-url=&quot;https://github.com/davidwernhart/AlDente&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dlap2A/hyOpEGNVDW/qmmBXnLq4kvi3bbPvnRIi0/img.png?width=1200&amp;amp;height=600&amp;amp;face=1001_144_1083_234&quot;&gt;&lt;a href=&quot;https://github.com/davidwernhart/AlDente&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/davidwernhart/AlDente&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dlap2A/hyOpEGNVDW/qmmBXnLq4kvi3bbPvnRIi0/img.png?width=1200&amp;amp;height=600&amp;amp;face=1001_144_1083_234');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - davidwernhart/AlDente: macOS tool to limit maximum charging percentage&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;macOS tool to limit maximum charging percentage. Contribute to davidwernhart/AlDente development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;d. &lt;a href=&quot;https://brew.sh/index_ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Homebrew&lt;/a&gt; 설치&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발을 하다보면 이것저것 설치할일이 많이 생깁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맥에서는 Homebrew를 많이 이용하는데요, 홈페이지에 들어가서 설치가이드를 따라 설치를 해도 괜찮고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드를 커맨드에 입력하여 설치해도 괜찮습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1655425587391&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/bin/bash -c &quot;$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이정도만 하면 당장 개발하는데 뭐 더 필요한건 없겠네요..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 외에 추가적으로.. oh my zsh랑 iterm도 설치하면 좋기는한데, 이건 내용이 조금 길기도하고, 항상 제가 쓰면서도 iOS 개발자 입장에서 굳이..? 라는 생각이 매번 들어서 나중에 기회가 되면 다루겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그외에는 나중에 뭔가 또 생각나거나 생기면 추가 하겠습니다.&lt;/p&gt;</description>
      <category>영웅칼럼</category>
      <author>HEROHJK</author>
      <guid isPermaLink="true">https://herohjk.tistory.com/70</guid>
      <comments>https://herohjk.tistory.com/70#entry70comment</comments>
      <pubDate>Mon, 16 May 2022 10:45:31 +0900</pubDate>
    </item>
    <item>
      <title>UIColor Custom</title>
      <link>https://herohjk.tistory.com/68</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;뷰를 개발하다보면, &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uicolor&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;UIColor&lt;/a&gt;를 사용할일이 정말 정말 많습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 좀 더 편하게 사용하기 위한 커스텀 코드를 몇번 작성하다보니, 정리가 필요하겠구나 싶어 포스팅 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;script src=&quot;https://gist.github.com/HEROHJK/e584b6cccb024b515949af44957a1d18.js&quot;&gt;&lt;/script&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 코드를 작성하게되면, 아래와 같이 편하게 UIColor 값을 조절할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1652230212956&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;myLabel.textColor = UIColor(0, 0, 0)
myLabel.textColor = UIColor(rgb: 0x000000)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 추가적으로 조금 더 들어가서..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 뷰의 색상값을 아래처럼 정의해두면 조금 더 가독성이 높은 코드가 완성 되겠죠&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1652230495607&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;extension UIColor {
    static let titleColor: UIColor = { UIColor(rgb: 0x000000) }()
    static let subtitleColor: UIColor = { UIColor(rgb: 0x888888) }()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 정의해둔 다음..&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1652230535917&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;titleLabel.textColor = .titleColor
authorLabel.textColor = .subtitleColor&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로 활용해 볼 수 있겠네요.&lt;/p&gt;</description>
      <category>IOS/Swift</category>
      <category>RGB</category>
      <category>UIcolor</category>
      <author>HEROHJK</author>
      <guid isPermaLink="true">https://herohjk.tistory.com/68</guid>
      <comments>https://herohjk.tistory.com/68#entry68comment</comments>
      <pubDate>Wed, 11 May 2022 09:55:49 +0900</pubDate>
    </item>
    <item>
      <title>스터디 github 가이드</title>
      <link>https://herohjk.tistory.com/66</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;서론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스터디를 하는 이유?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(너무 길어서 내용을 접어두었으니, 읽어보실분은 펼쳐 보세요)&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 스터디를 매년 2~3번씩 꾸준히 정기적으로 참여하는 편입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;12개월중 6~9개월정도는 되도록 스터디에 참여를 하고, 나머지는 휴식시간을 가지는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;곰곰히 생각해보면 스터디보다는 스스로 공부하는것이 훨씬 효율이 나겠지만(관리, 진도 등의 이유로)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저같은경우 보통 스터디를 하게 되면 같이 하는 공부 속에서 혼자 공부를 하게됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그냥 쉽게 말해 혼자 공부하게되면 대충 하다가 말게되는 게으름때문에..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오히려 취업준비할때까지는 쳐다도 안봤는데.. 2017년에 일을 시작한 이후부터는 여유가 생겼는지 매년 빼지않고 꼬박꼬박 참여하는 편 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 연차가 이제 조금씩 쌓이면서 주니어 티를 벗어나는 수준이 오니, 상대적으로 경력자분들끼리 모여서 하는 스터디는 인맥을 통해서 하는게 아니면 조금 구하기가 힘들어져서, 초보 스터디에 참여를 하게 되는데요 ㅠㅠ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런곳에 매번 참여할때마다 '아 이번에는 가만히 따라만 가야지', '나서지 말아야지..' 라고 마음을 먹지만, 항상 종장에는 리드를 하게 되더라구요 ㅠㅠ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 스터디 진행을 리드하는거지, 기술, 공부 자체를 리드하지는 않는 편입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어쨋든.. 스터디를 운영하다보면 구성원의 범주가 정말로 다양합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;학생, 취준생, 비전공 취준생, IT 관련 종사자(기획자, 디자이너 등), 다른 포지션의 개발자 등등..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약에 iOS 앱 개발 프로젝트 스터디를 진행한다고 하면, iOS 개발에 관심있는 다양한 카테고리의 인원들과 함께하는 경우가 많습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연히 수준도 천차만별이고, git의 사용을 어려워 하는 분들도 종종 계십니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적으로 github을 사용할줄 아냐 모르냐로 역량을 판단하지는 않습니다. (그러고 싶지도 않고..)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중요하다고 생각하는 우선순위를 정해보면..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫번째로 참여 의욕&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;개발자는 자기 프로젝트 바쁘대서 잠수타고~ 학생은 시험있다고 잠수타고~ 취준생은 취업했다고 잠수타고~ ㅠ.ㅠ&lt;/li&gt;
&lt;li&gt;그리고 스터디 문의를 할 때마다 항상 참여자중 현업 개발자는 몇분이냐고 물어보시는데,&amp;nbsp;왜 물어보시는지 이해는 되지만.. 상당히 실례라고 생각합니다 ㅠ&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두번째로 친화력&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;스터디를 10번정도 하면, 3~4번정도는 단톡방에 아무런 얘기가 없을때가 많습니다.. 공부를 하는거지 월급받고 일을 하는게 아닙니다. &amp;nbsp;처음 공부하는것이기 때문에 부끄러워할 필요가 전혀 없습니다. 얼마든지 물어보시고, 공유해주세요!&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 코딩 능력&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;공부하는 부분으로 서로 소통하는데 문제만 없으면 괜찮다고 생각합니다.&lt;/li&gt;
&lt;li&gt;부족하다고 생각해서, 스터디를 마치고 나니 머릿속에 들어온게 없어서.. &amp;lt;- 이건 본인의, 그리고 스터디원 개인의 문제지 스터디원 전체에게 영향이 가는건 아니니까요^^;;&lt;/li&gt;
&lt;li&gt;스터디라고 하지만, 공부는 결국 본인이 하는거라서요&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;말 정리가 항상 부족해서 서론이 상당히 길었네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제부터 작성할 내용도, 제가 이번에 스터디를 진행하면서 스터디원들을 위해 작성한 가이드라인인데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PR로 서로 리뷰하며 코드를 공유하는 방식인데..&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 스터디장이 Repo를 만들었다는 가정 하에 설명 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스터디는 하나의 Repository에서 스터디원들의 이름을 딴 Branch를 사용하여 코드를 관리할 예정입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1140&quot; data-origin-height=&quot;1560&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dG9Ezb/btrAAcSeWYo/Zt7suCArDgfxGlKAV8uIj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dG9Ezb/btrAAcSeWYo/Zt7suCArDgfxGlKAV8uIj0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dG9Ezb/btrAAcSeWYo/Zt7suCArDgfxGlKAV8uIj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdG9Ezb%2FbtrAAcSeWYo%2FZt7suCArDgfxGlKAV8uIj0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;233&quot; height=&quot;319&quot; data-origin-width=&quot;1140&quot; data-origin-height=&quot;1560&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 방식으로 사용하는 이유는 몇가지가 있습니다만,&lt;span&gt;&amp;nbsp;&lt;/span&gt;PR을 활용하여 서로에 대한 코드를 리뷰해주는것이 가장 큰 목적입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 이런방식으로만 관리를 하게 된다면.. 개인의 github Repositories에는 저장이 되지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;PR?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(이 부분도 그다지 필요한 부분은 아닌것 같아 접어둡니다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PR은 Pull - Request의 줄임말로, github을 비롯한 많은 git Platform에서 사용중인 Merge 및 Code Review 방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대략적으로 github의 Repository의 구성이 이렇다고 치면..&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;547&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mxIgv/btrABKneQTt/gHXLxJMEVmiZhkpGbLsxOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mxIgv/btrABKneQTt/gHXLxJMEVmiZhkpGbLsxOk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mxIgv/btrABKneQTt/gHXLxJMEVmiZhkpGbLsxOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmxIgv%2FbtrABKneQTt%2FgHXLxJMEVmiZhkpGbLsxOk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;580&quot; height=&quot;248&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;547&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;수정할 내용이 생겨서 기여자가 수정을 하게 될 때, 리뷰를 받고, 리뷰에 대해 승인을 받게 되면 그때 코드가 합쳐지는 방식입니다. (요청 - 승인 없이 기여자 독단적으로 Repository의 코드를 수정한다면, 관리가 힘들게 되겠죠?)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 이때 Pull - Request라는 방식으로 수정된 코드를 확인하고, 병합하는 과정을 거치는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기여자가 수정할 코드를 PR 요청을 통해 요청을 한다면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관리자는 해당 코드를 해당 Repository(기여자가 Fork하거나, 아니면 요청할때 임시로 생성된 저장소)를 Pull하여 확인합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확인하는 과정이 일종의 Code Review가 되는것이죠.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;진행 사이클&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드에 대한 기록은 다음과 같이 진행합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;447&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCqcCr/btrAxjE56i5/8ZJQneNudKJr0l5a5HT6cK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCqcCr/btrAxjE56i5/8ZJQneNudKJr0l5a5HT6cK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCqcCr/btrAxjE56i5/8ZJQneNudKJr0l5a5HT6cK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCqcCr%2FbtrAxjE56i5%2F8ZJQneNudKJr0l5a5HT6cK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;593&quot; height=&quot;207&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;447&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;0. (중요) 모든 작업은 Fork를 한 Repository에서 자신의 Branch에서만 작업을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 원본 저장소를 내 저장소로 Fork 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;158&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4Urgn/btrAB0cHdkK/LWI7LQ0Cp8b6RjddL3LHAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4Urgn/btrAB0cHdkK/LWI7LQ0Cp8b6RjddL3LHAk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4Urgn/btrAB0cHdkK/LWI7LQ0Cp8b6RjddL3LHAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4Urgn%2FbtrAB0cHdkK%2FLWI7LQ0Cp8b6RjddL3LHAk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;682&quot; height=&quot;84&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;158&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 코드를 작성 및 수정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 코드가 정리되었다면, PR을 요청합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;351&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cKB2pH/btrAyNFqLsP/57DvVdgCLuU8MpwkLyR6jK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cKB2pH/btrAyNFqLsP/57DvVdgCLuU8MpwkLyR6jK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cKB2pH/btrAyNFqLsP/57DvVdgCLuU8MpwkLyR6jK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcKB2pH%2FbtrAyNFqLsP%2F57DvVdgCLuU8MpwkLyR6jK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;678&quot; height=&quot;186&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;351&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;542&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d1gy3P/btrAx8iKOM1/DSZvYWqxifkzauoQjE4Iw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d1gy3P/btrAx8iKOM1/DSZvYWqxifkzauoQjE4Iw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d1gy3P/btrAx8iKOM1/DSZvYWqxifkzauoQjE4Iw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd1gy3P%2FbtrAx8iKOM1%2FDSZvYWqxifkzauoQjE4Iw0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;688&quot; height=&quot;291&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;542&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. PR에 대한 승인 및 병합작업은 원본 Repository의 Owner(스터디장)이 진행합니다.&lt;/p&gt;</description>
      <category>공통</category>
      <category>github</category>
      <category>PR</category>
      <category>스터디</category>
      <author>HEROHJK</author>
      <guid isPermaLink="true">https://herohjk.tistory.com/66</guid>
      <comments>https://herohjk.tistory.com/66#entry66comment</comments>
      <pubDate>Wed, 27 Apr 2022 12:10:23 +0900</pubDate>
    </item>
  </channel>
</rss>