위의 내용을 설치하지 않아도, cuda tool kit 이 설치가 완료 되었다고 한다고 하면, 굳이 할 필요 없다. Visual Studio 만으로도 충분히 사용할 수 있다. 일단 C 에 Program Files 안에 CUDA Toolkit 안에 있는 예제 .exe 파일을 돌려보거나, 설치가 되어있다고 하면, Project 를 생성할때 아래와 같이 사용할수 있다.
대학원때 나는 Deep Learning 에 대해서 배우고, Protein Folding 관련되서도 연구를 해보았었다. 그때 참 나는 Deep Learning 이라는게 딱히 편하지 않았다. Data 뽑는것 부터해서, 교수님한테 Data 더 필요해야될것 같아요! 이러면서 굽신 굽신까지는 하지는 않았지만, 뭔가 마음도 편하지 않았고, 너무 오래걸린다는 의미에서는 참 굉장히 거리감이 느꼈었다. 그래서 개발로 변경하고 이렇게 왔었는데, 워낙 요즘 기술의 발전이 빠르다 보니, 나도 모르면 안되겠다라는 생각이 든다. 특히나 Parallel Processing 쪽으로 GPU 연산 쪽으로 더 가고 싶기 때문에, 기본적인 기본을 다시 Review 한답시고 요청을 드렸었다. 너무 친절하게도, 책 전체를 Review 하는게 아닌 챕터 별로 Review 를 하기 떄문에, 부담감을 덜었었다. 내가 Review 할 부분은 “Attenion 과 Self-Attention” 이 부분이다. (나는 Attention is all you need 의 읽기를 사실 포기 했었다… ㅎㅎ)
Comments & General Review
일단 경험상으로, Deep Learning 수업시간에 이야기 했던 부분이 뭐였냐면, 너가 어떤 Project 를 할건지에 따라서, Architecture, Loss function, Softmax, etc 등이 구분이될거다. 예를 들어서 one-to-many 를 하려면, softmax 를 사용해야한다 등.. 이 activation function (tanh) 이 (relu) 보다 좋은 이유는… 이러면서 이야기를 했었는데, 이런 Category 를 주어지면, 우리가 직접 찾지 않아도, 일단 이렇게 만들어보자 라는게 먼저 나온는데, 이 책은 그걸 확실하게 정리해준다. (확실하게 정리 해준 부분은 이전 챕터를 이해하다보면 정리가 된다.) 그리고 간결하다. RNN 을 거슬러 올라가면 LSTM 하고 GRU 가 있는데 이것부터 알아야하는것도 중요하다. 물론 어떤 역활을 하는건지는 중요하지만, 이 부분을 잠깐 알아두기 라는 방식으로 참고해서, 책을 읽었을때 집중력을 흐리지 않게 하는 부분은 참 좋았다.
다른 한점은 용어적인 부분을 Bold 식 또는 색깔을 다르게 해서, 이 부분이 핵심이라는걸 짚어주는데, 확실히 요즘 ChatGPT, DeepSeek 에 비롯된 AI 들이 OpenSource 로 많이 나와있고, 인터넷에 많은 Source 들이 돌아다니는데, 항상 용어의 문제가 있다. 어떤게 정확하게 맞는지 이해를 피해가는 용어들이 생각보다 많이 돌아다닌다. 예를 들어서, Tokenizing 과 Token, Tokenizer 에 대해서, 용어의 의미가 정확한 예제가 없으면, 잘 이해하기가 힘들다. (물론 Compiler 쪽을 공부했다면, 이해가 가능하지만, 입문서에서 다른 Resource 를 보고 공부하다보면 뜻이 흐려진다. 마치 RNN 의 한계점 처럼…)
Tokenizing: 데이터를 적절한 단위로 나누는 과정
Token: 나눠진 각 단위
Tokenizer: 데이터를 어떤 단위로 나누는 객체
Next Token Prediction: 그다음 Token 을 예측 (예제: 자동완성)
그리고 이에 덧붙여서, 이책에서는 Architecutre 를 정확하게 예시를 들어서 말해준다. RNN 같은 경우 각각의 h1, h2 표시, Input/output 문자등을 쉽게 설명을 해줘서 이부분에 있어서는 굉장히 잘표현했다.
RNN 부분에 있어서, 왜 한계점이 있는지를 두가지로 잘 표현했는데 Loss 부분의 편미분을 했을때, Gradient 가 각 입력 시점에 대해 불균형적 가중합으로 구해지는 문제 Activation Function: tanh 의 문제점 - Data 의 정보가 점점 뭉개지는 현상 이 부분을 수학적으로 잘표현되었다는게, 독자들에게 훨씬 쉽게 다가서려고 많이 노력했구나 라고 말을 해준다.
Test 환경과 Train 했을때의 문제점을 Case 별로 잘 설명해준다. 이건 사실 경험이 없으면 요약하기 정말 어려운 부분이다. 근데 장황하게 안쓰셨지만, 확실히 이건 전략적으로 작성한게 보인다. (너무 train 된것만 쓰다 보면, 말에 신뢰가 떨어질수 있으니)
제일 좋았던건 transformer 에 대한 설명을 천천히 한계로부터 발전되어왔고, 그것에 대한 원리를 잘 설명 해주었던것 같다. 예를 들어서, context vector 와 Encoding 에서의 embeding vector h_n 들 Decoding 에서의 s_n 들을 그림으로 잘설명을 해주셔서 확실히 이해했던 부분이 있었지만, 갑작스런 C4 는 조금 집중을 흐리게했다. (c1, c2, c3 는 뭔데? 라는 질문을 할수 있는데) 이 부분은 확실히 attention 부분에 있어서, 간소화하기 힘든 부분들을 어쩔수 없이 하나의 예제로 가져간걸로 보인다. 하지만 아래의 그림을 보면, 이 부분을 자세하게 어떤 부분이 Query 고 이부분이 Key, 고 이부분이 Value 인지를 통해서 확실히 이해했다고 볼수 있다.
전반적으로 문제, 그리고 Resercher 들의 해결 방법, 하지만 또 한계점에 대해서는 확실히 표현하고 있어서, 이부분을 딥러닝을 입문하는 사람이 보았을때 이해가 안될수도 있지만 흐름을 이해할수 있고, Detail 한 부분은 독자들에게 맡기는 부분도 있을것 같다. 이렇게 사실 좋은 입문서가 있었더라면, 장황한 동영상 하나보다는 훨씬 나은 점이 있다! 예제 Figure 그리고 피할수 없는 수학들을 잘 설명 해주다 보면, 아 뭐 이정도면 알겠다라는 식으로 쉽게 지식을 확장하거나, Application 을 만드는데 사용하거나 할수 있을것 같다라는게 총평이다.
조금 아쉬웠을수도 있고, 최대한 입문서에 맞게 쓰려고 하는것도 기준점을 두고 있으니까라고 이해했지만, 내적을 사용하는 이유에 대해서는 배경설명이 조금 필요한것 같기는 하다. 이건 어느정도 선형 대수의 Background 를 이야기하면 좋았지 않을까이다.
사실 Interactive Web 을 사용하고 싶었고, 뭔가 항상 가지고 있었던, Front-end 는 별로야. 너무 볼것도 많고, 디자인 할것도 많고, Core Value 가 없어보여.. 이런말만 했었는데, 요즘은 Spatial Computing 이 되게 중요하지 않나? 라고 생각해서 mobile 로 할수 있는게 뭘까? 하니 What is Spatial Computing. 읽어보면,
It’s the purset form of “blending technology into the world
라고 작성이 되어있다. 즉 우리가 사용하는 Computer Hardware 를 사라지게 하며, digital 현상으로 볼수 있는, machine 으로 부터 Output 만 보는 형태! 라고 볼수 있다. Application 으로는 VR/AR/MR 등이 있으며, 아래와 같이 정의한다.
VR: VR places the user in another location entirely. Whether that location is computer-generated or captured by video, it entirely occludes the user’s natural surroundings.
AR: In augmented reality - like Google Glass or the Yelp app’s monocle feature on mobile devices - the visibile natural world is overlaid with a layer of digital content.
MR: In technologies like Magic Leap’s virtual objects are integrated into - and responsive to- the natural world. A virtual ball under your desk, for example, would be blocked from view unless you bent down to look at it. In theory, MR could become VR in a dark room.
일단 개발환경을 앞서서, 어떻게 XR 과 관련된 개발을 찾아보자. 일단 ARKit, RealityKit 같은 경우는 ARKit 은 증강현실 프레임워크 이고, RealityKit 는 3D Rendering Framework 이다. RealityKit 이 ARKit on top (ARSession) 에 실행된다고 보면 된다. RealityKit 은 rendering 할수 있는 engine 이 존재하고, physics 또는 animation 을 담당한다. 언어로는 swift / object-c 가 있다. 물론 Framework (Unity)를 껴서 개발은 가능하다. 더 자세한건 여기 Forum 에서 보면 될것 같다. 그리고 RealityKit 과 Metal Computing Shader 와는 같이 작동하고, API 를 사용해서 렌더링 성능을 향상 시킨다.
그리고 OpenXR 같은 경우 vulkan 을 만든 chronus group 에서 만들어졌고, 뭐 C/C++ 을 사용한다. 그리고 나머지는 WebXR 이다. 웹브라우저에서 XR 을 지원하기 위해서 만들어졌으며, WebGL 함께해서 갭라을 한다고 하자.
물론 Metal 을 공부하는것도 나쁘진 않지만, 기본적인 구조는 DirectX11/12 Graphics Pipeline 는 같은것 같다?(이건 확인 필요!)
Setting up IOS(VM) Dev on Windows or MacOS directly
이 부분은 굉장히 까다로웠다. 일단 기본적으로 환경설정을 고려할때, 굳이 macOS 를 Base 로 쓰고 싶지 않았다. VMWare 설치 및 환경설정 (단 여기서, .iso file 은 unlocker 에 있는 .iso) 파일을 설치하도록 하자. 그리고 Resolution Setting 여기를 확인해보자. 가끔씩 VMWare 가 금쪽이 같은 면 이있지만 App 을 Build 하고 코드 작성하는데 크게 문제가 있지는 않은것 같다. 그리고 XCode 를 혹시 모르니까 설치를 해놓자. 설치하는데 시간을 뻇기는건 어쩔수 없는거긴 하지만, 너무 비효율적이고, 길어진다. 인터넷 같은 경우에는 VMWare Player 세팅에서, NAT: Used to share the host's IP address 만 해놓으면 괜찮다.
그리고 부가적으로, Homebrew 를 설치하자. /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)".
그이후에 https://reactnative.dev/docs/set-up-your-environment?os=macos&platform=ios 여기에서 Environment 를 설정해주자.
node - v
nvm - v
npm - v
vim ~/.zprofile # or .zsrc / .bashrc# add this into .zprofile & .zsrc & .bashrcexport NVM_DIR="$HOME/.nvm"[-s"/opt/homebrew/opt/nvm/nvm.sh"]&&\."/opt/homebrew/opt/nvm/nvm.sh"# This loads nvm[-s"/opt/homebrew/opt/nvm/etc/bash_completion.d/nvm"]&&\."/opt/homebrew/opt/nvm/etc/bash_completion.d/nvm"# This loads nvm bash_completionsource ~/.zprofile # .zprofile, .zsrc, .bashrc
nvm install--lts
node - v
nvm - v
npm - v
이후에는 XCode 설치 및 설정을 한다. XCode -> Settings -> Command Line Tool 최신으로 바꿔준다. 그리고 IOS Simulator 를 설치하면 끝이다.
Project Setting
프로젝트 생성 관련 및 개발환경 관련된건 두가지가 있다. Expo 와 React Native CLI 가 있는데, 자세한건 이 링크 간단하게 말하면, Expo 는 개발 환경 초기설정을 단순화하고 개발 속도가 빠르다는 장점이 있다. Expo go 앱이 있다면 프로젝트 실행이 된다. 하지만 제공되는 API 만 사용해야되고 Native Module 이 없기 때문에, 기술 구현상 어려운 부분이 있다. 그리고 Package 볼때, Expo 에 사용될수 있는지 확인 해야 한다.
React Native 같은 경우 Native module 을 사용할수 있고, 다양한 라이브러리 사용 가능하다. 기본적으로 제공되는 라이브러리가 없다 보니, 대부분 기능 구현에 있어서는 직접 설치해야한다 하지만 장점으로는 유지보수가 잘되어있다고 한다. (이건 잘모름) 배포 하기 위해서는 Android Studio 나 XCode 가 있어야한다. 이 글을 보게 되면 어떤걸로 개발할지가 뭔가 잘 나와있다. 결국에는 요약한건 이거다. (React Native itself is an abstraction over the native layer and expo adds another abstraction on top of that one ... learning react native cli first can help you with debugging issues when using expo)
Create Project
처음 하기에는 expo 로 한다고 했는데, 나는 장기적으로 보기 때문에, cli 로 했다. expo 로 개발하려면 expo 개발환경 설정 참조하자.
기존에 react-native-cli 를 전역(global) 로 설치한적 이 있으면 깔끔하게 지워주자. 그리고 project 를 생성하자.
Combine 을 알기전에 앞서서, Combine 이 나오기전에 어떻게 비동기 event 를 처리했는지 보자. 아래의 코드는 간단한 Fake Jason Data 를 Load 해서 UI 에 뿌리는 용도이다. @escaping 이라는걸 간단하게 이야기하자면, 일단 함수안에 completionHandler 가 매개상수로 들어오고, 이 closure 는 함수가 시작한 이후에 바로 실행시킬수 있게끔 되어있다. 만약 asyncAfter 를 사용하게 되면, 함수는 끝나는데 closure 가 살아있을수가 없기 때문에, @escaping 사용해서 closure 가 비동기로 사용할수 있게끔 만들어주는거다. 이를 통해서 비동기 처리를 통해서 Data 를 받아 올수 있었다.
The Combine framework provides a declarative Swift API for processing values over time. These values can represent many kinds of asynchronous events. Combine declares publishers to expose values that can change over time, and subscribers to receive those values from the publishers.
즉 Publisher 와 Subscriber 가 있고, Publisher 는 Data 를 방출하거나, 뭔가의 완료의 Signal 보내는 역활을 하고, Subscriber 는 Publisher 가 쏴준 Data 나 완료 Signal 을 받는 역활을 한다. 그리고 그 사이에 Operator 가 있는데 Publisher 가 생성하는 이벤트를 처리하는 역활을 한다. 이때 연산자(map, filter, reduce) 를 사용할수 있다.
기본적인 예시는
importCombineletpublisher=[10,20,30,40,50].publisherpublisher.map{$0*2}// Operator 를 통해서 값 변환.sink{print($0)}// 값을 recevied
Publisher 연산자에도 (Just, Sequence, Future, Fail, Empty, Deferred, Record) 등이 있다. Just 를 사용한 Publisher 를 사용해보자.
아래의 코드는 여기에서 "https://jsonplaceholder.typicode.com/todos/1" todo 를 하나 가지고 와서, Data 를 Publisher 를 통해서 가지고 온이후에 subscriber 로 receive 받은 이후, UI 를 main thread 에서 update 를 해주는 코드이다.
그리고 Timer Publisher 의 사용법을 하려고 한다. Timer Thread 의 on 은 어떤 RunLoop 에서 Timer 를 사용할지를 정해줄수 있다. 여기에서는 main thread 를 사용하고, 어떠한 방식으로 RunLoop 을 실행할건지를 넣어주는데 default 값을 넣어주었다. 여기에서 필수적으로 알아야하는건 Timer 가 반환하는 Publisher 는 ConnectablePublisher 이며, 이 Publisher 를 subscribe 해서 실행하려면, connect() 나 autoconnect() 를 사용(첫 subscriber 구독 할때). sink 로 receive 해서 print 를 하면된다. 그리고 main thread 가 기다리면서, 5 초 뒤에 subscribe 취소 해주면된다.
어떤 Language 가 됬든 일단 방대한 Data 를 Loading 을 해야하거나, 어떤 통신에 맞물려서 상태 return 받을 때 main thread 에서 모든걸 하게 되면, Performance 가 떨어진다. Swift 에서는 이걸 어떻게 해결하는지, 동작 방법 및 실제 구현해서 App 에서 어떻게 Profiling 을 하는지도 봐보자.
GCD (Grand Centeral Dispatch)
일단 Multithreading 을 알기 이전에 Grand Central Dispatch 에 대한 용어 부터 보자. wiki 에서 나와있는것 처럼 multi-core processor 와 other symmetric multiprocessing system 을 최적화하는걸 support 하기위해서 만들어졌고, Thread Pool Pattern 으로 Task 기반으로 병렬화를 진행한다. Thread Pool Pattern 생소할수 있는데, Thread Pool 은 결국에는 Thread(일용직) 들을 위한 직업 소개소라고 생각하면 된다. 여러개의 Thread 가 대기 하고 있다가 할 일이 들어오면, 대기했던애가 들어와서 일(실행) 하게 되는거라고 볼수 있다. Thread Pool 은 Queue 기반으로 만들면된다. 그래서 Swift 에서는 DispatchQueue 를 사용해서 이를 해결한다. 쉽게 말해서 Task 에 대한 병렬 처리 또는 (비)동기 처리 를 총괄하는 것이 GCD 라고 볼수 있다. 아래의 그림을 보면 간략하게 GCD 가 뭔지를 대충 알 수 있고, DispatchQueue, DispatchWorkItem, DispatchGroup(thread group?) 등을 볼수 있다. (참고: Ref)
GCD 에서 제공 하는 Thread 를 살짝 살표 보자면 Main(Serial) 은 UiKit 이나 SwiftUI 의 모든 요소를 담당한다고 볼수 있고, Global(Concurrent) 같은 경우는 system 전체에서 공유가 되며, 병렬적으로 실행되지만 QoS 따라서 prioirity 를 지정할수 있다.
Priority 위의 그림에서 Interactive 가 Highest Priority 를 가지고, 아래로 갈수록 우선순위가 낮아진다. (참조: Energy Efficiency Guide for iOS Apps) 참조한글을 보면 Use Case 별로 아주 잘 나와있다.
DispatchQueue
Apple Developer Doc 에 찾아보다 보니 DisptchQueue 라는걸 이렇게 설명한다. An ojbect that manages the execution of tasks serially or concurrently on your apps main thread on a background 마치 QT 하고 비슷한 역활을 하는구나라고 볼수 있다. DispatchQueue 는 결국엔 어떤한 work 에 해당되는 item 들이 있다보면, 그 work 의 실행을 Thread Pool 에 넘겨서, executuion 된다고 볼수 있다.
Thread 를 이야기할때는 내가 짠 프로그램이 Thread Safe 한지를 Check 를 해야하는데, 이 DispatchQueue 는 Thread-Safe 한다고 한다. (즉 Thread 들이 한곳에 접근 가능하다는 뜻이다.)
위에 GCD Image 를 보면 Serial 과 Concurrent 로 나눠지는데, Serial 순차적으로 Task 진행 (전에 있던 Task 가 끝난 이후), Concurrent 는 작업이 끝날때까지 기다리지 않고, 병렬 형태로 동시에 진행이다. 결국엔 이게 async 와 sync 키워드로 나눠진다.
일단 DispatchQueue 사용법을 봐보자. 일단 print 된걸 보면 제일 마지막에 Main Thread 가 돌아가고, Aync 로 돌리기 때문에 개념상으로는, for-loop 과 다른 background 와 userInteractive 용 thread 가 동시에 돌리는걸 볼수 있다. 그리고 계속 돌리다보면, Output 은 다를것이다. 하지만 확인할수 있는건 userInteractive 가 background 보다는 더 빨리 돈다는걸 확인할 수 있다.
예를 들어서 어떤 Data 를 다운로드 받아서 display 를 한다고 하자. 물론 어떤 Loader 로 부터 다운로드 받아서 fetch 하기는 하는데, 여기에서는 간단하게, 한군데에서 하고, downloadData method 자체를 private 으로 구분해주자. 각 class 역활은 BackgroundThreadViewModel class 는 fetch, download 를 하고 async 로 Data 를 Download 받고 fetch 로 UI 에 다가 download 된 데이터를 뿌려준다라고 보면될것 같다. 일단 .background thread 에서 돌리는거 하나 `main 에서 UI update 해주는걸 생각하면 될것 같다.
importSwiftUIclassBackgroundThreadViewModel:ObservableObject{@PublishedvardataArray:[String]=[]funcfetchData(){// Background Thread// DispatchQueue.global().asyncsDispatchQueue.global(qos:.background).async{letnewData=self.downloadData()// Main Thread Update (UI)DispatchQueue.main.async{self.dataArray=newData}}}privatefuncdownloadData()->[String]{vardata:[String]=[]forxin0..<50{data.append("\(x)")}returndata}}structBackgroundThreadBootcamp:View{@StateObjectvarvm=BackgroundThreadViewModel()varbody:someView{ScrollView{LazyVStack(spacing:10){Text("LOAD DATA").font(.largeTitle).fontWeight(.semibold).onTapGesture{vm.fetchData()}ForEach(vm.dataArray,id:\.self){iteminText(item).font(.headline).foregroundColor(.red)}}}}}#Preview {BackgroundThreadBootcamp()}
자.. 여기에서 할수 체크할수 있는건 build 를 해보고 돌려보는거다. 아래의 그림을 보면 Main Thread 1 에서 첫 Loading 과 그리고 뿌려질때의 spike 가 보이는걸로 보이고, thread 3 에서 이제 downloading 하는걸 볼수 있다. 그 이외에 background thread 도 아마 관찰이 가능할거다. 여기에서 중요한점은 무조건 thread 를 많이 사용하면 좋지 않다라는 점과 developement doc 에서도 sync 로 했을경우에 deadlock 현상이 나타날수 있다는거만 주의하면 과부하가 잃어나지 않는 앱을 만들수 있을것이다.
실제 Image Loader 를 만들어본다고 하자. 총 3 가지의 방법이 있다고 한다. escaping, async, combine 형태로 아래의 코드를 봐보자. 배경설명은 이러하다. URL 로 부터, 서버에서 Image 를 가져와서 화면에 뿌려주는 그런 앱을 작성한다고 하자. 일단 URL 과 UImage 를 받았을때의 Handler 를 작성한걸 볼수 있다. Data 를 못받으면 nil 로 return 을 하고, 아니면 Data 를 받아서 UIImage 로 변경해주는 코드이고, response error handling 도 안에 있다.
일단 기본적으로 escape 를 사용한걸 보면, URLSession.shared.dataTask 자체가 closure 형태로 전달로 받고, .resume() method 를 반드시 작성해줘야하며, 하나의 background thread 로 동작한다. 그리고 completionHandler 를 통해서 image 를 받을시에 UIImage 와 함께 error 코드를 넘겨준다. (void return). 그 이후 image 를 fetch 한 이후에 main thread 를 update 해야 UI 에서 보여지기 시작한다.
이것만 봤을때는 코드가 잘작동은 되겠지만, 별로 깔끔하지못하다. 그 아래 코드는 combine 이다. 위의 Escaping 코드를 본다고 하면, combine 도 not so bad 이다. 정확한건 combine 이라는 개념만 이해하면 잘작성할수 있을것 같다.
마지막으로는 async 를 사용한 데이터 처리이다. URLSession.shared.data(from: url, delegate: nil) 여기 함수 signature 을 보면 data(from: URL) async throw -> (Data, URLResponse Description ...to load data using a URL, creates and resume a URLSessionDataTask internally... 라고 나와있다. 즉 이 함수를 호출하게 되면 바로, URLSession.shared.data(from: url, delegate: nil) 호출하고 tuple() return 을 받지만, response 가 바로 안올수도 있기 때문에 await 이라는 keyword 가 필요하다. 그래서 await 을 사용하게 되면, 결국엔 response 가 올때까지 기다리겠다라는 뜻이다. 그 이후에 downloadWithAync() 를 호출할때, concurrency 를 만족하기위해서 여기에서도 async keyword 가 필요하다. 그렇다면 마지막으로 main thread 에서 어떻게 UI Update 를 할까? 라고 물어본다면, 답변으로 올수 있는 방법은 DispatchQueue.main.async { self?.image = image } 하지만 아니다. main thread 에서 이걸 처리를 하려면, .appear 부분에서 Task 로 받아서 await 으로 처리해주면 된다. 빌드 이후에 Warning 이 뜰수도 있는데 이부분은 Actor 라는걸로 처리를 하면된다. 물론 Actor 라는건 이 post 에서 벗어난 내용이지만 따로 정리를 해보려고 한다.
importSwiftUIimportCombineclassDownloadImagesAsyncImageLoader{leturl=URL(string:"https://picsum.photos/200")!funchandleResponse(data:Data?,res:URLResponse?)->UIImage?{guardletdata=data,letimage=UIImage(data:data),letres=resas?HTTPURLResponse,// res coderes.statusCode>=200&&res.statusCode<300else{returnnil}returnimage}// escapingfuncdownloadWithEscaping(completionHandler:@escaping(_image:UIImage?,_error:Error?)->()){// async codeURLSession.shared.dataTask(with:url){[weakself]data,res,errinletimage=self?.handleResponse(data:data,res:res)completionHandler(image,err)return}.resume()}// CombinefuncdownloadWidthCombine()->AnyPublisher<UIImage?,Error>{URLSession.shared.dataTaskPublisher(for:url).map(handleResponse).mapError({$0}).eraseToAnyPublisher()}// AsyncfuncdownloadWithAync()asyncthrows->UIImage?{do{let(data,res)=tryawaitURLSession.shared.data(from:url,delegate:nil)returnhandleResponse(data:data,res:res)}catch{throwerror}}}classDownloadImagesAsyncViewModel:ObservableObject{@Publishedvarimage:UIImage?=nilletloader=DownloadImagesAsyncImageLoader()varcancellables=Set<AnyCancellable>()funcfetchImage()async{// escapeloader.downloadWithEscaping{[weakself]image,errorinDispatchQueue.main.async{self?.image=image}}// combineloader.downloadWidthCombine().receive(on:DispatchQueue.main).sink{_in}receiveValue:{[weakself]imageinself?.image=image}.store(in:&cancellables)// async letimage=try?awaitloader.downloadWithAync()// Error -> run this main thread// To resolve this issue: key concept for actorawaitMainActor.run{self.image=image}}}structDownloadImagesAsync:View{@StateObjectprivatevarvm=DownloadImagesAsyncViewModel()varbody:someView{ZStack{ifletimage=vm.image{Image(uiImage:image).resizable().scaledToFit().frame(width:250,height:250)}}.onAppear(){Task{// get into async taskawaitvm.fetchImage()}}}}#Preview {DownloadImagesAsync()}
Generic
사실 cpp 에서는 Meta programming 이라고도 한다. swift 에서도 generic 을 일단 지원한다. 어떤 타입에 의존하지 않고, 범용적인 코드를 작성하기 위해서 사용된다.
이런건 코드로 보면 빠르다.
funcswapValues<T>(_a:inoutT,_b:inoutT){lettemp=aa=bb=temp}vara=10varb=20swapValues(&a,&b)print(a,b)structStack<T>{// Generic Type(T) Arrayprivatevarelements:[T]=[]mutatingfuncpush(_value:T){elements.append(value)}// std::optional T (null) | swift (nil)mutatingfuncpop()->T?{// exceptionguard!elements.isEmptyelse{returnnil}returnelements.popLast()}vartop:T?{returnelements.last}funcprintStack(){ifelements.isEmpty{print("Stack is Empty")}else{print("Stack: \(elements)")}}}varintStack=Stack<Int>()intStack.push(1)intStack.push(2)ifletitem=intStack.pop(){print("Pooped Item : \(item)")}
C++ 에 있는 Function Type 을 이해하면 편하다. Swift 에서도 Function Type 이 존재한다. 아래의 코드를 보면 Function Type Declaration 이 존재한다. (Int, Int) -> Int 하고 addTwoInts 라는 함수를 참조한다. (여기에서 참조). 즉 swift 가 할당을 허락한다. 라는 뜻. 저 불편한 var 로 할당된걸, 함수로 표현하게 되면, (Int, Int) -> Int 자체를 함수의 Parameter 로 넘겨줄수도 있다.
// Function Type ExamplefuncaddTwoInts(_a:Int,_b:Int)->Int{returna+b}funcmultiplyTwoInts(_a:Int,_b:Int)->Int{returna*b;}funcprintHelloWorld(){print("hello, world")}varmathFunction:(Int,Int)->Int=addTwoIntsprint("Result : \(mathFunction(2,3))")funcprintMathResult(_mathFunction:(Int,Int)->Int,_a:Int,_b:Int){print("Result: \(mathFunction(a,b))")}
그리고 다른 함수의 반환 타입으로 함수 타입을 설정할수 있다. 반환하는 함수의 반환 화살표를 -> 사용해서 함수타입으로 사용할수 있다.
// Function Type ExamplefuncstepForward(_input:Int)->Int{returninput+1}funcstepBackward(_input:Int)->Int{returninput-1}funcchooseStepFunction(backward:Bool)->(Int)->Int{returnbackward?stepBackward:stepForward}varcurrentValue=3letmoveNearerToZero=chooseStepFunction(backward:currentValue>0)
여기서 (Int) 라는 chooseStepFunction 안에 있는 함수의 Return 을 의미하고, Parameter 는 Boolean 값으로 넘겨주며, return 을 그 다음 화살표인 Int 로 한다는 뜻 이다.
아직 코드가 간결하지 않다, Nested 함수로 해보자. 함수안에 함수를 작성하는게 불필요할수도 있지만, Closure 을 이해하기전에 필요한 정보이다. 아래의 코드를 보면, currentValue > 0 이면 False 를 반환하고, chooseStepFunction(backward) 가 stepForward 함수를 반환한 이후, 반환된 함수의 참조값이 moveNearerToZero 에 저장된다.
C++ 에서 Closure 라고 하면, 주로 Lambda Expression (Lambda) 를 Instance 화 시켰다고 볼수있다. 즉 위에서 본것 처럼, swift 에서도 똑같은 의미를 가지고 있다. 자 중첩함수에서 본것 처럼 chooseStepFunction 이라는 함수 이름이 존재했다. 그리고 값을 (=) 캡쳐해서 currentValue 를 Update 하였다. closure 는 결국 값을 캡처할수 있지만, 이름이 없는게 Closure 의 개념이다.
Closure 의 Expression 은 아래와 같다. (<#parameters#>) -> <#return type#> Closure 의 Head 이며, <#statements#> 는 Closure 의 Body 이다. Parameter 와 Return Type 둘다 없을수도 있다.
주의점이 하나 있는데, 예를들어서 아래의 코드를 본다고 하자. 첫번째 print 를 했을때는 "Hello, Nick" 이라는게 나온다. 하지만 두번째 Print 에서는 error: extraneous argument label 'name:' in call print(closure(name: "Jack")) 라는 Error 가 뜬다. Closure 에서는 argument label 이 없다. 이 점에 주의하자. 일반 함수 Call 과 다르다. 여기에서 또 봐야할점은 Closure Expression 을 상수에 담았다.(***)
그리고 Function Type 을 이해했다라고 한다면, Function Return Type 으로 Closure 을 return 할수 있으며, 함수의 Parameter Type 으로도 Closure 가 전달이 가능하다. 여기서 첫번째로는 아까 그냥 함수를 작성할때와는 다르게 Argument Label 이 없어야한다고 하지 않았나? 근데 실제 Arugment Label 이 없으면 missing arugment label 'closure' 이라는 Error 를 내뱉는다. 즉 에는 Argument Label 이 Parameter 로 전달됬다는걸 볼수 있다. 그리고 return 같은 경우는 위의 Function Type 을 이해했다면 충분히 이해할수 있는 내용이다.
// function Type: Closure as ParameterfuncdoSomething(closure:()->()){closure()}doSomething(closure:{()->()inprint("Hello!")})// function Type: Closure as ReturnfuncdoSomething()->()->(){return{()->()inprint("Hello Nick!")}}varclosure=doSomething()closure()
클로져의 용도는 간단하면서 복잡한데, 주로 Multithreading 에서 안전하게 State 관리를 하기 쉽다. 하지만 관리 하기 쉽다는건 항상 뭔가의 Performance 가 조금 Expensive 하다는 점이다. 조금 더 자세한걸 알면 Functor 를 보면 될것 같다.
일단 Swift 안에서는, Array, Queue, Stack 이 Data Structure 이 있다. 뭔가 c++ 처럼 Library 를 지원 queue 나 stack 을 지원하나 싶었는데? 없다고 한다 ref 그래서 직접 구현하란다. 하지만 Deque<Element>, OrderedSet<Element>, OrderedDictionary<key, value>, Heap 은 Collection Package 에 있다고 한다. 흠 왜? Array 하고 Sets 는 주면서? 조금 찾아 보니, Array, Set 의 최소한의 자료구조만 표준으로 제공하고, 다른건 Pacakaging 해서 쓰란다. 그리고 생각보다 Generic 도 잘되어있지만, 역시 CPP 하고 비교했을때는 불편한점이 있긴한것같다.
Array & Sets
둘다 Randomly Accessible 하다. 이 말은 Array 같은 경우, 순차적으로 메모리에 저장되므로, 어떤 특정 Index 에서 접근 가능. Set 같은 경우, Hash Table 기반으로 구현되어 있어서 var mySet: Set = [10, 20, 30].contain() 라는 함수로 있으면 True 없으면 False return 을 한다.
왜? 삽입/삭제시 성능 저하? => 찾아서 지워야할텐데, Element 가 마지막이면 O(n) 이니까, 그래서 Set 사용하면 되겠네
Set 과 Array 의 차이점: Set 은 unordered array 는 ordered.
예제 코드들 보니까, Generic 도 사용할수 있다. 그리고 Swift 에서 특별하다고 생각했던게, 요청하지 않는 한 value type 인 애들의 속성값 변경을 허용하지 않는다는거, 즉 Instance method 에서 수정할수 없다는거, 특이하구나…
Queue
FIFO (First In, First Out) 구조 (ex: printer & BFS)
Example Code
structQueue<T>{// Generic Type(T) Arrayprivatevarelements:[T]=[]mutatingfuncenqueue(_value:T){elements.append(value)}// std::optional T (null) | swift (nil)mutatingfuncdequeue()->T?{// exceptionguard!elements.isEmptyelse{returnnil}returnelements.removeFirst()}varhead:T?{returnelements.first}vartail:T?{returnelements.last}funcprintQueue(){ifelements.isEmpty{print("Queue is Empty")}else{print("Current Queue: \(elements)")}}}varqueue=Queue<String>()queue.enqueue("Nick")queue.enqueue("Kayle")queue.enqueue("Juan")queue.printQueue()ifletserving=queue.dequeue(){print(serving)// Optional("Nick")}ifletnextToServe=queue.head{//Optional("Kayle")print(nextToServe)}queue.printQueue()
Stack
LIFO (Last In, Last Out) 구조 (call stack)
Example Code
structStack<T>{// Generic Type(T) Arrayprivatevarelements:[T]=[]mutatingfuncpush(_value:T){elements.append(value)}// std::optional T (null) | swift (nil)mutatingfuncpop()->T?{// exceptionguard!elements.isEmptyelse{returnnil}returnelements.popLast()}vartop:T?{returnelements.last}funcprintStack(){ifelements.isEmpty{print("Stack is Empty")}else{print("Stack: \(elements)")}}}varcookieJar=Stack<String>()cookieJar.push("chocolate")cookieJar.push("walnut")cookieJar.push("oreo")cookieJar.printStack()ifletpopItem=cookieJar.pop(){print(popItem)}cookieJar.printStack()iflettopItem=cookieJar.top{print(topItem)}
Memory Management in Swift
IPhone 이라는 어떤한 Device 를 놓고 봤을때, Storing Data 의 방법은 두가지 있을것 같다.
Disk
RAM
만약 App 을 실행시킨다고 했을때, executable instructions 이 RAM 에 올라가고, system OS 에서 RAM 의 덩어리 일부분(Heap)을 Claim 하면서, App 을 실행시킨다. 그래서 앱에서 실행시키는 모든 Instance 들이 Life cycle 을 가지게 된다. C/CPP 에서도 마찬자기로 malloc / new / delete heap 영역에서의 memory management 를 프로그래머가 해주니까 뭐 말이된다.
Swfit 에서는 Memory Management 는 ARC 에서 해준다.결국에는 모든 Instance 들이 reference count 라는걸 가지고있고, 그 reference count 는 properties, constants, and variable 들에 strong reference 로 잡혀져 있다. 그래서 ref count 가 0 일이 될때 메모리 해제된다! 완전 Smart Pointer 잖아! 또 궁금한게, Garbage Collection 이라는 Keyword 도 무시할수 없는건데, 이것도 전부다 ARC 에서 한다고 한다 그리고 Ownership 도 생각해봐야할 문제 인것 같다.
Reference -> Coupling -> Dependency
Reference 를 생각하고 개발하다보면, 결국에 오는건 Coupling / Dependency / Circular Dependency 문제이다. 그래서 C++ 에서는 Interface 사용하거나, weak_ptr 사용해서, Strong Count 를 안하게 하는 방법이 있다.
swift 에서는 Weak Reference 나 Unowned Reference 를 사용한다고 한다. 바로 예제코드를 보자.
classPerson{varname:Stringvarpet:Pet?// optional init(name:String){self.name=name}deinit{print("\(name) is destructed")}}classPet{varname:Stringvarowner:Person?// optional init(name:String){self.name=name}deinit{print("\(name) is destructed")}}varperson:Person?=Person(name:"Nick")varpet:Pet?=Pet(name:"Jack")// Circular Dependencyperson?.pet=petpet?.owner=personperson=nilpet=nil
와 근데 아무런 Error 안나오는게 사실이냐…? 아니면 Online Compiler 라서 그런가보다 하고 넘기긴했는데.. 좋지는 않네. 뭐 근데 정확한건, deinit() 호출 안되니까 해제가 안됬음을 확인할수 있다.
해결하려면, weak 키워드 사용하면 된다. 아래의 코드를 보자.
classPet{weakvarowner:Person?}
이걸로 변경하면, 서로의 deinit() 호출되면서 Nick 먼저 해제, 그 다음 Pet 해제 형식으로 된다.
다른 하나방법은 unowned 키워드 사용하면 된다.
classPet{unownedvarowner:Person}
Difference Between Unowned and weak
weak 는 Optional 이고, Optional 일 경우에는 Unwrapped 을 해줘야한다.(이말은 Optional 값이 nil 이 아닐 경우에 Safe 하게 unwrap 해줘야하는거) 참조된 객체가 해제되면 nil 로 설정된다. 즉 객체가 해제 되어야하는 상황에 쓸것이다.
unowned 는 Optional 이 아니다. 그리고 참조된 객체가 해제되면 RunTime Error 가 발생한다. (즉 이말은 unowned reference 는 항상 Value 를 갖기를 원한다. = 이거 좋음), weak 와 다르게 unowned unwrap 할필요가없다. 항상 Value 를 가지고 있기 때문이다.
classPerson{varname:Stringinit(name:String){self.name=name}deinit{print("\(name) is destructed")}}classPet1{varname:Stringweakvarowner:Person?// weak 참조는 옵셔널 타입init(name:String,owner:Person?){self.name=nameself.owner=owner}deinit{print("\(name) is destructed")}}classPet2{varname:Stringunownedvarowner:Personinit(name:String,owner:Person){self.name=nameself.owner=owner}deinit{print("\(name) is destructed")}}varperson:Person?=Person(name:"Nick")varpet1:Pet1?=Pet1(name:"weak dog",owner:person!)varpet2:Pet2?=Pet2(name:"unowned dog",owner:person!)// safe unwrapifletowner=pet1?.owner{print(owner.name)}print(pet2!.owner.name)person=nilpet1=nilpet2=nil
Virtual Memory 와 비슷, Paging 을 거쳐서, 주소값 조사 및 저장할곳을 정함, 그리고 메모리 할당해제 했을때, Memory Management Unit 업데이트를 해줘야함.
크기 자체는 Run Time 에 결정
inheritable
Final Keyword 사용 가능
Overall
위와 같은 특징으로 인해서? Class 가 느릴수도 있지만, 관리 측면에서는 역시 Class 가 좋고, 그리고 Struct User Data Type 으로 사용하면 될것 같다.
Intro to protocol
Swift 공식 홈페이지에서의 프로토콜의 정의는 아래와 같다. A protocol defines a blueprint of methods, properties, and other requirements that suit a particular task or piece of functionality. The protocol can then be adopted by a class, structure, or enumeration to provide an actual implementation of those requirements. Any type that satisfies the requirements of a protocol is said to conform to that protocol.
즉 결국엔 Protocol 이라는건 Enum, Struct, Class 의 어떤 Blueprint 에 해당되며, 이 Protocol 을 사용하기위해서는 어떠한 어떠한것들이 필요하다는걸 정의하는 것이다. 즉 요구사항 정리서 (Descriptor) 라고 볼수 있다. 그리고 구현은 Struct 나 Class 에서 직접하면 된다.
Example
Struct Example
CPP 와 비슷 하게 Operator 작성하면 됨
일반적으로 Swift 에서는 Equatable Protocol 을 작성해야 Instance 들의 비교가 가능
protocolGreetable{funcgreet()->String}structPerson{// define propertyvarname:Stringvarage:Intfuncgreet()->String{return"Hello, my name is \(name). Nice to meet you!"}}classRobot{varid:Intinit(id:Int){self.id=id}funcgreet()->String{return"Hello, my special id \(id). Nice to meet You!"}}letperson=Person(name:"Alice",age:32)print(person.greet())letrobot=Robot(id:4)print(robot.greet())
Protocols Extension Example
약간 Overloading 이랑 비슷한것 같기는한데, 일단 Protocol 에서 정의를 했었을때, 똑같은 Protocol 을, 다른 Objects 들이 중복적으로 사용하지 않으려면 사용.
구현된게 우선권을 얻으므로, Person 과 Robot 은 greet() 정의한대로 return, Alien 은 구현체가 없고 Extension 된 Protocol 로 채택
protocolGreetable{funcgreet()->String}extensionGreetable{funcgreet()->String{return"Hello"}}structPerson:Greetable{// define propertyvarname:Stringvarage:Intfuncgreet()->String{return"Hello, my name is \(name). Nice to meet you!"}}structAlien:Greetable{}classRobot:Greetable{varid:Intinit(id:Int){self.id=id}funcgreet()->String{return"Hello, my special id \(id). Nice to meet You!"}}letperson=Person(name:"Alice",age:32)print(person.greet())letrobot=Robot(id:4)print(robot.greet())letalien=Alien()print(alien.greet())
이제 퇴사한지도 거의 한달이 다가오고, 설도 눈깜빡할사이에 지나갔다. 12 월부터 읽었던 책을 공유하려고 하는데, 내가 “소프트웨어” 회사에 다니고 있었을때 바랬던 부분들이 있었는데, 첫번째로는 시니어가 없는 회사에서, 일단 Roadmap 에 맞게 이것저것 해보고, 다른 Resource 도 찾아보고, Process 를 채택하려고 했었던 부분도 있었고, Managing 하는것도 없어서 굉장히 곤란해서, Burn out 이 왔었었다. 이러한 부분들을 정말 가볍고 포근하게 이 책은 다 풀어주었다.
About Author
일단 이책의 Author 부터 소개하겠다. 리뷰를 할때, 주로 이책을 쓴사람은 별로 궁금하지 않았다. 이사람은 The Programatic Engineer’s New Letter 를 발행하고 있으며, Uber, Microsoft, Skype, SkyScanner 에서 Engineer 또는 Engineering Manager 로 직종을 변화시키면서 바라본 시점들을 글로 정말 시원하게 풀어주고 있으며, Software Engineer 가 성장하면서 멘토링을 정말로 필요한 사람 또는 정말 현업에서 잘하거나 못하거나 간에 꼭 보면 그 사람을 성장시켜주는 책이다고 볼수있다. 물론, 사람마다 일하는 스타일등 다르고, 어떤거에 정답이 없는거는 확실하다. 하지만 항상 권장이라는건 의견은 내세울수 있는거기 때문에, 이걸 중요시하고 보면 이 책이 정말 사람을 성장 시킬수 있는 책으로 시점이 바뀔것 이다. 그리고 이글은 특히나 내관점(junior level) 에서 쓴것이니 junior 에서 senior 를 가기 위해선 어떤것들이 필요한것인지 요약해서 쓸 예정이다.
Index
이책은 총 6 부로 아래처럼 이루어져 있다. 이책을 딱 폈을때, 정말 좋았던건 이 부록이 였다. 개발자, 엔지니어, 매니저 별로 커리어 수준에 초점을 맞춰서 읽게끔 되어있어서, 내 커리어 지금 이 순간에 승진을 위해서, 더 나은 엔지니어로 성장하려면 어떻게 해야되는지 총 26장으로 나누어져있다.
개발자 커리어의 기본 사항
유능한 소프트웨어 개발자
다재다능한 시니어 엔지니어
실용주의 테크리드
롤모델로서의 스태프 및 수석 엔지니어
결론
부록 (한국의 개발자들의 가이드)
Things I learn from this book
내가 뜻깊게 읽었던 부분을 정리하려고 한다. 사실 이 책에서 딱 이 부분이 핵심이다라고 말을 할수 없다. 약간 모든 챕터마다 어떤 챕터를 참고해라 라는 의미도 많기 때문에, 뭔가 회고록을 작성하거나, 승진을 하기 위해서 그 다음 Step 이 뭘지를 고민이 될때, 보면 정말 괜찮은 책이다.
커리어 발전을 위한 사고 방식 첫번째로는 커리어 발전을 위한 대안적 사고 방식이다. 커리어 발전을 직장의 만족도에 영향을 끼치는 요소들을 알아보자.
함께 일하는 사람 및 팀의 역학 관계
매니저 및 매니저와의 관계
팀 및 기업에서 본인의 위치
기업 문화
기업의 사명 및 사회에 대한 기여도
전문적 성장 기회
업무 중 정신적, 신체적 건강
유연성(원격 근무 가능 여부)
온콜 (온콜 업무의 강도)
일과 삶의 균형
개인적 동기
사실은 어디에서나, 새 회사에 취직한다고 하면, 그 회사의 일의 업무 양과 좋은 개발자가 있다는건 확실히 알수 없다! 그래서 “회사 가봐야 알아?” 이런말들이 많이 나온다. 하지만 전체적으로 현업에서 일을 하다보면, 저 위에 있는 목록을 다한 내용들이 아래와 같은 이미지로 표현이 될수 있다. 아무리 AI 가 나오라고 한들, 개발자 또는 엔지니어는 새로운 기술을 배워야하고, 그리고 더 나아가서 성장해야한다. 그러기위해서는 긴호흡을 가지기위해서는 아래와 같은걸로 기준을 세울수 있다.
커리어 관리 가끔씩 물경력? 이라는 소리가 굉장히 많이 나온다. 어떤 Product 나 Project 를 하고 있지만 내가 뭘 위해서 하고 있는건지를 가끔씩 놓쳐버리는때가 있을것 같다. 이때 정말로 중요한건 커리어에 대한 주인 의식이다. 기본적으로 일을 할때는 제일 중요 한것은 업무를 완수 가 중요하다. 그리고 영향력이 높은 작업을 많이 수행하는 것이다. 가끔씩은 작은 리팩토링은 팀원들에게 중요할수 있지만, 우리 회사의 Product 가 어떤 비즈니스에 영향을 끼치는건 생각을 해보지는 않았을것 이다. 그래서 팀의 우선순위 및 비즈니스의 우선순위를 두는게 중요하다. (참 회사가 어떻게해서 돈을 버는지, 정확하게 몰랐었다.).
내가 한 일을 사람들에게 알리자 이건, 자랑보다는 내가 어떤 일을 하고 있는지 다른팀에서는 전혀 모른다는 가정하에이다. 그리고 매니저와의 우호 관계를 위해서라도, 내가 영향력있는 일을 잘맞추고, 뭔가 필요한 업무를 해내면 공유하는게 중요하다. (즉 내가 하는 일에 대한 어려움은 아무도 모른다라는것이 중요한 포인드이다.)
작업 일지(살아있는 문서) 작성도 굉장히 중요하다. 나는 작업일지를 어려운 Task 에만 적용을 했었다. 연봉 협상을 했었을때, 내가 어떤 Task 를 끝냈는지, 그리고 왜 이렇게 했는지? 다시 Jira Issue 를 보면, 왜? 라는 질문 부터 나왔었다. 그래서 팀에서는 Team Daily Scrum 도 했었는데, 나만의 작업 일지를 작성해서, 성과 평가에 대한 기초 자료를 만들수 있고, 그로인해서 나를 정량적으로 평가 할수 있다는 점에서 굉장히 중요하다고 생각한다. 이거에 대해서 Template 를 보면, 팀미팅은 어떻게 했는지, 어떻게 코드 commit 을 작성 했는지를 다 작성할수 있고, 우선순위를 매길수 있다. 그리고 우선순위를 정하다보면 “아니요” 라는 말을 하기가 쉽다.
그 이외에 피드백 요청 및 피드백 전달이 있는데 나는 이부분에서는 잘한 편이라고 생각했다고 했지만, 피드백 전달에 있어서, 구체적(명확하게) 으로 말하며, 긍정적인 피드백은 진심일 때만 하자 이 부분을 제대로 커버하지 못했던것 같아서, 굉장히 전 팀원들에게 부족했다라는것이 느껴졌다. 사실 우리가 선물을 고를때 진심으로 주는것 처럼 피드백도 정말 선물이 되어야한다는 말이 이책에서 중요했던 말이다. 고민하면서 유용하고, 건설적인 대화를 이끌어가기위해서는 피드백도 진심으로 대해야한다라는게 중요하다는것을 느꼈다. 그리고 부정적인 피드백을 받았을때는 항상 나는 열린 마음으로 받아들였었다. 하지만 그렇지 않는 사람들의 감정이 실렸을때의 피드백은 굉장히 모호했다고 생각하고, 쉽지 않았었다.
다른 사람을 돕고, 흔적을 남기자. 다른사람을 돕는건 늘 잘했던것 같다. 이거에 부족하면 내가 덧붙여서 설명하고, 세미나도 들어봐서 정리해서 말을 하는것도 했었다. 하지만 나는 이거에 대한 흔적을 남기지 않았다. 즉 매니저들은 내가 도왔다라는 사실을 모르는거다. 나는 Unreal Engine 을 하면서 도움을 줄때가 많았고, 다른 팀 업무도 해봤기 때문에 경험이 있었다. 그래서 여기에 시간을 빼기는걸 사실은 원하지 않았었는데, 말을 안하게되면, 내가 내 업무에 집중하지 않는것으로 오해받을 수 있고, 나의 기여가 인정받지 못할수 있다는거에 유의 했어야했다. 즉 작업일지를 작성할때, 내가 어떤 부분에 대해서 해결을 도와주었으며, Pair Programming 을 도왔고, 뭔가 팀을 위해서 이런걸 했었다? 라는걸, 우리팀이 호흡을 하고 있다는걸 알아야, 지원을 해주는것 같았다.
매니저와의 관계 나는 Project Manager 와 관계는 항상 좋지는 않았던것 같다. 예를 들어서 요구사항이 명확하지 않았고, 정리도 안된 상태를 구현하는건 나는 Risk 가 있다고 Product 를 만들어가는 입장에서는 굉장히 좋지 않았다고 생각을 했다. “그래서 이거 해결 되겠죠?” 라고 물어보는건 굉장히 좋지 않다고 생각했었는데, 이 책에서 나에게 굉장히 따끔한 충고를 해준다. 사실상 난 Team Manager 와 하고는 생각보다 사이가 좋았고, 항상 의견을 물어보고, Task 가 잘못될 가능성이 있다는건 중요하게 공유할사항이라고 생각하면서 문서를 작성했었다. 하지만 결국엔 “신뢰”의 문제 였었던것 같고, 같은 목표를 잘바라보는것, 어떠한 상황이 됬든 해결할려고 하는게 더 중요하다라는게 좋다. 하지만 확실히 불확실한 일정은 절대 잡지 않아야한다는건 변치 않아야 한다는것도 책에서 나온다. 그래서 일단 어떤 Manager 가 됬든 상호 신뢰를 구축하는게 정말 중요하다. 라는걸 이 책에서는 중점적으로 말을 하고 있다. 신뢰에서 더 나아가 업무 성과를 인정받는게 중요하다 라는게 정말 중요하게 생각했었다.
페이스 조절 개발을 계속하다보면 지치기도 하고, 뭔가 안좋은일이 계속 발생하다보면, 개인적으로 받아들일때가 있었다. 하지만 개인적인 상황으로 뭔가 받아들였을때, 업무에 집중을 못하거나 동기가 저하될수 있어서 관성 이라는게 떨어진다. 그러면 결국엔 일을 능동적으로 처리하기도 어렵고, 비생산적으로 일을 마무리 하게 될때가 있었었다. 근데 이 책에서 이런 말을 한다. 의욕 저하가 이유라면, 무엇이 변화해하는지 자문하고 적극적으로 변화를 시도하자? 환경이나 팀, 직장을 옮겨야 하나? 업무를 완수할 적절한 기술을 보유했는가? 역량이 미흡해서, 투자를 할필요가 있는가? 더 도전적인 목표를 설정하고 야심 찬 일을 할수 있는가? 끊임없이 생각해 보아야한다. 주저 않아 있는 시간이 길어진다면, 자신을 잘 돌아보고 다시 기지개를 켤방법을 찾아보아야한다. 프로 운동선수가 장기적으로 성과와 부상방지를 위해 운동 강도를 조절하듯, 장기적인 성장을 위해 노력하고 번아웃을 방지하자. 라는 말에, 내가 정말 그냥 번아웃 상태였고, 나를 위한 주인 의식이 정말 필요했구나 라는 생각이 들었다.
승진(Promotion) 정말, 정말 내가 커리어를 쌓다가 전혀 몰랐던 사실 부분중에 하나이다. 사실 한국에서는 직급에 대한 책임감은 있다고 생각하지만, 그거에 대한 책임감 및 목표가 불문명하고 더나아가서, 보상에 대한 부분이 투명하지 않다. 하지만 이 책이 커버하는 부분에는 승진에 대해서 정확하게 나와있다. 승진을 위한 조건을 일단 보여주었는데 아래와 같다.
인식: 다른 사람에게 자신의 영향력을 미치는 게 실제 비즈니스에서의 성과보다 더 중요할 수 있다. 특히 회사가 성장하고 누군가가 비즈니스에 미치는 직접적인 영향력을 파악하기가 더 어렵다면 더욱 그렇다.
다른 사람의 지원: 승진은 의사 결정권자의 지지가 있어야만 이루어진다. 5명으로 구성된 스타트업과 수천 명으로 구성된 대기업의 의사결정권자는 다르다.
사내 정치: 정치는 승진 후보자의 조직 내 영향력과 입질르 의미한다. 승진 과정에 의사 결정권자가 많을수록, 조직이 복잡할수록, 직급이 높을수록 승진을 위해 더 복잡한 사내 정치가 필요하다.
승진 절차: 승진 절차에 따라, 평가에서 중요하게 보는 성과가가 다르다.
이책에서 나오는 terminal level 이라는 term 이 있다. 이것은 여러 기술 기업에서 사용되는 개념이고, 소프트웨어 엔지니어가 도달하기를 기대하는 직급으로, 일종의 상한선이라고 한다. 예를 들어서 구글은 L5, 메타는 E5, 우버는 L5A 직급이 터미널 레벨이라고 한다. 즉 회사에서 직급까지 성장하지 못한 엔지니어는 퇴사하거나 성과 개선 계획 (performance improvement plans) 대상이 되기도 하고, 일종의 ‘승진을 못하면 떠나는’ 시스템인거다.
terminal level 이 존재하는 이유중에서는 두가지라고 설명을 하는데, 1. 엔지니어가 이 직급에 도달하기 위해 노력한다. 터미널 레벨은 일반적으로 엔지니어가 완전히 자주적이라는 전제하에 설정한다. 이고 2. 엔지니어가 터미널 레벨 이상으로 승진한다는 보장이 없음을 명확히 한다. 직급이 높을수록 도달하기 더 어려워지기 때문에다. 터미널 레밸 이상의 직급에는 예산이 배정되어야 하는 경우가 많으며, 모든 팀이 터미널 레밸 이상의 직급을 요구할 예산이 있는것이 아니다라는 점이다.. 정말 이부분에 있어서, 이러한 terminal level 이 한국에 존재하는지? 그리고 이 직급을 달기 위해서 엄청난 노력을 하는지는 뭔가 지표가 있는지는 확실하지 않다만, 정말 Software Engineer 에서도 뭔가 뚜렷해져야 할필요가 있다고 생각한다.
그리고 더 나아가서 빅테크에서의 승진은 성과에 따른 보상이 일반적이며, 승진 기준이나 승진을 하면 보상이 다음구간의 맨아래로 이동한다라는 말, 낮은 직급에서 최고 성과를 내는 직원이 다음직급에서 평균 성과자보다 더 많은 보수를 받기도 한다던가, 승진만이 유일한 보상이 아니다라는 잔류 약속 보상 (retention grants) 같은 보상을 지급한다는거는 사실 빅테크를 가보지 않았을때에 있어서는 굉장히 좋은 중요한 정보인것 같다.
승진에 대한 기대치 이 부분이 사실은 제일 내가 궁금한 부분이였다. 영항력과 역량에 대한 기대치는 빅테크에서 어떻게 이루어지는지는 사실 나는 잘모른다. 근데 만약 내가 대기업에 다니고, 중요한 포인트를 놓치고 있다면, 책에서는 두가지 측면에서 직급에 맞는 성과를 입증해야한다고 한다.
‘무엇을(업무의 영향력)’: 엔지니어는 다음 직급 수준의 비즈니스 영향력을 입증해야한다. 예를 들어, 스태프 엔지니어는 조직 차원에서 의미 있는 문제를 해결하기 위해 장기적인 노력을 기울어야한다. 일반적으로 고객과 가까운 제품 업무는 일부 플랫폼 업무보다 영향력을 정량화하기 더 쉽다. 제품 업무는 매출증대나 비용 절감과 같은 회사의 핵심 성과지표 KPI (Key Performance indicators) 과 연계되는 경우가 많다고 한다.
‘어떻게(역량에 대한 측정)’: 각 회사는 직급마다 다차원적인 역량 기대치를 정의한다. 예를 들어 우버는 소프트웨어 엔지니어링, 디자인 및 아키텍쳐, 실행 및 결과, 협업, 효울 창출, 시민 의식 등을 역량으로 정의한다. 이런 영역에서 다음 직급의 역량을 발휘하는 직원은 이해관계자(stakeholders) 및 다른 동료들과 잘 협력한다고 볼 수 있다. 실제로 다른 역량 보다 더 큰 비중을 차지하는 역량도 있으며, 이런 비중은 회사나 승진 절차, 떄로는 부서에 따라 다르다.
결국에서는 책에서 말을 하는건 이거다. 승진을 위해서 어떤 영향력을 보이고, 어떻게 성과를 낼것인지, 어떻게 조율하고, 어떠한 어려운 문제를 해결해나갈건지를 고민을 해봐야한다라는 것이다.
승진을 위한 조언 승진을 하려면 줄을 잘타라는 말은 나는 믿을수 있다고 생각하지만, 그래도 건강한 회사 생활 엔지니어가 되려면 어떻게 해야하는지에 대해서 조언이 나와있다.
현실적으로 생각하자 : 마지막으로 성과 평가는 어땟나? 기대 이상의 성과를 거두었는가? 그렇지 않았다면 승진 가능성이 거의 없다. 다음 평가 주기에서는 기대치를 뛰어넘을 수 있도록 노력하다.
승진이 어떻게 이루어지는지 이해하자. 누가 추천하고, 누가 결정하며, 기준은 무엇이고, 회사에서는 어떤 절차를 따르나?
승진이 실제로 어떻게 이루어지는지 이해하자. 실제로 승진 결과를 보면 항상 공식적인 절차를 따르는 건 아니다. 그 과정을 경험한 사람이나 멘토와 이야기를 나눠보자. 문서화되지 않은 일반적인 차이점으로는 실제 출시된 승진프로젝트를 완료 해야한다는 점, 동료 피드백이 훨씬 덜 중요하다는점, 스태프급 엔지니어나 이사급의 피드백이 훨씬 더 중요하다는 점, 매니저의 영향력이 문서화된 승진 절차에서 제시하는 것보다 크다는 점 등을 들 수 있다.
자기 평가를 하자: 역량에 따른 경력 사다리와 같은 명확한 기대치가 있다고 가정하고, 현재 수준과 다음 단계를 비교해 자신의 현재 위치를 가늠해보자. 자기평가 템플릿이 도움이 될수 있다.
동료로부터 피드백을 받자. : 특히 연차가 높은 동료에게 내 업무 수행에 대한 피드백을 요청하자. 승진 시기가 되면 여러분의 성과를 지지할 동료가 필요하다. 동료 피드백을 줄사람과 선제적으로 상의하자. 내가 어떤 부분에서 개선이 필요하다고 생각하는가? 다음 단계로 나아가고 있는가? 부족한점이 무엇인가? 여기서 어떻게 성장해야할지에 대한 저언이 있는가? 같은 질문이 유효하다.
다음 직급의 멘토를 찾자.: 경력 사다리에서 적어도 한 단계 높은 직급에 있는 사람, 이상적으로는 같은 회사에서 해당 직급으로 승진한 사람에게 연락하자. 1:1 로 만나서 다음 직급으로 오르기까지 성장한 경험과 조언, 피드백을 요청하자. 물론 이런자리가 승진을 보장하지는 않지만, 실행할 수 있는 피드백을 줄 동료가 한 명 더 생기는 셈이다.
이런걸 주의를 했더라면, 이라는 생각이 있지만 이제 부터 차근 차근 하면 되는 생각이다. 더 디테일한건 책을 사서 읽어보기를 권장한다.
Thriving in Different Environment 여기에서 말을 하는 Thriving 에 포인트가 있다. 즉 어떤 회사에서 가든 간에, 결국엔 어떻게 성공하는가? 는 여러가지가 있지만, 이책에서 말하는 Thriving 은 Engineer 로서 어떻게 성공하는가의 특징을 이야기한다.
제품 지향적 엔지니어가 되는법
기업의 성공 방법과 이유를 이해하자: 난 이부분에 대해서는 사실 관심이 없었다. 내가 개발만 잘하면 되지 라는 생각밖에 없었는데, 비즈니스 모델을 무엇이고, 수익은 어떻게 창출되고, 가장 수익성이 높고 가장 빠르게 확장되는 분야는 무엇이고, 그 이유가 뭔지? 우리팀은 어디에 속해있는지 고민을 해보진 않았다.
프로덕트 매니저와 긴밀한 관계를 구축하자. : 대부분의 프로덕트 매니저는 제품 지향적 엔지니어가 스스로 성장하도록 멘토링할 기회에 흔쾌히 응한다. 엔지니어로서 프로덕트 매니저에게 제품 관련 질문을 하고, 시간을 들여 관계를 구축하고, 제품문제에 관여하고 싶다는 의사를 분명히 밝혀야 한다.
사용자 조사, 고객 지원 및 관련활동에 참여하자: 옛날에 Voc 도 담당했었을때가 있었고, 교육에대해서도 많이 했었다. 즉 고객이 어떻게 제품을 사용하는지 또는 제품을 어떻게 작동을 하는건지에 대해서는 알고는 있었지만, UX 담당자, 디자이너, 데티어 과학자, 운영 팀 동료 등 사용자가 어떻게 소통하는 것에대해서는 잘모르는 부분이였다.
실현 가능한 제품 아이디어를 테이블에 올리자: 비즈니스, 제품, 이해관계자를 잘 이해하고 있다면 주도권을 잡자. 진행 중인 프로젝트게 작은 제안을 하거나, 제품 관련 개발 담당자에게 설명해 팀의 작업 우선순위 조정이 용이하게 하면서 더 큰 노력이 드는 제안도 할수 있다.
프로젝트 관점에서 제품 / 엔지니어링 절충안을 제안하자. : 진행중인 프로젝트에서 제품의 기능을 타협해 개발 노력을 줄이거나, 반대로 개발을 더 하더라도 기능을 더 포함시키는 절충안을 제안하고 피드백을 수용하자
프로덕트 매니저에게 정기적으로 피드백을 받자. 제품 지향적 엔지니어가 된다는 것은 엔지니어링 기술 외에 제품 기술도 잘 갖추고 있어야한다는 뜻이다. 이에 대한 피드백을 가장 잘 줄 수 있는 사람은 프로덕트 매니저이므로 제안이 실현할 수 있는지, 어떻게 성장할 수 있는지 프로덕트 매니저의 의견을 구하자
제품에 대한 주인의식: 대부분의 숙력된 엔지니어는 사양 확보부터 구현, 출시, 작동 검증까지 제품의 시작과 끝을 모든 영역에 깊게 관여한다. 제품 지향적 엔지니어는 한 걸음 더 나아가 사용자 행동과 비즈니스 지표를 확보한 후에야 자신의 작업이 ‘완료’ 됐다고 생각하는 경우 가 많다. 출시 후에는 프로덕트 매니저, 데이터 과학자, 고객 지원 팀과 적극적으로 협력해 실제 환경에서 해당 기능이 어떻게 사용되고 있는지 파악한다. 결론을 도출할 만큼 신뢰도 높은 데이터를 확보하는 데는 몇주가 걸릴수 있다.
정말 다 주옥같은 말이니, 내가 놓친 부분도 있었고, 어떻게 내가 엔지니어로서 살아가야되는지 정말 좋은 지표가 되었던것 같다.
이직 이책에 대해서 이직에대해서 정말 Step 별로 설명을 하는데, 어떻게 이력서 부터 시작해서, 인터뷰까지 세세하게 나와있다. 꼭 꼭 참고 하길 바란다. 그중에서도 기술 면접 및 준비에 대해서 이야기를 하는데, 아래의 Bullet Point 는 공유하면 좋을것 같다.
결국에는 Applicant 가 확신을 줄수 있는지에 대해서 판단을 하는것이다. 그래서 준비하는 방법은 아래와같다.
면접 과정에 대한 정보를 수집하자: 가장 쉬운 방법은 리크루터에게 어떤 형식인지 준비 과정에 필요한 조언을 요청하는것이다.
챌린지가 어떻게 평가되는지 이해하자: 기능, 코드 품질, 테스트 중 무엇이 더 중요할까? 언어 선택이 중요한가? 평가 전에 이런 내용을 알아보자. 다시 강조하지만 리크루터가 가장 좋은 정보원이다.
대화형 면접이라면 면접관에게 질문을 하자.: 기술 면접에서는 명확한 답을 하기 위해 면접관에게 질문을 하는 편이 좋다.
과제형 코딩 괒ㅔ는 기한을 설정하자: 일부 과제형 코딩 과제에는 상당한 시간이 소요 되므로 일정을 세워야한다.
시간을 투자하기 전에 자세한 피드백이 제공되는지 확인한다. : 수십 시간을 투자해야하는 과제도 있다. 시간을 투자했는데 아무런 피드백도 없이 불합격 통보만 받으면 의욕만 떨어진다. 따라서 시작하기 전에 리크루터에게 불합격 되더라도 서면으로 피드백을 받을 수 있는지 물어보자.
To Be Useful and Smart
How To Be Useful 여기에서 한 문맥을 끊고 가려고 한다. 결국엔 성장하는 개발자 또는 엔지니어가 되기 위해서 해야되는 것 들이 무엇인지를 책은 정말 잘 설명해주고 있다. 물론 누구는 아 이런거 별거 아니고, 당연히 알아야 할것들 아니야? 라고 할수 있지만, 당연한것들을 인지하지 못할때가 있는것 같다.
일단 useful 하기 위해서는 gettting things done 기본적으로 일을 잘 처리한다는 사람이다. 잘 처리한다? 라는 사람의 영향력이 있고, 도전적인 프로젝트가 주어진다고 해도, 학습속도가 빠르고, 매니저가 신뢰할수 있고 알아서 잘하는 사람으로 여기기 때문에 더 많은 자율성을 얻는 일이 많다! 라고 한다.
또 useful 하기 위해서의 특징이 있다고 한다. 첫번째로는 직장 생활을 단순히 하는것이다. (즉 중요한 업무가 무엇인지 자문하는것이다.) 가장 중요한 업무가 무엇인가? 이번주에 단한가지 일만 할 수 있다면 뭘 할것인가? 에대해서 답변하는것이다. 나는 scrum 을 통해서, 이러한것들을 동료로 부터 insight 를 많이 얻었던것 같다. 그래서 우선순위를 작성하고, 어떤것을 먼저해야하는지 생각을 많이 했었던것 같다. 두번쨰로는 거절하는 법 배우기 난 이부분을 태도와 동일시 생각했지만, 나의 생각이 틀렸다. 너무 일이 많이 싸였을때, 요청을 어떻게 거절하는 방법을 몰랐다는 것이다. (예: 네, 저도 도와드리고 싶지만… 으로 더 중요한 업무가 있어서… 급한 업무가 있어서… 이렇게 말을 하면 된다라는 책에서의 제안이다.) 회의도 마찬가지이다. 급한일이 있어서, 회의록을 보내주시면 내용을 확인한다라고 하든지, 물론 모든 일을 거절할 필요는 없지만, 최우선 과제를 완료 하지 못할 위험이 더 크기 때문에, 거절할때는 해야한다. 라는것이다.
막힌 부분 풀기 소프트웨어를 하다보면 기술적인 문제 또는 기술적이지 않는 문제도 많이 발생한다. 예를 들어서 권한 문제라 들지, 다른팀의 코드 리뷰를 기다리다를지, 예기지 못한 장애나 복잡하고 이해할수 없는 오류 등 정말 많은 문제를 푸는데 있어서의 힘을 기르는 방법을 이책에서 소개한다.
일단 문제를 해결하기 위해서는 어떤 부분에서 막혔는지, 스스로의 벽을 느꼈는데 어디있는지 정확한 인식 및 사실을 깨닫고 인정하고, 어떻게 해야하는지 알아야한다. 어떤 문제가 막혔다면 30 분이상 생각을 해도 안된다고 했을때가 막힌거라고 한다.
막힌 상태를 뚫는 방법 이 책에서는 몇몇가지의 막힌 상태를 해결하는 방법을 많이 나와있었다. 일단 방대한 내용이지만 하나 하나 굉장히 중요하다. 왜냐하면 팀원들과의 관계나 매니저의 관계에서도 중요하기 때문이다.
러버덕을 이용한다. ‘고무 오리’ (물건 또는 자신)에게 문제를 설명하고, 이미 시도해본 접근 방식을 설명한다. 문제를 말로 풀어나가다 보면 때때로 새로운 해결책이 떠오를 수 있다.
종이에 문제를 스케치한다. 시각화하면 다른 방법이 떠오르기도 한다.
막힌 기술에 대한 공식 문서와 참고 자료를 읽는다. 언어 자체의 기능, 프레임 워크 또는 라이브러리를 설계되지 않은 방식으로 사용하고 있는지는 않는가? 쉽게 문제를 해결하는 기능을 무시하고 있는 것은 아닌가? 문서와 코드 샘플에서 단서를 찾아보자
AI 도구를 사용한다. 문제를 설명하고 이미 시도한 해결방법을 입력한 뒤, AI 도구가 제안하는 접근 방식을 확인하자.
온라인에서 비슷한 문제를 검색한다. 사람에 따라 같은 문제에 대해 다른 용어를 사용 할 수 있으므로 다양한 방식으로 검색하자
프로그래밍 Q&A 사이트에 질문한다. 기업 내부에 Q&A 사이트가 있다면 이를 활요하거나 Stack Overflow 와 같은 사이트나 관련 포럼을 찾아보자. 문제를 설명하면 해결 방법에 대한 추가 아이디어를 얻을 수 도 있다.
머리를 비운다. 산책을 하거나 관련 없는 다른 작업으로 전환하자. 다시 문제로 돌아왔을때 새로운 관점에서 문제를 바라보거나 이전에 놓쳤던 부분이 보일것이다.
처음부터 다시 시작하거나 모든 변경 사항을 취소한다: 많은 수정을 수행한 코드가 장애를 일으킨 경우, 코드가 작동한 마지막 지점으로 돌아가서 한 번에 하나씩 조금씨 수정하며 다시 시작하자. 이렇게 하면 많은 작업을 포기해도, 그 대신 프로세스에 더많은 주의를 기울여 문제의 원인을 발견할수도 있다.
자원을 받아 문제를 해결하자: 막힌 부분의 문제를 해결할 적절한 사람을 만나서 해결하도록 하자. 다만 다른 사람을 기다릴때는 정중하게 부탁하거나 상급자에게 요청을 해야한다. 그러므로 상급자에게 요청을했을때 상대방이 과잉반응을 하지 않게 어떤일을 하고 있었고, 어떻게 해결해야하는 방향성을 명확하고 문서화로 설명을 하는게 제일 좋다고 권장한다.
이렇게 실전대응법도 나와있지만, 이것에대해서는 자세하게 설명은 하지 않겠다. 하지만 1 부터 9 까지의 내용을 흝어보면 어떻게 대응해야될지를 대충이나 짐작이 갈수 있을것 같다.
작업 소요 시간 추정 사실 회사다니면서, 이게 불명확해서 야근도 했었던 경우가 초반에 정말 많았다. “얼마나 걸릴까요?” 이 질문 정말 싫었다. 근데 이걸 기피할 방법은 전혀 없다. 기업은 계획을 세워야 되고, 개발자가 싫던 좋든 소요시간 추정단계를 받아간다. 일단 해봐야한다. 추정시간에 대해서 못지키는 약속을 하게된다면 신뢰가 떨어진다. 그러므로 정확한 이유와 그에대한 명확한 설명이 필요하다. 이거에대해서 책에서는 이전에 수행되었던 작업과 이전에 수행되지 않았던 작업으로 나와있으며 디테일에 대해서는 간략하게 생략한다. 하지만 간단하게 설명을 하자면, 잘아는 기술 스택에대해서는 충분히 작업 소요시간을 추정을 할수 있다. 하지만 여기에서 멈추는게 아니라 어떠한것을 시스템에 통합할지는 PoC 나 예제 product 를 만들어서, 내가 한거에 대한 시스템 증명을 하거나 문제가 없다고 가정하는 ‘이상적인 추정치’를 제공 할수 있다 그리고 ‘최악의 추정치’도 줄수 있는것이다. 즉 프로토타입을 만들어라라는 말이다. 그리고 내가 모르는 기술을 대할때에는 멘토나 나를 도와줄수 있는 사람을 적극적으로 찾아서, prototype 이나 project 를 하는데 시간을 단축하며, 학습과정을 빠르게 할수 있는게 하나의 방법이다.
책에서 나와있는것중에서 Nielet D’mello 라는 사람이 주장하는글은 아래와같다.
“멘토는 다방면에 걸쳐 많은 경험을 싸았지만 한 사람이 모든 것을 전문적으로 다룰수는 없습니다. 게다가 모든 사람의 경험은 서로 다르고 다양합니다. 이를 활용하는 것은 커리어에서 배우고 성장할 수 있는 좋은 방법입니다. 그렇기 때문에 저는 멘토로 구성된 기술 부족의 아이디어를 믿습니다.” 라고 말한다.
멘토를 이야기하면 결국엔 다른 팀원을 말할수도 있지만 결국엔 소프트웨어 개발 엔지니어에서는 불필요한 시간을 뺏지 않으면서, 서로를 도와주며, 고맙다 표시를 확실히 해야한다. 모든것이 당연할수 없다. 이걸 이야기 하는게 바로 책에서는 선의의 통장 이라고 한다. 다른사람을 도우면 선의의 통장이 늘어나고, 합당한 이유 없이 방해하면 선의 통장의 잔고는 줄어든다. 라는것으로 선의 통장을ㄹ 빨리 소진하지말라고 한다. 동료에게 부탁할때는 꼭 사전준비가 필요하고, 그 사전 준비에서는 문제를 정확하게 설명하고 지금 까지 시도한 방법과 그 방법이 도움이 되지 않았다면, 그 다음 단계가 무엇인지를 질문하며, 요약하는게 중요하다. 결국 “존중” 하자! 그리고 내가 처음에 개발자로 들어갔을때는 솔선 수범하고 기록하고 수정을 했었지만, 어느 순간에 되게 귀찮게 느껴졌던걸 이야기를 했었다. 솔선 수범 하는 방법을 보면서 내가 놓쳤던 부분을 다시 재 확인하게 되었다.
솔선수범하는 법
불분명한 사항을 문서화한다. : 이 문서를 팀에 공유하자. 문서화를 통해 이해도를 높이고, 동료들에게 팀을 돕고 있다는 것을 보여줄 수 있으며, 향후 팀원이나 신규 입사자에게도 도움이 될수 있다.
조사에 자원한다. : 새로운 프레임워크나 기술을 시험하거나, 새로운 서비스나 구성요소를 통합하는 방법을 프로토타이핑 하는 등 어떤 것이든 할 수 있다. 경험이 부족하다면 다른 사람과 짝을 이루겠다고 제안하고 다른 사람에게 협업을 요청하자
팀에서 사용할 흥미로운 도구나 프레임워크를 조사한다: 팀원들과 배운 내ㅛㅇㅇ을 공유하자. 업무 중중간 기업에서 사용할 수 있는 도구나 프레임워크, 또는 다른 팀에서 사용 하는 도구나 프레임워크를 조사하자. 그래서 결과로 데모를 만들고, 이 결과가가 ‘이걸 사용하면 안된다’ 라는 결론이 나와도 팀원들은 당신이 자신의 역활을 넘어 더 많은 것을 배우기 위해 모험을 하고 있다는것을 인지하게 될것이다.
매니저와 예정된 프로젝트에 대한 이야기를 나눈다. : 이 과정에서 궁금한 업무에 대한 관심을 표현하자. 매니저에게 예정된 프로젝트에 대해 물어보면 우선순위를 더 잘 파악 할 수 있고, 업무적으로 앞으로 어떤 일이 있는지를 알 수 있다. 관심을 가진 프로젝트를 매니저에게 이야기하면 해당 프로젝트에 참여할 가능성이 높아진다. 또한, 미리조사해야 할 내용을 배울 수 있다.
To be Smart 결국엔 엔지니어는 코딩을 잘해야한다. 그러기 위해서는 아래의 Bullet Point 를 읽어보는것도 좋은것 같다.
코드 리뷰를 요청하거나 리뷰를 통해서 Insight 를 배우는것이 중요하다. : 코드 리뷰를 하면서 자기가 부족한 내용이나 다른 Insight 를 얻을수 있다. 코드 리뷰를 어떻게 하는것에대해서는 이 코드 리뷰의 십계명 을 보면 좋다고 했다.
코드를 작성하는 만큼 코드 읽기: 코딩 능력은 코드를 작성하면서도 가장 빠르게 성장하지만, 코드를 읽는것도 굉장히 중요하다. 가장 쉬운 방법은 팀 전체 코드베이스의 코드리뷰를 참여하거나, 동료가 변경하는거나, 어떤게 Core Logic 인지 판단이 가능할수 있다. 그리고 다른 Code Repo 나 Open Source 에 코드를 보는게 굉장히 중요하다. 물론 코딩 규칙은 다를 수 있지만, 불평하지말고 Open 된마음으로 참여하고, 읽어보자
더 많은 코딩: 사이드 프로젝트를 하거나, 코딩연습이 포함된 튜토리얼/강의, 코딩 챌린지(LeetCode, ProjectEuler), 정기적인 짧은 코딩연습등(daily code kata)이 해당된다.
높은 품질의 코드를 하자: 가독성 높은 코드나, CI / CD 가 잘 물려져있을수 있는 코드, 적절한 수준의 추상화 사용등이 있다. 그리고 항상 좋은건 예외처리 및 오류처리가 잘될수 있게 unknown 처리를 검증하는것도 하나의 방법이다.
숙련자 개발자들의 디버깅을 한번 관찰하는것도 중요하다: 시니어 개발자가 디버깅하는것과 주니어가 디버깅하는건 다를수 있다. 그러니 이과정을 살피다보면 나도 모르게 흐름을 알게 될수 있고, 새로운 도구를 찾을수 있고, 새로운 shortcut 을 찾을수 있다.
자동화된 테스트 코드 또는 TDD: 자동화된 테스트 코드 도구를 사용할수 있다면, 나중에 예기치 못한 상황을 방지 할수 있고, TDD 방식으로 Test Code Base 를 짜면서 하다보면 시스템의 한계나 개선점등을 파악할수 있다. 물론 TDD 만 추구하는것이 이상적이지 않을수 있다. Meta 에서는 TDD 를 사용하지 않는다고 한다. (그래서 Kent Back 이 어떻게 적응했는지를 이책에서 다룬다.)
Software Engineering & Developement
이책에서 정의하는 Software Engineering 과 Development 차이를 알려준다. Software Development 는 기본 계획 작성, 코딩, 테스팅, 배포, 디버깅 이 있다. Software Engineering 은 요구 사항 수집, 해결 방안 기획 및 접근 방식 간의 장단점 분석, 소프트웨어 구축, 프로덕션으로 배포, 솔루션 유지 관리, 새로운 유즈케이스로 솔루션 확장, 다른 솔루션으로의 마이그레이션 등이 있다. 단기적인 성과 뿐만아니라 자신의 업무가 어떻게 어디서 어떠한 영향력이 주어지는지에 투자를 할필요가 있다는걸 책에서는 말한다. 이책에서 더나아가 읽어볼만한책은 구글 엔지니어는 이렇게 일한다 라고 한다.
시니어가 되려면…
시니어가 되려면 엔지니어링 기술 숙달로서는 부족하다. 오히려 다른 사람과 효율적으로 협업하고, 팀의 업무 수행을 돕기가 훨씬 어렵기 때문에, 업무의 투입하는 노력보다, 업무의 영향력이 더 중요하다. (결국에는 Align 이 될것같다.) 현명한 업무 방식(Detail 한건 책을 참고) 찾고 노력해야 성과를 얻을수 있다고 한다. 별개로 멘토링이나 다른 사람이 더 효율적인 엔지니어가 될수 있게 끔 가이드 하는것도 중요하고, 멘토링 자체도 굉장히 시야가 넓어지고, 복잡한 문제를 풀어갈수있는 Capacity 가 늘어난다. 다소 시니어 엔지니어 Term 은 기업마다의 기대치는 다를수는 있지만, 복잡한 문제를 해결하며, 팀과의 역학 관계, project 의 수행 능력, risk management 까지 필요한 소양이라고 생각한다고 이 책에서는 말한다. 사실 이책에대해서 시니어 관련된건 조금 애매한 부분이 있어서, 당연한 말을 쓴것 같지만, 역활에 대한 책임 “영향력” 이 정말 중요한것 같다. 내가 어떤 문제를 푸는데 있어서, 쉽게 풀릴수 있는 문제인지, 아닌지에 대해서는 사실 경험에 나오는거라고 나는 생각한다. 그리고 그걸 어떻게 효율적으로 푸느냐도 마찬가지 이며.
마지막으로…
이책의 부록에 있는 말들이 생각보다 주옥같았다. 어떻게 하면 그렇게 커리어가 변경됬는지 부터 시작되서 기본적인 소양등으로 이책을 읽으면서 되게 Align 이 되어있다라는 측면에서 이책은 정말로 좋은 Guidebook 이다! 정말로 아끼는 개발자 친구가 있다고 하면 꼭 추천하고 선물로 주고싶을정도의 완성도가 정말 높았다. 이 책을 리뷰를 할수 있게끔 한 한빛미디어에게도 큰 감사를 표합니다.
예전에 대학원생일때, Addition, Subtraction, Division, Multiplication 에 관련되서 Project 로 작성한적이 있다. (어떻게하면 각 Operation 을 빠르게 할수 있는지에 대해서, Hardware 측면에서 더 이야기를 했던것 같아. Multiplexer 의 추가 등…)
그래서 일단 Addition / Subtraction / Multiplication 에 대한 예제코드를 봐보겠다. C++ 로 작성했으며, 항상 First Digit 이 Second Digit 의 길이가 크다라는 가정하에 작성을 했다. 자 특이점은 전부다 String 으로 처리한다는 점이다.
자 여기에서는 조금 다를수 있다. 기본적으로 Two loops 가 있으니까 (N-1) * (N-1) = N^2 로 표현할수 있는데, ‘0’ 의 index 를 찾는데 과정에서, constant * N^2 로 표현할수 있다. 물론 상수를 이야기를 안할수도 있는데 Major 로 Impact 로 줄수 있는걸 찾는게 여기에서는 Main point 이다.