CI란?

Continuous Intergration의 줄임말로, 지속적인 통합을 의미한다. CD(Continuous Delivery&Deployment)와 짝꿍 관계기도 하다. 

 

그럼 지속적 통합은 뭘까? 우선 통합의 의미를 살펴보자. 나 또는 다른 사람이 어떠한 코드 변경(새로운 기능 추가, 수정, 삭제 등)을 했을 때 그 코드가  빌드 및 테스트되어 우리의 공유 리포지토리(깃헙 등)에 병합되는 걸 통합이라 부른다. 지속적 통합이란, 이 과정이 정기적으로 계속해서 일어난다는 것을 의미하는 것이다.

 

이는 다수의 개발자가 다 함께 코드 작업을 할 경우, 서로 충돌이 일어날 수 있는 문제를 해결하기 위해 도입된 개념이라고 한다. 통합 과정을 주기적하게 되니, 자연스럽게 충돌 과정이 최소화된다는 거다. CI는 다시 말해 빌드 및 테스트를 자동으로 실시하여 공유 리포지토리에 통합하는 과정이라고 이해할 수 있으며, 이를 통해 코드 변경 내용을 우리 손을 거치지 않고 자동으로 빌드하고 테스트할 수 있다.

 

 

GitHub Action이란?

GitHub에서 제공하는 서비스로, CI & CD 플랫폼이다. 리포지토리에 .github폴더를 만들고, 그 안에 workflows폴더를 만든 뒤 그 안에 yaml파일을 만드는 것으로 구축할 수 있다. 그러면 어떤 동작이 발생했을때, yaml파일에 내가 작성했던 workflow가 실행되게 할 수 있다.

 

 

플러터 프로젝트 CI 구축

GitHub marketplace에 flutter란 이름으로 검색하면, Flutter Action라는 이름의 Action이 있다

 

https://github.com/marketplace/actions/flutter-action

 

Flutter action - GitHub Marketplace

Setup your runner with Flutter environment

github.com

 

친절하게 사용법이 나와있어서 따라하면 된다.

난 우분투, 윈도우, 맥 모두에서 테스트 & 빌드를 해볼 수 있게끔 만들었다.

 

name: Flutter CI (test & build)

on:
  pull_request:
    branches: [ main ]
  push:
    branches: [ main ]
    
jobs:
  Tests-and-build-ubuntu-and-window:
    name: Tests and Build on ${{ matrix.os }}
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest]
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-java@v2
        with:
          distribution: 'zulu'
          java-version: '11'
      - uses: subosito/flutter-action@v2
        with:
          flutter-version: '3.10.0'
          channel: 'stable'
      - run: flutter pub get
      - run: flutter test
      - run: flutter build apk
      - run: flutter build appbundle

  Tests-and-build-macos:
    name: Tests and Build on MacOS
    runs-on: macos-latest
    steps:
    - uses: actions/checkout@v3
    - uses: subosito/flutter-action@v2
      with:
        flutter-version: '3.10.0'
        channel: 'stable'
        architecture: x64
    - run: flutter pub get
    - run: flutter test
    - run: flutter build ios --release --no-codesign

 

 

uses : 다른 사람들이 이미 만든 action을 이용하는 것

run : 직접 커맨드를 실행하는 것

 

이라고 이해하면 된다.

 

[ runs-on ] 작업들이 실행될 인스턴스 환경(OS)

[ uses: actions/checkout@v3 ] 작업한 깃헙 레포지토리의 소스코드 가져오기(일단 코드를 가져와야 테스트하든 빌드하든 하니까)

[ uses: actions/flutter-action@v2 ] 플러터 SDK 설치. with로 버전명, 채널을 설정 가능

[ run : flutter pub get ] 체크아웃으로 가져온 프로젝트가 사용하는 패키지들 설치

 

 

 

음..사실 yaml파일을 내가 한 것보다 더 리팩토링이 가능했다. if문 등을 통해 더 줄일 수 있겠다는 생각이 들었지만 GitHub Action을 처음 하던지라 일단 동작하게끔 만들자는 생각으로 macos에서 실행되는 건 따로 분리를 했다. 우리의 킹갓지피티에게 물어보니 더 줄일 수 있는 것 같다.

 

CI/CD를 구축해본 적이 없어 GitHub Action 공부를 많이 해야 할 것 같아 좀 걱정이었는데 막상 해보니까 되게 간단하다는 생각이 든다. (물론 CD는 구축하지도 않았지만ㅋㅋ) 역시 뭐든 직접 해봐야 더 잘 알게 되는 것 같다.

스플래시 스크린이란?

애플리케이션 실행 시 화면 컨텐츠가 로딩되기까지 일시적으로 보여주는 화면을 말한다.


그럼 이걸 플러터에선 어떻게 만들 수 있는가?

찾아보니까 다른 방법들도 있었지만, 가장 쉬운 방법은 flutter_native_splash라는 패키지를 이용하는 것이었다.

 

매우 쉽다. 아래 링크에 나온 방법대로 패키지를 설치하고 사용하면 된다. 

https://pub.dev/packages/flutter_native_splash

 

flutter_native_splash | Flutter Package

Customize Flutter's default white native splash screen with background color and splash image. Supports dark mode, full screen, and more.

pub.dev

 

패키지 설치 후,  flutter_native_splash.yaml을 만들어주고 다음 내용을 복붙해준다. 위 웹페이지에 있는 내용과 동일하다.

 

flutter_native_splash:
  # This package generates native code to customize Flutter's default white native splash screen
  # with background color and splash image.
  # Customize the parameters below, and run the following command in the terminal:
  # dart run flutter_native_splash:create
  # To restore Flutter's default white splash screen, run the following command in the terminal:
  # dart run flutter_native_splash:remove

  # color or background_image is the only required parameter.  Use color to set the background
  # of your splash screen to a solid color.  Use background_image to set the background of your
  # splash screen to a png image.  This is useful for gradients. The image will be stretch to the
  # size of the app. Only one parameter can be used, color and background_image cannot both be set.
  color: "#42a5f5"
  #background_image: "assets/background.png"

  # Optional parameters are listed below.  To enable a parameter, uncomment the line by removing
  # the leading # character.

  # The image parameter allows you to specify an image used in the splash screen.  It must be a
  # png file and should be sized for 4x pixel density.
  #image: assets/splash.png

  # The branding property allows you to specify an image used as branding in the splash screen.
  # It must be a png file. It is supported for Android, iOS and the Web.  For Android 12,
  # see the Android 12 section below.
  #branding: assets/dart.png

  # To position the branding image at the bottom of the screen you can use bottom, bottomRight,
  # and bottomLeft. The default values is bottom if not specified or specified something else.
  #branding_mode: bottom

  # The color_dark, background_image_dark, image_dark, branding_dark are parameters that set the background
  # and image when the device is in dark mode. If they are not specified, the app will use the
  # parameters from above. If the image_dark parameter is specified, color_dark or
  # background_image_dark must be specified.  color_dark and background_image_dark cannot both be
  # set.
  #color_dark: "#042a49"
  #background_image_dark: "assets/dark-background.png"
  #image_dark: assets/splash-invert.png
  #branding_dark: assets/dart_dark.png

  # Android 12 handles the splash screen differently than previous versions.  Please visit
  # https://developer.android.com/guide/topics/ui/splash-screen
  # Following are Android 12 specific parameter.
  android_12:
    # The image parameter sets the splash screen icon image.  If this parameter is not specified,
    # the app's launcher icon will be used instead.
    # Please note that the splash screen will be clipped to a circle on the center of the screen.
    # App icon with an icon background: This should be 960×960 pixels, and fit within a circle
    # 640 pixels in diameter.
    # App icon without an icon background: This should be 1152×1152 pixels, and fit within a circle
    # 768 pixels in diameter.
    #image: assets/android12splash.png

    # Splash screen background color.
    #color: "#42a5f5"

    # App icon background color.
    #icon_background_color: "#111111"

    # The branding property allows you to specify an image used as branding in the splash screen.
    #branding: assets/dart.png

    # The image_dark, color_dark, icon_background_color_dark, and branding_dark set values that
    # apply when the device is in dark mode. If they are not specified, the app will use the
    # parameters from above.
    #image_dark: assets/android12splash-invert.png
    #color_dark: "#042a49"
    #icon_background_color_dark: "#eeeeee"

  # The android, ios and web parameters can be used to disable generating a splash screen on a given
  # platform.
  #android: false
  #ios: false
  #web: false

  # Platform specific images can be specified with the following parameters, which will override
  # the respective parameter.  You may specify all, selected, or none of these parameters:
  #color_android: "#42a5f5"
  #color_dark_android: "#042a49"
  #color_ios: "#42a5f5"
  #color_dark_ios: "#042a49"
  #color_web: "#42a5f5"
  #color_dark_web: "#042a49"
  #image_android: assets/splash-android.png
  #image_dark_android: assets/splash-invert-android.png
  #image_ios: assets/splash-ios.png
  #image_dark_ios: assets/splash-invert-ios.png
  #image_web: assets/splash-web.gif
  #image_dark_web: assets/splash-invert-web.gif
  #background_image_android: "assets/background-android.png"
  #background_image_dark_android: "assets/dark-background-android.png"
  #background_image_ios: "assets/background-ios.png"
  #background_image_dark_ios: "assets/dark-background-ios.png"
  #background_image_web: "assets/background-web.png"
  #background_image_dark_web: "assets/dark-background-web.png"
  #branding_android: assets/brand-android.png
  #branding_dark_android: assets/dart_dark-android.png
  #branding_ios: assets/brand-ios.gif
  #branding_dark_ios: assets/dart_dark-ios.gif

  # The position of the splash image can be set with android_gravity, ios_content_mode, and
  # web_image_mode parameters.  All default to center.
  #
  # android_gravity can be one of the following Android Gravity (see
  # https://developer.android.com/reference/android/view/Gravity): bottom, center,
  # center_horizontal, center_vertical, clip_horizontal, clip_vertical, end, fill, fill_horizontal,
  # fill_vertical, left, right, start, or top.
  #android_gravity: center
  #
  # ios_content_mode can be one of the following iOS UIView.ContentMode (see
  # https://developer.apple.com/documentation/uikit/uiview/contentmode): scaleToFill,
  # scaleAspectFit, scaleAspectFill, center, top, bottom, left, right, topLeft, topRight,
  # bottomLeft, or bottomRight.
  #ios_content_mode: center
  #
  # web_image_mode can be one of the following modes: center, contain, stretch, and cover.
  #web_image_mode: center

  # The screen orientation can be set in Android with the android_screen_orientation parameter.
  # Valid parameters can be found here:
  # https://developer.android.com/guide/topics/manifest/activity-element#screen
  #android_screen_orientation: sensorLandscape

  # To hide the notification bar, use the fullscreen parameter.  Has no effect in web since web
  # has no notification bar.  Defaults to false.
  # NOTE: Unlike Android, iOS will not automatically show the notification bar when the app loads.
  #       To show the notification bar, add the following code to your Flutter app:
  #       WidgetsFlutterBinding.ensureInitialized();
  #       SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: [SystemUiOverlay.bottom, SystemUiOverlay.top], );
  #fullscreen: true

  # If you have changed the name(s) of your info.plist file(s), you can specify the filename(s)
  # with the info_plist_files parameter.  Remove only the # characters in the three lines below,
  # do not remove any spaces:
  #info_plist_files:
  #  - 'ios/Runner/Info-Debug.plist'
  #  - 'ios/Runner/Info-Release.plist'

 

background image로 쓸 이미지가 있으면 주석 풀어서 경로 써주면 되고.. 뭐 그런 식이다

참고로 안드로이드 12 이상을 쓸 경우 android 12: 쪽에 있는 내용도 별도로 주석풀어서 작업해줘야 한다!! 이거 안 하면 안드로이드 쪽에선 에러난다.

 

또한 위 웹페이지에서 살펴볼 수 있듯, 안드로이드 12 이상을 쓰는 경우에는 권장하는 이미지 사이즈가 있다. 그거에 맞춰서 이미지 만들면 됨. 대략 가로 세로 1152px이어야 하고, 가운데에 지름이 768px인 원을 두고 그 안에 다 보여줄 수 있게끔 이미지를 만들어야 한다고 함.

 

그 뒤 터미널에 다음 커맨드를 입력하면 된다

dart run flutter_native_splash:create

 

그럼 알아서 배율에 맞게끔 이미지들이 생성되며(스플래시 스크린에서 보여줄 이미지들이 있는 경우) 스플래시 스크린이 만들어진다.

 

참고로, 수정이 필요한 경우 

flutter pub run flutter_native_splash:remove

 

요 커맨드 입력하고 내용 수정한 후, 다시 dart run ~ 이거 하면 된다.

형제간 margin병합 현상

형제간 margin충돌이 일어날 때, 각각의 margin값들이 반영되는게 아니라 더 큰 margin으로 병합되는 현상.

음 말로 적으니까 뭔 말인지 어렵다..

 

  <div id="box1"></div>
  <div id="box2"></div>
    #box1 {
      width: 100%;
      height: 200px;
      background-color: yellow;
      margin-bottom: 100px;
    }  
    
    #box2 {
      width: 100%;
      height: 200px;
      background-color: pink;
      margin-top: 50px;
    }

 

요로코롬 HTML, CSS가 작성돼있다고 하자. box1, box2는 형제 관계고 box는 밑 쪽으로 100px만큼의 margin을, box2는 위쪽으로 50px만큼의 margin을 가지고 싶어 한다. 상식적으론 둘의 의견을 반영해 150px의 margin이 생기는게 맞다고 생각되지만, 

 

 

실제론 이렇게 100만큼의 margin만 가지게 된다. 이게 형제간 margin 병합 현상. 충돌이 일어날 때 더 큰 margin값이 채택된다는 얘기다.

 

 

부모 자식간 margin병합 현상

자식만 margin을 두고 싶지만 부모도 함께 margin이 적용되는 현상을 말한다.

 

  <div id="div">  
    <article></article>
  </div>
    #div {
      width: 100%;
      height: 400px;
      background-color: yellow;
    }
    
    article {
      width: 100px;
      height: 100px;
      background-color: red;
    }

 

요로코롬 HTML, CSS가 작성돼있다고 하자. 현재 화면은 다음과 같다.

 

 

여기서 노란색은 놔두고 빨간색 박스만 밑으로 50px만큼 움직이고 싶어서 빨간색 박스(article태그)에 margin-top을 다음과 같이 먹였다고 하자.

 

    #div {
      width: 100%;
      height: 400px;
      background-color: yellow;
    }
    
    article {
      width: 100px;
      height: 100px;
      background-color: red;
      margin-top: 50px;
    }

 

하지만 결과는,

 

 

이렇게 부모도 같이 움직이게 된다. 이를 부모 자식간 margin병합 현상이라 한다. 자식에 쓰인 margin이 부모에게도 적용되는 것!

 

참고로 이건 자식에게 position값으로 absolute를 멕이는 것으로 해결 가능하다. absolute가 부모를 기준으로 적용되는 position값이기 때문!

Recoil이란?

React.js를 만든 Facebook에서 만든 전역상태관리 라이브러리. 이 이상의 설명은 필요없을 듯 하다.

 

전역 상태란?

컴포넌트에서 쓰이는 상태값(state)가 아닌, 이곳저것에서 전역적으로 쓰이는 상태(state)를 말한다.

 

전역 상태를 사용하는 이유

굳이 안 써도 되긴 한다. 하지만 그렇게 된다면 서로 다른 컴포넌트끼리 특정한 값(= 상태 = state)를 공유하려면 공통된 상위 컴포넌트로부터 prop을 계속 전달하는 방법을 써야 하는데,

 

  • 중간에 거치는 컴포넌트들마다 하나하나 prop을 전달하게끔 코드 작성하는거 귀찮음
  • 중간에 거치는 컴포넌트들은 전달받은 prop을 하위 컴포넌트로 전달만 하고 사용은 안 할 수도. 즉 비효율적
  • 중간에 거치는 컴포넌트들이 많아지면 나중엔 prop을 추적하기가 힘들어짐

 

이런 문제들이 있다. 이 때 전역적으로 쓰는 상태를 만든다면, 어느 컴포넌트에서나 전역 상태에 접근가능하므로 위에서 말한 불편들이 해소될 수 있다!

 

그럼 수많은 전역관리 툴 중 Recoil을 사용하는 이유?

일단 따지고보면 context API는 전역상태관리를 해주는 기능이라기보다는 단순히 전역적으로 상태를 공유하게끔 해주는 기능이라고 보는게 정확하다. context API를 사용해서도 전역상태값을 변경하는 건 물론 가능하지만, context API를 사용해 상태값을 변경하면 provider로 감싸져있는 컴포넌트들이 죄다 렌더링된다는 치명적 단점이 있다! (provider 하위에서 context를 구독하는 모든 컴포넌트는 provider의 value가 바뀔 때마다 다시 렌더링된다는 말)

 

// ContextTest.jsx

export const myContext = createContext("JOFE");

const ContextTest = (props) => {
    const [name, setName] = useState("JOFE");
    
    return (
        <myContext.Provider value={[name, setName]}>
            <Welcome />
            <Hello />
        </myContext.Provider>
    )
}

 

// Welcome.jsx

const Welcome = (props) => {
    const [name, setName] = useContext(myContext)
    return (
        <div>
            <button onClick={() => {setName("Nunu")}}>이름바꾸기</button>
            {name}
        </div>
    )
}

 

// Hello.jsx

const Hello = (props) => {
    useEffect(() => {
        console.log("Hello 렌더링")
    })

    return (
        <>
        </>
    )
}

 

myContext라는 context의 value로 name이란 상태와 name을 변경해주는 세터함수를 넣어줬다. 그리고 이 context를 Welcome컴포넌트에서 사용하는 모습이다. Hello컴포넌트는 useEffect를 사용해서 렌더링될때마다 console에 Hello 렌더링이란 문구를 찍어준다.

 

 

이름바꾸기를 하면?

 

 

myContext를 사용하지 않는 Hello컴포넌트도 단순히 provider 안에 있다는 죄(?)로 다시 렌더링이 되는 모습을 확인할 수 있다..!

 

바로 여기서 Recoil이 이런 문제들을 해결해줄 수 있기 때문에 사용한다. 하단에서 보여드림.

 

Recoil 기초적인 사용법

일단 상위 컴포넌트에서 RecoilRoot란 컴포넌트로 애들을 감싸주면 된다.

 

import { RecoilRoot } from 'recoil';

function App() {
  return (
    <RecoilRoot>
      <RecoilTest />
    </RecoilRoot>
  );
}

 

그리고 atoms.js파일을 만들어서 요로코롬 atom들을 만들어준다.

※ atom = Recoil에서 사용하는 상태들의 명칭이자 단위라고 생각하면 됨. 이 atom들의 key들은 각각 고유한 값이어야 함! 중복되면 안 된다는 뜻

 

// atoms.js

import { atom } from "recoil";

export const nameState = atom({
    key: "name",
    default: "JOFE"
});

 

참고로 굳이 atoms.js에 만들 필요는 없는데 이렇게 전역상태들을 한 파일에 관리해주는 게 더 편하다고 한다.

 

// RecoilTest.jsx

const RecoilTest = (props) => {
    return (
        <div>
            <Welcome />
            <Hello />
        </div>
    )
}

 

 

// Welcome.jsx

const Welcome = (props) => {
    const [name, setName] = useRecoilState(nameState)
    return (
        <div>
            <button onClick={() => {setName("Nunu")}}>이름바꾸기</button>
            {name}
        </div>
    )
}

 

요로코롬 useRecoilState를 사용해서 React의 hook마냥 쓸 수 있다! React스럽다는게 최고 장점인 듯. 참고로 세터함수는 필요없고 값만 읽고 싶다면 useRecoilValue를 쓰면 된다.

 

암튼, 이렇게 하면

 

 

 

요로코롬 Hello컴포넌트는 리렌더링되지 않는다! 오직 해당 atom을 사용하는 애들만 바뀐다.

 

 

※ 참고한 글https://hong-jh.tistory.com/entry/Context-API%EB%8A%94-%EC%99%9C-%EC%93%B0%EA%B3%A0-%EA%B7%B8%EB%A0%87%EB%8B%A4%EB%A9%B4-Redux%EB%8A%94-%ED%95%84%EC%9A%94%EC%97%86%EC%9D%84%EA%B9%8C

 

Context API는 왜 쓰고, 그렇다면 Redux는 필요없을까?

안녕하세요 오늘은 제가 평소에 궁금했던 주제로 글을 써보려고 합니다. Context API의 기능과 왜 사용하는지, Context API의 리렌더링 이슈란 무엇인지 그리고 Redux를 대체할 수 있는지. 위 내용을 설

hong-jh.tistory.com

 

sw마에스트로 14기를 준비하면서, 합격 후기들을 이곳저곳 긁어모으면서 많은 도움을 얻었다. 나도 면접과정을 다시 돌아보면서 부족했던 부분을 점검하고, 15기 + 를 준비하시는 분들에게 도움이 됐으면 좋겠다는 생각에 후기글을..써보기로 했다


2차 코테 복기 및 주저리주저리 ↓

https://jofestudio.tistory.com/98

 

[SW 마에스트로 14기] 코딩테스트 2차.. 주저리 주저리

3월 8일 오늘 14시, 저번 주 토요일에 본 sw마에스트로 2차 코테의 결과가 나왔다. 일단 결과는 합격이어서 다음 주 심층면접을 준비해야 하는 상황. 마음도 진정시키고, 어차피 심층면접 때 2차 코

jofestudio.tistory.com


포트폴리오 준비과정

자신이 했던 프로젝트를 2개까지 쓸 수 있고, 만약 프로젝트 경험이 없다면 sw역량을 기르기 위해 했던 노력들이나 소마에서 만들고 싶은 프로젝트, 그리고 소마에서의 각오 등을 포트폴리오에 자유롭게 기술하라고 제시해주셨다.

 

사실 나는 포트폴리오가 걱정됐던 게

 

  • 백엔드를 현재 지향하지만 백 쪽은 공부한지 별로 안 됐고 백 관련 프로젝트 경험 x (물론 멋사 9기 해커톤할 때 django써보긴 했지만 안 쓴지 오래 지났고 지금은 spring쪽 공부하니까 논외..)
  • 프론트 쪽으로 했던 팀 플젝이 1개 있지만 대중적인 커뮤니티 만드는 플젝이어서 그닥 매력이 없음
  • 멋사 9기 해커톤 때 했던 팀 플젝은 너무 허접해서(ㅋㅋㅋㅋ..) 얘도 매력없음

 

이런 상황이었다.. 백 쪽으로 준비하고 있던 상황인데 백 관련 경험이 없다는 것도 내 약점이라 생각했고, 그렇다고 프론트 쪽에서의 경험을 어필하자니 그 경험도 의미있던 경험은 아니라고 생각될 여지가 있다는 것이 내 입장이었다.

 

전체적인 포폴의 컨셉 즉 방향을 어떻게 해주지부터 생각했다. 일단 난 2년 반 넘는 시간동안 네이버를 거쳐 티스토리까지 개발 관련 블로그를 나름 꾸준하게 운영해오고 있다. 또한 코딩에 관련된 컨텐츠를 올리는 유튜브(물론 영상들을 많이 올리진 않고 중간중간 코딩이 아닌 영상도 있지만)도 운영하고 있다. 내 강점, 나만의 차별점이 될 수 있는 부분들을 어필해야 할 것 같다고 생각해 일단 블로그&유튜브 운영에 관한 얘기는 해주자고 생각했다.

 

뭐가 됐든 플젝경험이 빈약한 편인 만큼, 활동내역 쪽에서 내가 어떤 경험들을 했는지를 어필해주는 게 좋다고 생각했다. 2년 전에 했던 부스트코스 서포터즈 3기, 대학생 코딩 캠프 1기를 비롯해 멋사 9기에서 활동했던 걸 적기로 했다. 사실 부스트코스 서포터즈 3기와 대학생 코딩 캠프 1기는 뭐 엄청난 의미와 배움이 있다거나 밖에서 알아주는(?) 그런 활동은 아니었지만, 일단 싹싹 긁어모아서 나 이런 저런 것들 해봤다!라고 어필해주는 게 내 상황에선 더 좋다고 생각돼서 넣어줬다. 그리고 작년 여름방학 때 학교에서 Level DB라는 오픈소스를 분석하는 스터디를 했던 것도 넣어줬다. 스터디할 때 조교님이 "시스템 소프트웨어 분야로 진출하지 않으시는 분들도 이 스터디를 포폴 경험에 넣으실 수 있을 겁니다~"라고 해줬던 적이 있는데, 당시에는 별 생각 없었지만 이걸 진짜로 포폴에 넣게 될 줄은 몰랐다.. 그래도 나한테는 의미가 있던 활동이었기 때문에 넣어줬다. 그 후론 마지막으로 작년 하반기에 우테코 5기 프리코스에 참가했던 걸 적어줬다. 최종 탈락하긴 했지만 그래도 열심히 참가한 과정이었고 그 안에서 얻은 게 많았으니까.

 

이렇게 일단 긁어모아서 활동내역을 적고 나니, "나 그래도 나름 이것저것 해봤네?"라는 생각이 들었다. 나름 그래도 이것저것 해보고 싶은 것들 해보고 성장하려고 했었구나..라는 생각. 그러다 이 부분을 어필하면 되겠다는 생각이 들었다. 성장하기 위해서 이런저런 경험들을 쌓아보려고 했다!라는 부분! 웹 프론트, 시스템 소프트웨어, 백엔드라는 분야들을 접해봤다는 것을 이 활동내역을 통해서 보여줄 수 있기 때문이었다. 그래 그러면 "나는 성장하려고 노력하는 사람이고, 다양한 경험들을 쌓으며 성장하려고 노력해왔다. 그리고 소마를 통해서도 성장하고 싶다!"라는 컨셉을 잡기로 했고, 이 컨셉을 포폴에 녹여내려 애썼다.

 

이제 플젝 2개를 어떤 것들을 보여주냐가 남았다. 프론트쪽 담당해서 커뮤니티 만들어본거는 그닥 큰 매력이 없어도 일단 넣어주는 게 좋을 것 같다고 생각했다. 이제 남은 하나를 뭘 채워야하나..를 고민했는데, 멋사 9기 해커톤에서 했던 허접한 플젝을 넣을까 하다가 작년에 학교에서 ai수업을 진행하며 했던 팀 프로젝트를 넣기로 했다. 사실 ai프로젝트는 앵간하면 안 넣으려고 했던 이유가 해당 플젝에서 진행했던 부분들에 대한 것들은 당시에 빡세게 했던 만큼 빠삭하지만, 그 외에는 나도 ai쪽은 정말 모르기 때문에 관련된 질문이 들어오면 탈탈 털릴 수도 있다는 걱정 때문이었다. 그러나 내가 포폴 컨셉 자체를 다양한 경험들을 접해보려고 했다는 점을 어필하는 쪽으로 정한 만큼, ai플젝을 했던 걸 보여주며 ai분야도 접해보려 했다는 것을 보여주는게 좀 더 포폴의 결에 맞는다는 생각이 들었다. 뭐 실제로 내가 ai쪽을 접하려고 그 수업을 신청했었으니까. 대신, 이 플젝에 관련된 질문들에 대한 대비는 그만큼 빡세게 해야 한다는 각오를 했다. 그래도 ai쪽 플젝을 보여주는게 뭔가 겉보기에도 까리해보인다는 생각도 했던 듯 하다.

 

 

포폴 발표 연습

포트폴리오를 만들면서 중간중간에 대본 작성도 계속 해나갔다. 3분이 생각보다 길 줄 알았는데 엄청 타이트했다. 사실 내가 어필하기로 했던 부분들은 포폴에만 녹여내는게 아니라 발표때도 말하는 게 좋다고 생각했는데, 그러기엔 3분을 훌쩍 넘어버린다.. 그래서 최대한 각종 활동들과 포폴과 관련된 내용들은 시작한 동기와 느낀 점 위주로만 설명하기로 했다.

 

발표 연습은 틈틈이 했다. 틈날 때마다 방에서 연습했고, 학교 가는 길에도 했고, 샤워하면서도 했고.. 면접 보기 3일전부터는 학교에서 저녁에 빈 강의실에서 빔 프로젝터로 화면을 띄우고 발표하는 연습을 했다.

 

 

면접 질문 대비

나는 포폴 자체를 나름대로 질문들을 유도하는 식으로 만들었다. (ex: 소프트 스킬을 기르기 위해 노력해오고 있습니다 → 면접관이 "소프트 스킬을 기르기 위해 어떤 노력들을 했죠?"라고 물을 수 있도록)..그래서 내 포폴을 보면서 면접관 입장에서 무러볼 만한 것들을 최대한 다 뽑아내서 준비했다. 포폴에서 나올 수 있는 기술적인 질문들은 꼬리질문들까지 최대한 준비해갔다 (ex: React를 공부해보셨는데 React의 특징이 뭐죠 ? 방금 Virtual DOM을 말씀해주셨는데 Virtual DOM이 뭐죠?, Class Activation Map이 어떤 기술이죠 ? → Class Activation Map의 원리가 뭐죠? → CNN이 어떤 구조를 가지고 있죠 ? → Pooling이 어떤 걸 말하는 거죠 ? → Pooling의 종류가 뭐가 있죠 등등..)

 

 

이 외에도 이전 기수에서 나왔던 인성질문(특히 포폴 발표가 생긴 13기 때 나온 기출 질문들을 중점적으로)들을 긁어모아서 따로 준비했고, cs질문은 기초적인 내용들(프로세스와 스레드 차이, osi 7계층, http vs https 등..) 20개 정도만 긁어서 준비했다.

 

아 그리고 2차 합격자들만 남아있는 오픈톡방에서 따로 면접 스터디를 구했다. 강남에서 모이는 스터디, 그리고 내 동네 쪽에서 모이는 스터디에 참가하면서 서로 3분동안 포폴을 발표하고 각자 포폴에서 질문할 수 있겠는 내용들을 질문하는 스터디였다. 다른 사람들 앞에서 발표하는 것도 연습할 수 있고, 다른 사람들은 내 포폴을 보고 이런 것들을 궁금해하면서 물을 수 있음을 알게 되니까 예상질문들에 대한 대비도 어느 정도 할 수 있었다. 만약 15기 +를 준비하시는 분들이 있다면 이런 면접 스터디에 참가해보는 것도 추천합니다:)

 

 

나름대로의 면접 전 마인드세팅

면접 질문들에 대한 답변을 말하는 연습을 계속하면서, 내가 말을 좀 빨리 하게 된다는 걸 알게 돼서 최대한 천천히 말하려고 연습했었다. 아무래도 긴장하게 되면 말을 좀 빨리하면서 더듬는 버릇이 있어서 그런 듯 하다. 근데 이게 단기간 내에 몰라볼 정도로 고쳐지기는 어렵다고 생각했다. 그래서 최대한 천천히 말하려 하되, 더듬어도 괜찮아! 절어도 괜찮아! 라는 걸 스스로한테 주입시켰다. 모든 질문에 대한 답을 다 잘 말할 필요 없다, 몇 개는 잘 모른다고 말해도 괜찮고 대답을 이상하게 해도 괜찮다.. 내가 준비한 내용들은 그래도 다 대답할 수 있게끔 하자면서 마인드 세팅을 해나갔다. 준비하지 못한 질문이 분명 들어올 수 있지만, 내 임기응변 능력이 그렇게 후달리지는 않을 거다라는 생각도 한 것 같다.

 

 

면접 복기..

도착하고나서 내가 배정받은 번호가 1번인걸 알게 됐다. 당연히 1번이 처음 발표하는 사람이겠군이란 생각에 긴장됐지만, 미리 1번인 걸 알아서 다행이라고 생각했다. 처음으로 발표 시작하게 될거라는 생각으로 심호흡을 좀 했다. 최대한 대기실에서 "내가 제일 잘 한다. 다 찍어누르겠다(?)"라는 걸 스스로에게 세뇌할려고 했다. 어떻게든 자신감을 채우고 싶었던 듯. 마음 한구석에서는 살짝 쫄리긴 했지만, 그래도 겉으로 보이는 건 안 쫀 척 당당해보으려고 어깨 계속 펴주면서 으르렁댔다. ㅋㅋㅋㅋㅋ..

 

역시나 처음으로 포폴 발표를 하게 됐다. 연습을 많이 해서 그런지 막상 발표할 때는 엄청 긴장되고 그러진 않았다. 그냥 적당한 긴장감 속에서 진행됐다. 연습 때 보여줬던 베스트 퍼포먼스만큼 발표한 건 아니지만 그래도 만족할 정도로 발표한 듯 해서 다행이었다. 이후엔 최대한 허리를 펴고 앉으면서 두 손을 무릎에 두고 있었다. 다른 분들 포폴 발표하는 거 볼 때는 고개를 완전 90도로 오른쪽으로 꺾어야 해서 힘들었음.

 

 

질문들은 다음과 같은 질문들을 받았다.

 

공통질문

1. 코테 보면서 어떤 점이 가장 어려웠는지, 그런 부분들을 해결하기 위해 뭘 해오고 있는지

🧐 아무래도 경험이 부족하다보니 문제를 보고 어떤 유형인지를 파악하는게 어려웠다고 했고, 다양한 문제를 접하면서 문제를 딱 봤을 때 어떤 유형인지 알 수 있게끔 하려고 한다고 답했다

 

2. 자소서에서 각자가 만들기로 했던 서비스 한 번 더 설명해주고 비즈니스 모델은 어떤 거 생각햇는지

🧐 그냥 구상했던 서비스 다시 말씀드리면서 ~~기능 만들려고 한다고 답했다.. 부끄럽지만 솔직히 비즈니스 모델이 뭔지 잘 몰라서 최대한 자연스럽게(?) 비즈니스 모델에 대한 설명은 하지 않았는데 티가 났을 것 같다ㅋㅋㅋㅋ..근데 끝나고 비즈니스 모델이 뭔지에 대해 검색해봤는데, 다른사람들도 다 비즈니스 모델에 대한 답을 하진 않은 것 같아 다행이었다

 

3. 소마에서는 팀플을 하기 위해서 팀을 구해야 하는데, 본인의 어떤 점을 채워줄 수 있는 팀원을 원하며 그 팀원을 어떻게 설득할 건지

🧐 백엔드를 하고 싶은데 백엔드 관련된 경험은 부족하다보니 백엔드를 잘하는 사람과 팀을 하고 싶다고 했고, “나는 백엔드 경험은 부족해도 프론트랑 ai쪽 해봤다. 팀플에 잘 녹아들 수 있다”라고 설득해보겠다고 대답했다.

 

4. 소마가 널 왜 뽑아햐 하는지 (소마에 넌 뭘 줄 수 있는지..)

🧐 처음엔 질문 뉘앙스가 너희가 뭘 기여해줄 수 있냐를 묻는 줄 알았는데 마지막에는 뽑아야 하는 이유를 말해달라길래....준비했던 내용(나는 소마를 통해 성장하고 싶은 욕구가 정말 강하기 때문에 날 뽑아야 한다)를 말했다. 왜냐면 왜 너를 뽑아야하냐는 질문은 내가 준비를 했던 내용이었기 떄문! 근데 답하고 보니 뭔가 분위기가 띠용?이 되는 것 같았다. 사실 냉정하게 생각해보면 어떤 부분을 기여할 수 있는지를 말해달란거였는데, 이 부분은 준비를 못한 부분이라 당황해서 그런지 “왜 뽑아야 하는지”라는 질문에만 꽂혀서 그에 대해 준비했던 것만 말한 거였다.. 그래서 면접관님이 어떤 부분을 기여해줄 수 있는지에 대해서 되물으셔서 열정으로 분위기를 밝게 해보겠다는? 그런 답을 했다. 스스로도 부족한 답임을 알고 있어서 살짝 침울했지만, 다른 분들도 어떤 부분을 기여하겠다기보다는 스스로 성장하고 싶다는 점을 어필하셔서 다행이었다.

 

개인질문

1. ai관련 진행했던 플젝 간단하게 다시 설명해달라.

🧐 CAM이란 기술을 활용해 모델이 얼굴을 보고 감정을 분류할 때 얼굴의 어느 부분을 보고 분류하는지를 시각적으로 파악해보는 플젝이었다고 답했다.

 

2. CAM과 전이학습을 어떻게 활용한 거냐. 왜 전이학습 사용했냐.

🧐 처음에는 CAM과 전이학습이 서로 연관이 있는 거냐는 뉘앙스로 물어보셔서 서로 연관이 없다고 대답했는데, 거기에 대한 자세한 설명을 해달라면서 전이학습을 왜 했는지 물어보신 걸로 기억한다. 확보한 이미지 양이 적어서 새로운 모델을 만들어 학습시키기엔 효율이 안 좋을 것 같아 전이학습을 사용했다고 했고, 그 과정에서 전이학습을 위해 들고 온 모델에 CAM을 적용했다고 말했다.

 

 

3. 그럼 전이학습할 때 모델은 어떤 거 사용했고 어떤 식으로 학습시켰냐

🧐 VGG16 모델을 사용했고, 이 모델이 기존에 학습했던 이미지의 종류와 우리 플젝에서 새로 학습시키려는 이미지의 종류가 다르고 확보한 이미지가 적어서 모델에 새로 붙인 classifier layer뿐만 아니라 모델이 원래 가지고 있던 레이어들도 함께 학습시켜주는 식으로 했다고 말했다.

 

 

4. 결과를 잘 도출했고, 그래서 감정을 분류하는데 있어 모델이 중요시하는 부분이 어느 부분인지 볼 수 있게 된 것 같다. 도출된 결과는 어떤 특징이 있었냐

🧐 happy는 입 쪽을 보고 분류한다는 점을 볼 수 있었고 angry로 분류되는 애들은 코쪽, 주름쪽을 보고 분류한다는 점을 알 수 있었다고 답함.

 

그리고 공통적으로 마지막으로 하고 싶은 말들을 시켰다. 거기에 대해선

🧐 “소마 과정이 쉽게 잡을 수 있는게 아닌 소중한 기회라고 생각하고 있고, 소마를 통해 얻을 수 있는게 정말 많다고 생각한다. 처음 접하는 ai에 관련된 플젝을 할 때도, Level DB라는 규모있는 오픈소스를 분석할 때도, 우테코에서 처음 접하는 자바를 사용하며 미션을 수행할 때도 여러운 것들이 많앗지만 포기하기 않고 끝까지 해내왔다. 소마도 결코 쉽지 않겠지만 포기하지 않고 끝까지 해내서 꼭 스스로가 성장할 수 있는 기회로 반드시 만들어보겠다” 라고 답했다

 

 

음..포폴 발표할 때는 말을 그닥 빨리 하지 않았던 것 같은데 결국 질문같은 걸 받을 때 말을 빨리 하고 중간에는 말을 좀 더듬기도 한 것 같아 아쉬웠다. 이 부분은 진짜 고쳐나가야 할 듯 하다. 말을 왜 자꾸 빨리 하게 되는지..ㅜㅠ

 

일단 면접과정에 대한 후회는 없다. AI관련된 개인질문들은 바로바로 대답을 이어나갔기 때문에 꽤나 잘 대답했다고 생각하고, 꼬리질문을 이어가주시던 면접관 분도 시간이 있으면 더 물어보고 싶지만 그래도 플젝을 잘 진행해본 것 같다라는 뉘앙스로 말해주셔서 만족했다.

 

그 외 공통질문들에 대한 대답도 그 때의 내가 답할 수 있는 최선을 다한 대답이었다고 생각해서 후회가 들진 않는다(아 이렇게 답할걸.. 이런 생각 안 듦). 내가 준비했던 내용들에 대해선 다 답한 것 같아 과정에 대한 후회는 정말 남지 않을 것 같다.

 

다만 아쉬운 건, 다른 사람들한테는 질문들이 좀 많았는데(특히 블록체인 기술로 플젝한 분) 나한테는 그만큼의 질문이 들어오지 않았다는 거다. 나 진짜 내 포폴에 관련된 내용들, 포폴에 작성된 기술 관련된 내용들은 다 방어할 수 있을 정도로 꼼꼼히 준비했는데.. 나한테도 그 분만큼의 꼬리질문을 해주면 다 답해줄 수 있는데..  나 준비 많이 했다는거 보여줄 수 있는데.. 그만큼의 질문이 들어오지 않은 것에 대한 아쉬움. 준비한 것들을 다 못 보여준 느낌..이었다.

 

그래도 후회는 없다. 들어온 질문들에 대해서는 내가 할 수 있는 최대치 내에서 답변했다고 생각했다.

 

 

다른 분에게 들어온 개인질문 중 기억나는 것들

- 블록체인의 Finality가 뭐고 왜 중요한가요?

- 본인이 작성한 플젝에서 하신 역할이 뭔가요?

- OAuth를 사용해봤다고 했는데 OAuth가 뭔가요?

- 블록체인과 NFT의 차이점에 대해 말해주세요

 

우리 분과는 단순 프론트 / 백 프로젝트를 하신 분들에겐 그 플젝에서 다뤄봤다고 기재한 기술들에 대한 질문(ex: OAuth를 써봤다고 하는데 그게 뭔가요 등)을 하셨던 반면, AI나 블록체인같은 기술이 가미된 프로젝트를 한 분들에게는 그 기술들에 대한 질문들을 꼬리 질문 형식으로 계속 이어나가셨다. 그래서 단순 프론트/백 플젝을 하신 분들에겐 솔직히 질문이 별로 안 갔다. 물론 나에게도 질문이 많이 온 건 아닌 것 같다.. 아무래도 면접관님들 입장에서도 매력적으로 느껴지는 플젝은 AI나 블록체인을 활용한 플젝이기 때문일 것 같다. 그래서 어떻게보면 내가 AI플젝을 포폴에 넣었고, 그거에 대한 대비를 최대한 꼼꼼히 하려고 했어서 다행이라는 생각도 들었다.


그리고 합격..!

3월 23일 5시 경에 메일이 왔다.

 

 

일단 기쁘다. 마에스트로 과정을 옛날부터 한 4학년쯤 되면 해봐야지~ 했는데 정말 하게 돼서 너무 좋다. 글을 쓰고 있는 지금도 실감이 안 날 정도로 너무 좋다.

그리고 내가 공부해왔던 것들이 아무 의미 없던 게 아니었음을, 나 그래도 잘 하고 있었음을 인정받은 것 같아서 더욱 더 좋은 것 같다. 눈에 보이는 성취를 이루고 싶다는 생각을 했는데, 소마에 이렇게 합격하게 돼서 너무 다행이고 좋다. 

 

퀄리티있는 플젝을 하며 폭발적으로 성장할 기회. 다른 사람들과의 네트워크를 만들 기회, 생활비 벌자고 20살때부터 알바하던 것들 다 때려치고 지원금받으면서 오로지 성장에만 몰입할 수 있는 기회. 소마를 통해 내가 바라던 것들에 대한 기회를 잡게 됐다. 하지만 아직 잡기만 했을 뿐. 성장하지도, 다른 사람과의 네트워크를 만들지도, 몰입하지도 않았다. 단순히 기회만 얻게 됐을 뿐. 앞으로의 1년이 정말 중요할 거고, 소마에서 어떻게 지내가는지에 따라 내가 원하던 것들을 잡게 될지 안 될지가 나뉠 것이다. 이제부터가 중요하다.

 

잘..해보자!

 

+ Recent posts