서론

우리가 익숙한 프로그래밍 방식은 명령형 프로그래밍일 것이다. 

명령형 프로그래밍 방식은 컴퓨터에세 정해진, 명확한 지시를 하나하나 내린다는 뜻이다.
예를 들어 이 변수의 값을 10으로 바꿔줘, 이 라인을 n번 반복해줘 등이 있겠다.

처음에는 단순해 보이므로 명령형을 사용하지만 프로그램이 커질수록 복잡성이 증가한다. 
유지보수하기 어려워지고, 테스트하기 어려워지고, 코드가 어떤 목적의 코드인지 추론하기 어려워진다.

함수형 프로그래밍은 명령형의 대안으로, 위에서 말한 문제들을 해결할 수 있다. 

함수형 프로그래밍의 전제는 순수함수를 통해 프로그램을 구성한다는 것이다.

순수 함수란 아무런 부수 효과(Side Effect)가 없는 함수를 말한다. 

결과를 반환하는 행위 이외에 다른 일을 하는 함수가 부수 효과가 있는 함수이다. 

부수 효과를 일으키는 함수의 예시는 다음과 같다.

  • 블록 외부 영역에 있는 변수를 변경한다. 
  • 데이터 구조를 In-place로 변경한다.(메모리의 내용을 직접 변경)
  • 객체의 Field를 설정한다. (Setter)
  • 예외를 던지거나, 예외를 발생시키며 프로그램을 중단시킨다. (Exception)
  • 콘솔에 출력을 하거나 사용자 입력을 얻는다. (I/O)
  • 파일을 읽거나 쓴다 (File I/O)
  • 화면에 무언가를 그린다.

이런 부수효과를 일으키는 함수를 사용하지 않고 어떻게 유용한 프로그램을 작성할 수 있을까.
파일을 읽고 쓰거나, 화면에 무언가를 그리거나, 변수를 값을 변경할 수 없다는 뜻일까?

함수형 프로그래밍은 프로그램을 어떻게(How) 작성하냐에 대한 것이지, 무엇(What)을 작성하는지에 대한 것이 아니다. 
순수함수를 사용해서도 위의 작업들을 할 수 있다. 

함수형 패러다임을 따르면서 개발하면 더 나은 모듈성을 얻을 수 있고, 이로인해 테스트, 재사용, 병렬화, 결과 추론이 쉬워진다.

이번 장에서는 부수효과가 있는 명령형 코드들을 함수형 스타일로 리팩토링해보고, 함수형 프로그래밍의 필수 개념인 참조 투명성과 치환 모델에 대해 알아본다. 

1.1 FP의 장점

https://insideretail.asia/2017/05/22/singapore-coffee-shops-offer-cashless-payments-digital-ordering/

카페에서 신용카드로 커피를 구매하고, 거래 비용을 처리하는 프로그램을 예시로 들겠다.

명령형 프로그램에서 부수효과를 줄인 함수형 프로그램으로 개선하는 과정을 담는다. 

1.1.1 부수효과가 있는 프로그램

class Cafe {
    fun buyCoffee(cc: CreditCard): Coffee {
        val cup = Coffee()
        cc.charge(cup.price)
        return cup
    }
}

class CreditCard {
    fun charge(price: Int) {
        TODO("신용카드사에 결제 요청")
    }

}

data class Coffee(val price: Int = 4500)

buyCoffee 메소드는 커피를 만들고 신용카드사에 결제를 요청하고, 커피를 반환한다. 

카드를 통해 결제하면 카드사의 외부시스템으로 요청을 보내야한다. 여기서 부수효과가 발생한다. 
이 코드로 테스트를 한다면 매번 실제 외부 시스템의 요청을 해야하므로, 테스트 하기가 어려워진다. 

설계를 바꿔 테스트 성을 높일 수 있다.
CreditCard가 신용카드사에 접속해 비용을 청구하는 방법을 모르게 하면 된다.

class Cafe {
    fun buyCoffee(cc: CreditCard, p: Payments): Coffee {
        val cup = Coffee()
        p.charge(cc, cup.price)
        return cup
    }
}

class Payments {
    fun charge(creditCard: CreditCard, price: Int) {
        TODO("신용카드사에 결제 요청")
    }
}
data class CreditCard(val serial: String, val company: String)
data class Coffee(val price: Int = 4500)

Payments라는 클래스를 새로 추가해서, 비용을 청구하는 방법은 Payments 클래스가 알고있도록 변경했다. 
Payments를 인터페이스로 만들고 Mock Payments를 만들어서 테스트를 할 수 있다.

하지만 여전히 p.charge를 통해 외부 세계와 상호작용하고 있기 때문에 부수효과는 존재한다

그리고 Mock을 작성하는 것도 이상적이지는 않다. 구체화된 클래스 하나로 충분할 수도 있는데 불필요하게 인터페이스를 만들어야한다.

Mock 구현도 불편한 부분이 있다. 예를 들어 charge를 호출 한 후 이 상태가 제대로 변경되었는지를 검사해야하는 로직이 필요하기도 하고, buyCoffee를 호출하고 이를 살펴볼 수 있는 내부 상태가 필요할 수도 있다. 

이런 세부 사항들을 프레임워크를 통해 처리하게 할 수 있지만 비용 청구 테스트만을 위해 프레임워크까지 사용하는 것은 오버일 수 있다.

그리고 재사용하기도 어렵다.

여러잔의 커피를 구매하면, 개수만큼 신용카드사에 요청이 발생한다.
각 요청마다 수수료가 있다면 모든 주문을 모아 한 번만 요청하는 것이 이상적이다. 

1.1.2 부수효과 제거하기

  • 부수효과가 있는 상황

  • 부수효과가 없는 상황

buyCoffee 안에서 청구를 하는 게 아니라 청구 정보를 커피와 같이 리턴하여 청구 정보는 다른 곳에서 처리하도록 바꾼 것이다. 

class Cafe {
    fun buyCoffee(cc: CreditCard): Pair<Coffee, Charge> {
        val cup = Coffee()
        return cup to Charge(cc, cup.price)
    }
}

data class Charge(val cc: CreditCard, val amount: Int) {
    fun combine(other: Charge): Charge = if (cc == other.cc) Charge(
        cc,
        amount + other.amount
    ) else throw Exception("Cannot combine charges to different cards")
}

data class CreditCard(val serial: String, val company: String)
data class Coffee(val price: Int = 4500)

이렇게 금액 청구를 만드는 관심사와 청구를 처리하는 관심사를 분리했다. 

같은 CreditCard의 청구 정보를 하나로 만들 때 편리하게 쓸 수 있는 combine 함수도 정의하였다.

val charge1 = cafe.buyCoffee(cc)
val charge2 = cafe.buyCoffee(cc)

charge1.combine(charge2)

이런식으로 사용할 수 있다. 

이제 커피 n잔을 주문할 수 있는 buyCoffees를 만들어보자.

class Cafe {
    fun buyCoffee(cc: CreditCard): Pair<Coffee, Charge> {
        val cup = Coffee()
        return cup to Charge(cc, cup.price)
    }

    fun buyCoffees(cc: CreditCard, n: Int): Pair<List<Coffee>, Charge> {
        val purchases = List(n) { buyCoffee(cc) }
        val (coffees, charges) = purchases.unzip()
        return coffees to charges.reduce { c1, c2 -> c1.combine(c2) }
    }
}

이렇게 작성할 수 있다. 

List(n) { buyCoffee(cc) }를 하면 List<Pair<Coffee, Charge>>가 만들어진다. 

unzip()를 하면 Pair<List<Coffee>, List<Charge>> 가 반환된다. 

그리고 구조분해를 통해 coffees => List<Coffee>, charges => List<Charge>가 각각 할당된다. 

그 다음 reduce를 통해 charges의 값들을 combine한다.

 

이제 buyCoffees를 정의할 때 buyCoffee를 재사용할 수 있었고, 관심사를 분리하였기 때문에 굳이 Payments 인터페이스와 mock 을 정의하지 않아도 쉽게 테스트할 수 있다. 

실제 금액 청구는 Payments 같은 클래스가 필요하겠지만 Cafe 클래스는 Payments를 몰라도 된다. 

우리는 Charge를 일급 객체(First-class value)로 만들었다. 일급 객체로 만들면 청구 금액을 처리하는 비지니스 로직을 더 쉽게 조립할 수 있다는 장점이 생긴다. 

➤ 일급 객체란 다음 3가지 조건을 만족하는 객체이다.

  1. 다른 변수나 데이터에 담을 수 있어야한다. 
  2. 함수의 파라미터로 전달될 수 있어야한다. 
  3. 함수의 반환값으로 사용될 수 있어야한다. 

일급 객체이기 때문에 일련의 Charge List가 있을 때 같은 카드에 청구하는 금액을 모두 합치는 로직을 쉽게 작성할 수 있다. 

fun List<Charge>.coalesce() = 
    this.groupBy { it.cc }.values
        .map { it.reduce {c1, c2 -> c1.combine(c2)} }

이렇게 하여 사용한 신용카드에 따라 그룹으로 나누고 각 그룹의 청구금액을 하나로 합친 Charge List를 만들어낸다.

fun main() {
    val cc1 = CreditCard("1234", "K")
    val cc2 = CreditCard("2345", "H")
    val cc3 = CreditCard("3456", "W")
    val chargeList = listOf(
        Charge(cc1, 3000),
        Charge(cc2, 4000),
        Charge(cc3, 5000),
        Charge(cc2, 3800),
        Charge(cc2, 4500),
        Charge(cc1, 4000),
        Charge(cc1, 5500)
    )
    val result: List<Charge> = chargeList.coalesce()
}

이런식으로 사용할 수 있고 그림으로 보면 다음과 같다.

1.2 순수 함수란?

정확하게 순수함수를 정의해보자. 형식적(제한된 단어를 사용해서 엄격하고 정확하게 기술)인 정의이다.

앞에서 함수형 프로그래밍은 순수함수를 이용해 프로그래밍 하는 것이라 했다. 그리고 순수함수란 부수효과가 없는 함수라고도 했다.

입력이 A이고 출력이 B인 함수 f가 있다고 하자. 코틀린에서는 (A) -> B 이렇게 쓴다.

A 타입이 될 수 있는 모든 값 a 를 B 타입이 될 수 있는 모든 값 b에 매핑해주는 계산이다. b값은 a값에 의해서만 결정이된다. 
f(a) = b의 식에서 내부, 외부의 상태 변경은 결과에 영향을 끼치지 못한다. 

*의문점
내부의 상태 변경은 영향을 끼칠 수 있는 것 아닌가? a값과 연관이 없는 내부의 상태변화를 이야기 하는 것인가?

이러한 정의에 따른 순수함수의 예시는 +(plus)연산과 String.length() 연산이 있다. 
plus의 경우 두 정수가 주어지면 항상 같은 결과값을 반환한다. 
Java, Kotlin의 String은 불변 객체이기 때문에 주어진 문자열에 대해 "string".length()를 호출하면 항상 같은 결과가 반환된다. 
"string"라는 리터럴은 변할 수 없기 때문이다. 

불변 객체란? 객체의 내부 프로퍼티를 변경할 수 없는 객체이다. 
예를 들어

var str = "Hello"
str = "World"

이렇게 변수를 변경한다고 해도, Hello라는 String 인스턴스의 값이 World로 변경된 것이 아니라.

Hello라는 인스턴스는 그대로 있는 상태에서 World라는 인스턴스가 새로 생성되고, str의 참조값이 바뀌는 것이다. 

순수함수의 개념을 참조 투명성(RT; Referential Transparency)의 개념을 이용해 형식화 할 수 있다. 

참조 투명성이란?

예를 들어 2+3 이라는 식이 있을 때, 프로그램 안에서 2+3이라는 식을 모두 5로 치환할 수 있고, 그렇게 해도 프로그램의 의미가 전혀 변하지 않는다. 

어떤 식이 "참조 투명하다"라고 말하는 것은 프로그램의 의미를 변경하지 않으면서 식을 그 결과값으로 치환할 수 있다는 것이다. 

참조 투명한 인자를 사용해 호출한 함수의 결과가 참조 투명하다면 이 함수도 참조 투명하다.

형식화된 정의

어떤 식 e에 대해, 모든 프로그램 p에서 e를 e의 결과값으로 치환해도 p의 의미에 변화가 없다면 e는 참조 투명하다.
어떤 함수 f가 있을 때, 참조 투명한 x에 대해 f(x)가 참조 투명하다면 함수 f도 참조 투명하다.

∴ 참조 투명한 함수를 순수함수라고 한다.

 

1.3 참조 투명성, 순수성, 치환 모델

어떤 함수가 참조 투명하다면, 함수를 함수의 결과값(반환값)과 치환할 수 있다고 했다. 

우리가 가장 처음에 봤던 부수효과가 있는 buyCoffee 함수를보자.

fun buyCoffee(cc: CreditCard): Coffee {
    val cup = Coffee()
    c.charge(cup.price)
    return cup
}

이 함수는 cc.charge()의 결과와는 상관없이 cup을 반환한다.

참조 투명성에 정의에 의해 buyCoffee가 순수함수가 되려면 모든 프로그램 p에 대해서 buyCoffee를 Coffee()로 치환할 수 있어야한다.
하지만 p(buyCoffee())는 p(Coffee())와 같지 않다. charge를 하지 않기 때문이다.

이렇게 단순히 치환된다고 참조 투명성이 있는 것이 아니다.

함수가 수행하는 모든 일이 함수의 반환 값에 의해 표현되어야 한다.

이런 제약이 있을 때 참조 투명성이 있다고 할 수 있고, 치환 모델을 통해 프로그램 추론이 쉬워진다. 
* 치환 모델을 사용한다는 건 함수를 함수의 결과값으로 치환해서 계산한다는 것이다. 

참조 투명성이 확보되면 우리가 대수 방정식을 푸는 것처럼 코드를 읽을 수 있다. 

* 대수방정식은 미지수(x, y..)가 포함된 식을 말한다.
4x - 6 = 10 ⇢ ⓵
2y + 3x = 16 ⇢ ⓶
1번 식을 통해 x=4라는 답이 나왔다. 그럼 2번 계산에서 x를 4로 치환해서 계산해도 전혀 문제가 없다. 

코드를 통해 치환 모델의 예시를 살펴보자. 

1. 참조 투명한 식에 치환 모델 적용

위에서 말한대로 코틀린의 String은 불변객체이다. 문자열에 +를 하거나, reversed()를 하거나 문자열은 변경되지 않는다. 새로운 문자열 인스턴스가 할당될 뿐.

val x = "Hello, World"

val r1 = x.reversed() // dlroW ,olleH
val r2 = x.reversed() // dlroW ,olleH

치환 모델을 사용해보자. 즉, x를 "Hello, World"로 치환해보자는 뜻이다.

val r1 = "Hello, World".reversed() // dlroW ,olleH
val r2 = "Hello, World".reversed() // dlroW ,olleH

프로그램에 아무 영향이 없다. x가 참조 투명하기 때문이다.
그리고 x가 참조 투명하기 때문에 r1, r2 도 참조 투명하다.

2. 참조 투명하지 않은 시에 치환 모델 적용

val x = StringBuilder("Hello")
val y = x.append(", World")

val r1 = y.toString() // Hello, World
val r2 = y.toString() // Hello, World

StringBuilder는 String과 달리 객체 내부의 상태를 변경한다. 

현재 코드에서는 r1, r2가 같다 하지만, y를 치환한다면 어떻게 될까?

val x = StringBuilder("Hello")

val r1 = x.append(", World").toString() // Hello, World
val r2 = x.append(", World").toString() // Hello, World, World

r2의 값이 할당되는 시점에 x의 값이 변경되었기 때문에 다른 결과가 나왔다. 
따라서 append는 순수함수가 아니라고 할 수 있다. 그리고 이런 코드는 추론하기가 힘들다.

치환 모델을 사용하면 식이나 코드를 추론하기 더 쉬워진다. "x가 여기선 어떻게 변경되고 다음 상태에선 어떻게 되겠지" 이런 시뮬레이션을 안해도 된다.

 

 

 

 

지난 4월 ~ 7월에 공채를 진행했고, 이제서야 후기를 올립니다. 

결론부터 말하면 2차 인터뷰인 종합 역량 인터뷰에서 탈락했습니다. 

코딩 테스트나 면접 내용의 구체적인 내용의 관해서는 서약서를 작성했으므로 답변드리지 못합니다. 

일정

지원서 작성 코딩테스트 코딩테스트
결과 발표
기술 역량 인터뷰 기술 역량 인터뷰 결과 발표 종합 역량 인터뷰 종합 역량 인터뷰
결과 발표
3/29 - 4/10 4/15 5/9 5/15 - 5/25 6/2 6/12 - 6/21 7/6

※ 종합 역량 인터뷰는 6/23일 정도까지 지연된 것으로 알고 있습니다. 

서류 전형

문항은 대략 이렇습니다. 그대로 가져와도 되는지 모르겠어서 대략적으로 적겠습니다. 

[필수] 1. 지원 계기, 회사에서 어떻게 성장하고 싶은지
[필수] 2. 가장 열심히 한 프로젝트, 문제 해결과정 구체적으로
[필수] 3. 희망 분야, 이유
[필수] 4. Github, Blog 등

제 지원서는 아래와 같이 작성했습니다.

더보기
  • 1번 문항

지속 가능한 열정을 만들 수 있기 때문입니다.
평소 호기심이 많은 편이라 디자인, 공예, 패션, 심리학 등 여러 분야에 관심이 많습니다.
그중에서도 개발은 5년 이상 지속할 수 있던 취미이자 일이었습니다.

개발은 즐거움도 있었지만, 정신적인 피로와 많은 공부량 등 힘든 점도 많습니다.
그럼에도 지속할 수 있었던 이유는 성취감입니다.

"CPU는 0과 1만을 처리하는데 어떻게 화면을 표시하는 걸까", "내가 보낸 메시지는 어떻게 미국까지 가는 걸까" 같은
막연한, 마법 같던 지식의 안개가 걷히는 순간에 성취감과 이렇게 얻은 지식을 다른 사람들에게 설명하고 공유함으로써 나오는 성취감이 열정을 다시 만들어주었습니다.
또한 오류가 발생했을 때, 발생 원인을 파악하고 레거시 코드는 왜 이렇게 작성했을까 생각하며 기술적인 해결에 도달했을 때의 성취감 역시 이 일을 지속 가능하게 만들어 주었습니다.

그리고 이렇게 성취한 지식이 저의 궁극적인 성장에 도움이 된다는 점이 좋았습니다.
기술적인 지식뿐만 아니라 팀원들과 대화하는 능력, 설명하는 능력 그리고 코드에 담긴 의미와 속 뜻을 알아내는 과정이 소프트 스킬 향상에도 도움이 되었습니다.

팀 네이버에서는 레거시 코드를 분석하고 개선하는 경험을 통해 그리고 동료들과의 협업을 통해 성장하고 싶습니다.
네이버는 24년간 많은 사용자를 대상으로 서비스해 온 기업입니다.
그 간의 경험이 들어있는 코드를 통해 과거의 기술과 현재 기술 차이점을 배우고, 더 좋은 방향으로 개선하면서 성장하고 싶습니다.
그리고 적극적인 개발자들이 많은 환경이 큰 성장 동력입니다.
동료들과의 활발한 협업 및 스터디, DEVIEW 같은 콘퍼런스에서 발표하는 경험을 통해 성장하고 싶습니다.

  • 2번 문항

네이버 커넥트재단 부스트캠프에서 했던 RightWeight라는 프로젝트가 있습니다.
운동 루틴을 생성하고, 기록하고, 공유할 수 있는 안드로이드 어플입니다.

데이터 구조를 보면 특정 날에 여러 가지 운동, 한 운동에 여러 세트 수가 중첩되어 있는 구조입니다.
Json으로 보면 다음과 같습니다.
"routine" : {
    "day1" : {...},
    "day2" : {...},
    "day3" : {
        "exercise1" : {...},
        "exercise2" : {
             "set1": {}
        }
    }
}

이런 복잡한 데이터 구조 때문에 몇 가지 문제가 발생하였습니다.

1. 데이터 저장소 선택
파이어 베이스의 Realtime DB와 Firestore 중에 무엇을 선택할지 문제였습니다.
Realtime DB를 사용하면 간단히 데이터 저장이 가능하지만, 데이터가 커질 수 있고, depth가 깊어진다는 문제가 있었습니다.
또한 Recycler View에서 제목 정도만 보여주려 해도 모든 정보를 가져와야 했습니다.
이에 반해 Firestore는 NoSql로 document 컬렉션으로 저장되기 때문에 필요한 정보만 컬렉션 단위로 가져올 수 있고,
Paging 처리를 위한 인덱싱도 지원하기 때문에 Firestore를 선택했습니다.

2. 트랜잭션 처리
팀에서 Firebase를 SDK가 아니라 REST API 방식으로 사용했습니다.
운동 루틴 하나를 삭제하려면, 루틴 안에 있는 모든 day, exercise, set에 대한 삭제 쿼리를 요청해야 했습니다.
루틴이 커질수록 쿼리가 많아지고, 중간에 네트워크가 끊긴다면 데이터의 일부만 남아있는 상태가 될 수도 있습니다.
REST API 방식의 Transaction 처리에 관한 문서의 부재로 해결하지 못했습니다.
최종적으로는 runQuery라는 쿼리를 모아서 요청하는 방식을 사용했지만, 이 역시 네트워크가 끊길 시 같은 문제가 발생합니다.
SDK를 사용하거나, 별도의 서버를 두었다면 이러한 문제에 더 쉽게 대응할 수 있지 않았을까 아쉬움이 남았습니다.

  • 3번 문항

Android

사용자에게 가장 가까운 분야이고 클라이언트 분야 중에서도 휴대폰의 성능과, 디자인, 터치 인터페이스의 사용성까지 고려해야 한다는 점에서 흥미를 느꼈습니다.
또한 자바, 코틀린 언어를 사용하여 객체지향적인 코드를 작성할 수 있다는 점도 매력적이었습니다.

Java에 대한 이해도가 높습니다.
Java에 대한 스터디를 진행하며 객체 지향의 장정, 특징은 무엇인지, 컴퓨터에서는 어떻게 자원이 할당되고 실행되는지 등 지식이 언어에 한정되지 않도록 했습니다.
언어와 함께 JVM도 공부하며, 자바 코드가 어떻게 바이트코드로 변환되어 어느 메모리에 할당되고, 어떻게 실행되는지와 Garbage Collection도 함께 공부하였습니다.
안드로이드 OS도 JVM 기반의 ART를 사용하기 때문에 Java에 대한 지식이 안드로이드를 더 쉽게 이해할 수 있게 도와주었습니다.
이런 추상적인 사고방식 덕분에 자바에서 코틀린으로 빠르게 전환할 수 있었습니다.

'지역 XR 챌린지'라는 공모전에서 AR 내비게이션 앱으로 수상한 경험이 있습니다.
해당 프로젝트에서는 Lane Detection 을 하기 위해 OpenCV를 사용해야 했습니다. 
카메라 정보를 서버로 보내면 레이턴시가 생기기 때문에 Android NDK로 C++을 사용하여 기기 안에서 처리하도록 구현했습니다.
완성한 프로젝트는 플레이스토어를 통해 배포하였습니다.

네이버 커넥트재단 부스트캠프 웹・모바일 7기 과정, 안드로이드 분야를 수료하였습니다.
부스트캠프의 팀 프로젝트에서 1주일 단위로 스프린트 계획을 세우고, 매일 스크럼을 통해 진행 상황을 공유하고, 페어 프로그래밍을 하며 문제를 해결해보는 경험을 했습니다.
프로젝트는 MVVM 아키텍처로 앱을 구성하였고, 서버에서 가져오는 정보는 Paging과, Room을 사용해 로컬 데이터베이스에 캐싱하여 네트워크 사용량을 줄이고 오프라인 사용성을 높였습니다.
그리고 Retrofit을 사용한 네트워크 처리, Coroutine을 사용한 비동기 처리 등의 경험을 했습니다.

  • 4번 문항

RightWeight / 안드로이드 어플 개발 / https://github.com/boostcampwm-2022/android09-RightWeight
운동 루틴을 기록, 관리, 공유할 수 있는 서비스
역할: 
MVVM, Repository 패턴 적용
Toolbar, Navigation 관련 설정
타이머 Service 알림 표시 및 딥링크 설정
Room에 저장된 데이터 RecyclerView로 표시
Retrofit으로 Firebase와 통신

MusicHub / 백엔드 개발 / https://github.com/pknu-wap/Musichub
프로젝트 설명 : https://yoon6.github.io/work/0.html
여러명의 사람들이 함께 플레이 리스트를 만들어가는 서비스
역할:
Spring Security를 통한 로그인/회원가입 기능 구현
MVC패턴 사용, CRUD 처리  

자바의 정석 스터디 / https://gdsc-pknu.notion.site/2791abcc40804df3b9dacb15071b0d81
책 내용에서 더 깊게 공부한 내용 발표, 이해한 내용으로 비유 하기
역할: 스터디 운영, 참여

JVM 동작 원리 공부
블로그 : https://yoonda.tistory.com/12
유튜브 : https://youtu.be/w9-NfxfC9JY

코딩테스트

4/15일(토)

코테를 복기하지 않아서 기억이 흐릿합니다. 

총 4문제가 나왔고, 2문제를 풀었습니다. (정답여부는 확실하지 않습니다.)

1번은 구현이었고, 2번은 DP로 풀었던 것으로 기억합니다. 

3번도 구현이었던거 같은데 3,4번은 잘 기억나지 않습니다. 죄송합니다. 😥

주변과 오픈 톡방을 봤을 때, 1솔까지 합격한 사례가 있었습니다. 

지원서를 꽤 보는 것 같습니다. 

기술 역량 인터뷰 

5/24일(수)

기술 인터뷰는 Zoom을 통해 진행되었습니다. 

저는 마지막 타임이기도 해서 70분 - 80분 정도 봤습니다. 

메일에는 70분 본다고 적혀있습니다. 

2:1로 진행되고, 자유로운 편안한 분위기 였습니다. 

지원서 기반 질문과 기타 CS 질문 등이 나오고, 답변마다 추가적으로 면접관분들의 질문이 들어왔습니다. 

문제에 대한 답을 도출하는 게 아니라 저의 의견이 무엇인지, 어떤 사고과정을 통해서 그런 생각을 했는지를 깊게 물어보셨습니다. 

모르는 부분에 대해서는 제가 물어보기도 하고, 직장 동료/ 상사분과 커피챗하는 느낌이었습니다. 

종합 역량 인터뷰

6/14일(수)

종합 인터뷰 역시 Zoom으로 진행되었고 30분 정도 소요 되었습니다. 

2:1로 진행되었습니다. 

지원서 기반 질문과 약간의 기술적인 질문, 그리고 인성 면접 질문들을 받았습니다. 

그리고 면접 당시에 4개월 정도의 경력을 가지고 있었어서 그 부분에 대해서도 질문을 받았습니다. 

결과

 

결과적으로 합격을 하지는 못했습니다. 

멘탈도 적당히 나가고 조금 힘들긴 했지만, 그런다고 달라지는 건 없기 때문에 제 부족한 점을 찾고, 메타인지 능력을 키우고, 맡은 일을 열심히 하며 정진하고 있습니다. 

앞으로는 경력때문에 신입공채에는 지원하지 못하겠지만, 기술적 역량을 키우고, 개발을 더 좋아하고, 나 자신을 더 잘 알게될 때 면접을 한 번 더 보고싶긴 하네요. 

종합 역량 인터뷰에서 스스로 아쉬웠던 점은 이런 것들이 있습니다. 

  1. 기술 인터뷰 이후에 약간의 자만심과 게으름으로 면접 준비를 소홀히 했다.
  2. 답변을 얕게 했다. 표면적인 답변만 하고 근거가 되는 이유를 말하지 않았다.
  3. 동문서답 했다.
    • e.g. A를 하고싶다 하셨는데 어떻게 하실건가요?
      • 왜 제가 A를 하고싶었냐면~

 

+ 진행하면서 힘들었던 점

각 전형과 결과발표의 간격이 너무 길었고, 딜레이 되기도 하고, 정확한 결과 발표일을 알려주지 않아 매일 메일에 들어가 확인했다 ㅋㅋ

처음 예정되었던 OPEN CLASS는 기술 역량 인터뷰 합격자들이 대상이었는데, 최종 합격자들 대상으로 바뀐점도 아쉬웠다. 

 

뭐 어찌 되었든 모든 인터뷰를 경험해봤음에 감사하고 있습니다. 읽어주셔서 감사합니다. 

혹시 문제되는 내용이 있으면 수정하도록 하겠습니다. 

 


모집 사이트 참고

https://recruit.navercorp.com/micro/teamnaver/tech

 

2023 팀네이버 신입 공채

당신의 가능성이 팀네이버의 가능성으로!

recruit.navercorp.com

 

 

Google I/O Extended 2023 Cloud Busan & Busan 에서 발표한 자료입니다. 
https://festa.io/events/3820

본 발표는 안드로이드에서 웹 컨텐츠를 사용하는 방식을 다룹니다.

목차

  1. 발표 주제 소개
    • 주제 선정 이유, 발표에서 다루는 내용을 말합니다.
  2. 안드로이드와 웹
    • 안드로이드에서 웹 컨텐츠를 어디에 사용하는지, 어떻게 사용하는지 알아봅니다.
  3. WebView
    • 안드로이드의 웹뷰에 대해 알아봅니다.
  4. New in Web on Android
    • 2023 Google I/O에서 발표된 New in Web on Android 세션을 리뷰합니다.

 

우선 저는 안드로이드를 개발하고 있지만, 통계적으로 봤을 , 모바일 보다는 쪽에 관심있는 분들이 많을 같았고, 
그래서 개발이나 개발하시는 분들 모두 들을 있는 주제로 선정하고자 했습니다.

해당 발표에서는 안드로이드에서 컨텐츠를 어떻게 사용하고어떤건 되고, 어떤건 안된다. 
뭐가 좋고 뭐가 안좋다 이런 것들을 알아보려고 합니다.

그래서 앞으로 모바일과 협업할 있을 , "? 그거 된다고 하던데요." 라고 있는 정도로 알아가셨으면 좋겠습니다.

그리고 이번 Google I/O에서 발표된 New in Web on Android 같이 리뷰 해보는 시간을 갖겠습니다. 

모바일 앱에서 웹컨텐츠를 사용하는 경우는 어떤게 있을까요?
대표적으로는 카카오톡에서 링크를 클릭했을 때가 있겠습니다. 
앱안에서 링크가 열리는 것을 있습니다.

다른 예시를 보겠습니다. 토스의 증권탭, 카카오톡 쇼핑탭, 당근의 근처 탭도 모두 웹컨텐츠를 보여주고 있습니다. 
이런 탭들은 웹인지도 모를정도로 웹컨텐츠를 활용하고 있습니다.

이렇게 네이티브 구현할 있는 화면을 굳이 웹으로 만들까요?
첫번째는 비용입니다.

비용이라 하면 
첫번째로는 개발 시간입니다. 
android, ios, web 따로 개발하지 않아도 되죠.

두번째는 인력입니다. 
개발자 명만 있어도, , android, ios 커버칠 있습니다. 
그리고 개발자가 개발자에 비해 개발자 풀이 넓어 인력을 수급하기도 쉽습니다.

세번째는 배포속도입니다. 
앱에서 배포를 한다고 하면, 앱스토어에서 업데이트하고, 사용자가 앱을 업데이트 해야 최신기능이 반영됩니다. 
앱스토어 심사 등으로 배포자체가 늦어지기도 하고, 사용자가 앱을 업데이트 안하는 경우도 존재합니다. 
웹으로 배포하면 앱을 업데이트 일이 없으니까, 시간이 단축되고, 사용자의 피로를 낮춰줄 있습니다.

두 번째 이유로, 이벤트 페이지 처럼 자주 변하는 화면은 웹로 하는게 좋습니다.

이제 앞에서 컨텐츠를 어디서, 사용하는지 알아봤습니다. 
이제 안드로이드에서 컨텐츠를 어떤 식으로 보여줄 있는지 보겠습니다.

첫번째는 그냥 외부 브라우저로 이동시키는 방식이 있습니다. 제일 간편한 방법입니다. 

번째는 크롬 커스텀 탭이 있습니다. 앱안에서 열리는 크롬 탭이라고 생각하시면 됩니다. 

그리고 번째로 웹뷰라는 컴포넌트를 사용하는 방식이 있습니다.

3가지를 비교해본 표입니다.

속도

속도는 커스텀 탭이 가장 빠릅니다. 백그라운드에서 Pre-warming을 해서 퍼포먼스를 향상 시킬 수도 있습니다. 

브라우저는 브라우저 앱을 실행하고 로드하는 과정이 필요하기 때문에 비교적 Custom Tabs 보다 느립니다. 

웹 뷰는 뷰 객체를 생성하고 초기화하는데 시간이 오래걸려 비교적 느립니다. 

브라우징 기능

일단 브라우저는 브라우징을 위해 만들어진 것이기 때문에 모든 표준을 준수합니다. 

커스텀 탭도 크롬을 사용하기 때문에 동일합니다. 그리고 크롬브라우저와 캐시와 쿠키도 공유합니다.

웹뷰의 경우 표준을 지원안하는 경우도 있고, 앱마다 쿠키를 따로 관리하기 때문에 브라우징 자체의 기능은 떨어진다고   있습니다.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    

앱과의 연결성

브라우저의 경우는 별도의 앱이 실행되는 것이므로, 페이지를 닫거나 하는 등의 컨트롤을 하기가 어렵고브라우저 사용중의 우리의 앱이 꺼질 위험도 있습니다. 

커스텀 탭의 경우 안에서 열리기 때문에 컨텍스트 스위칭이 일어나지 않고 자연스럽게 사용할 있습니다. 

뷰는 안에서 일부분만 보여준다거나 자유롭게 사용할 있습니다.

커스텀 가능여부

브라우저는 커스텀 가능한 부분이 없고, 앱으로 돌아올 데이터를 받거나 하기 어렵습니다. 

커스텀 탭은 닫기버튼이나 색깔 정도 커스텀할 있고 몇가지 상황에 대한 콜백을 처리할 수도 있습니다. 

웹뷰는 네이티브 컴포넌드들과 같이 같이 쓴다거나, 웹에서 자바스크립트를 실행한다거나, 로딩 단계에 콜백을 추가한다거나 자유롭게 사용할 있다.

보안

보안의 경우 브라우저와 커스텀 탭은 크롬등의 브라우저를 사용하기 때문에 브라우저의 보안을 따라갑니다. 세이프 브라우징을 사용하고 별도의 프로세스에서 동작하기 때문에 앱에 리소스에 접근하기 어렵고 비교적 안전하다고 있습니다. 

웹뷰의 경우는 웹에서 네이티브 코드를 실행하거나 하는 위험성이 있습니다.
그래서 검증되지 않은 사이트보다는 본인의 사이트를 보여주는데 적합합니다.

우선 안드로이드의 뷰는 최신버전 기준으로 크로미움 엔진을 사용합니다.

그리고 웹뷰를 사용할 때는 웹뷰와 웹뷰 클라이언트, 웹뷰 크롬 클라이언트 3가지 클래스를 대표적으로 사용합니다.

웹뷰는 뷰를 표시하는 객체이고, 클라이언트는 웹페이지 로딩 일어나는 콜백을 정의할 있는 클래스입니다. 

크롬 클라이언트는 웹페이지 안에서 일어나는 액션에 대한 콜백을 정의할 있습니다.

클래스들은 웹이 로드되는 순서에 따라 콜백이 호출됩니다. 

url 로드한다고 하면, 페이지가 로드 시작, 리소스 다운로드, 타이틀 받아오기, 페이지 로드 완료 로딩 순서대로 호출됩니다. 

이제 코드로 보겠습니다. 

우선 이번 발표에서는 컴포즈라는 새로운 구성방식이 아닌 기존의 xml 사용해서 설명하겠습니다. 

이렇게 xml 뷰를 선언해주고, 인스턴스를 생성해줍니다.

웹 뷰 클라이언트를 설정해주고 loadUrl 페이지를 로드해주면, 오른쪽 화면처럼 페이지가 로드됩니다. 

하지만 이상태에서는 자바스크립트가 제대로 동작하지 않기 때문에 settings 자바스크립트 eabled 속성을 true 해주어야 자바스크립트가 동작하게됩니다.

settings 통해서는 웹뷰의 캐시 모드 설정이나, 유저에이전트, 파일 접근 권한, 멀티 윈도우 허용, 허용 등의 설정을 있습니다.

앞의 예제에서는 기본 웹뷰 클라이언트를 사용했는데 웹 뷰 클라이언트를 상속받아 구현할 수도 있습니다.

이렇게 메소드들을 오버라이딩 해서 구현하면 됩니다.

이런 메소드들은 로딩단계에 대한 콜백인데, 대표적인 것들만 살펴보겠습니다.

should override url loading 웹뷰에 url 로드되기 전에 호출되는 콜백입니다. 

페이지 로딩에서는 실행되지 않고, 웹뷰 안에서 페이지를 이동할 호출됩니다. 

콜백에서 특정 url 접근을 제한한다거나 있습니다. 

onPagestarted onPageFinished처럼 페이지가 시작된 호출되는 콜백과 페이지 로딩이 끝난 호출되는 콜백이 있습니다. 

그리고 에러처리도 있습니다. 400번대 이상의 http error 발생했을 호출되는 콜백입니다.

콜백을 통해 404등의 에러페이지가 나왔을 다른 네이티브 화면을 띄운다거나 있습니다. 

SSL 연결이 실패하면 호출되는 콜백입니다.

webview 기본적으로 https 지원하므로 ssl 연결이 필수적인데, 인증된 기관에서 발행한 ssl인증서가 아니면 에러가 발생합니다.

에러가 발생했을 때는 에러를 무시하고 페이지를 로드할 수 도 있고, 오른쪽 사진처럼 인증서가 올바르지않은데 진행하겠냐는 다이얼로그를 띄울 수도 있다. 

하지만 이렇게 처리 할 시에는 스토어 등록에 문제가 생길 수 도 있다. 

외에도 리소스를 받을 , 사용자가 입력한 키에대한 콜백, 히스토리에 페이지가 추가될 등에 대한 처리를 해줄 있습니다. 

웹뷰 클라이언트와 별개로 크롬 클라이언트도 설정해줄 있습니다 .

역시 크롬클라이언트를 상속받아 구현합니다 .

웹뷰 클라이언트가 페이지 로딩에 따른 콜백이었다면 웹크롬 클라이언트는 페이지 안에서 일어나는 액션들의 콜백입니다.

여기도 대표적인 함수들을 몇개 보겠습니다. 

페이지 로딩 정도를 알려주는 콜백 함수입니다. 오른쪽 이미지처럼 프로그래스 바로 진행정도를 표시하거나 하는 등의 용도로 사용할 있습니다.

그리고 윈도우(새탭)가 열릴때나 닫힐 호출되는 콜백이 있습니다. 

다음으로 Js에서 Alert, comfirm, prompt 호출되었을 , 호출되는 콜백이 있습니다. 

여기 안에서 다이얼로그를 표시하거나, 토스트를 표시하거나, 별도의 뷰를 띄우거나 등등 사용자가 원하는대로 커스텀할 있습니다.

그리고 권한 요청이나, 콘솔메세지 출력에 대한 콜백도 있습니다. 

그리고 상에서 프로필 사진 변경이나 게시물 작성을 파일 chooser 눌렀을 호출되는 콜백이 있습니다 .

네이티브 상에서 별도의 라이브러리를 쓰거나 갤러리를 열거나 등의 액션을 있습니다.

그 외에도 위치 권한 요청에 대한 콜백이나, JsTimeout 등의 콜백이 존재합니다. 

그리고 안드로이드 네이티브에서 자바스크립트 함수를 실행 / 호출 하거나, 웹 안에서 안드로이드의 네이티브 코드를 호출하거나 하는 등의 행위를 할 수 있습니다. 

먼저 안드로이드에서 자바스크립트를 실행하는 코드를 보겠습니다. 

evaluateJavascript를 써서 자바스크립트 코드를 실행할 있습니다. 

이렇게 html 배경 색을 변경할 있습니다.

익명함수를 만들고 실행하여 반환값을 받을 수도 있습니다.

여기 예제에서는 반환값을 받아 토스트를 표시하고 있습니다.

토스트는 이런 UI 요소입니다. 

그리고 웹 페이지에 정의되어있는 자바스크립트 함수를 실행할 수도 있습니다. 

다음으로 웹에서 안드로이드 코드를 실행하는 과정을 보겠습니다.

우선 안드로이드에서 자바스크립트 인터페이스를 등록해줍니다Android라는 이름으로 인터페이스를 추가해줬습니다.

그리고 자바스크립트에서 Android.showToast 이런식으로 실행해주면 네이티브에서 함수가 호출됩니다. 

외에도 디버그 옵션을 켜서 디버깅을 한다거나 있습니다. 

마지막으로 웹뷰의 한계점은 다음과 같습니다. 

속도면에서 리소스들을 다운받고 html 받아서 웹을 그리기 때문에 네이티브보다 반응속도가 느립니다. 

 File Chooser  유튜브 풀스크린 같이 웹에서 당연하게 지원하는 것들을 기본적으로 지원하지 않습니다. 

개발자가 따로 커스텀 해줘야합니다. 

캐시, 쿠키가 앱마다 관리되기 때문에 A앱에서 네이버에 로그인 해놨는데 B앱에서는 로그인이 안되는 있는 경우가 생기기도 합니다. 

HTML5 CSS 일부 기능이 지원되지 않을 수도 있습니다. 

그리고 애드센스가 포함된 웹페이지에서 애드센스가 안보일 수도 있다고 합니다. 

이번 구글 IO 2023에서 발표되었던 new in web on android 대한 간략 소개입니다. 

기존에 웹뷰를 통해 웹에 접속하면 요청해더에 항상 x-requested-with 포함시켰다. 

웹에서 어떤 앱을 통해 들어왔는지 있었다. 

유저 프라이버시를 위해 기능을 deprecation 했다.

그리고 원래 구축해놓은 웹사이트에서 헤더에 의존성이 있을 있으므로, 특정 오리진에만 적용할 있도록 속성을 만들어 줬습니다. 

그리고 이제 웹뷰에서도 chrome origin trials 사용할 있게 되었습니다 .

이를 통해 크롬의 기능이나 실험적기능을 테스트 있습니다.

화면을 사용할 , 웹 뷰의 이미지나 텍스트를 드래그앤 드롭으로 가져올 있습니다.

Text input field 손글씨를 사용할 있습니다.

Jetpack Javascript Engine  컨텐츠를 표시하지 않고도 안에서 자바스크립트를 실행할 있게 해주는 엔진입니다. 

웹과 비지니스 로직을 공유할 간편하게 사용할 있습니다.

웹뷰 인스턴스를 생성하지 않고도 자바스크립트와 어셈블리 코드를 실행할 수 있고, 웹 뷰에 비해 적은 리소스를 사용하는 게 장점입니다.

자바스크립트 코드는 별도의 프로세스에서 실행되어 안전하고 안정적이라고 합니다. 

이제 앞에서 잠 설명했던 custom tab 관한 내용입니다. 

원래 custom tab은 full screen으로만 열렸는데 높이를 조절할 있게 되었습니다. 

오른쪽에 보이는 것처럼 유튜브에서 예시를 있습니다. 

태블릿 같은 화면에서 사용되었을 가로세로 자동으로 전환해준다고 합니다. 

세로일 떄 - Bottom Sheet 형태, 가로일 때 - Side bar 형태

그리고 Engagement를 수치적으로 측정할 수 있습니다. 

커스텀 탭안에서 얼마나 머물렀는지, 스크롤에 대한 정보들을 측정할 있습니다. 

 

끝.


 

 

 

 

 

 

 

행사를 위해 노력해주신 GDG Cloud Busan, GDG Busan 분들 그리고 스탭 분들 감사합니다. 

GDSC 활동 이후에 첫 외부활동인데 잘 마무리된 거 같아 다행입니다. 

 

PendingIntent

PendingIntent란 대기 중인 인텐트라는 뜻으로, 현재 어플리케이션이 살아있는지와 관계없이 특정 시점에 Intent를 수행할 수 있게 해주는 것이다.

다른 어플리케이션을 통해 Intent를 실행할 수 있도록 PendingIntent를 전달할 수 있다.

예시

  • Notification
    • NotificationManager에게 PendingIntent 전달
  • Widget
    • RemoteView에 ClickListener로 PendingIntent를 설정
    • AppWidgetManager가 PendingIntent를 실행
  • 특정 시점에 실행
    • AlarmManager에게 PendingIntent 전달

Android 12 부터는 PendingIntent를 만들 때 변경 가능 여부를 지정해야한다.

  • PendingIntent.FLAG_MUTABLE
  • PendingIntent.FLAG_IMMUTABLE

PendingIntent는 변경될 수 있는데, 악성 앱이 PendingIntent를 수정하여, 우리 앱의 다른 컴포넌트에 접근할 수도 있다.

Foreground Service

백그라운드에서 포그라운드 서비스를 시작할 수 없다.

앱이 백그라운드 상태에 있는 동안 포그라운드 서비스를 시작하려고 하면 ForegroundServiceStartNotAllowedException 이 발생한다.

WorkManager를 사용해야한다.

백그라운드에서 포그라운드 서비스 실행이 허용되는 경우

다음 상황에서는 앱이 백그라운드에서 실행되는 동안에도 포그라운드 서비스를 시작할 수 있습니다.

  • 앱이 활동과 같은 사용자에게 표시되는 상태에서 전환됩니다.
  • 사용자가 앱과 관련된 UI 요소에서 작업을 실행합니다. 예를 들어 대화창이나 알림위젯, 활동과 상호작용할 수 있습니다.
  • 사용자가 앱의 배터리 최적화를 사용 중지합니다. 시스템 설정에서 앱의 앱 정보 페이지로 사용자를 보내 사용자가 이 옵션을 찾도록 할 수 있습니

포그라운드 서비스 실행 제한  |  Android 12  |  Android Developers


Uploaded by N2T

+ Recent posts