일을 하다가 상식적으로 말도 안되는(?) 이상한 반올림 방법을 알게 되었는데,

곰곰이 생각해보니 정말 합리적이라는 생각이 들어서 적어둔다. (사실 처음 들으면 이제까지의 수학을 모두 부정하는 이상한 이론으로 들린다)

0.5를 반올림하면 0이 되고, 1.5을 반올림하면 2가 된다.

이런 공식이 나오게 된 일화1는 다음과 같다.

미국 국세청에서 국민들에게 소송을 당해서 패한 사건이 있었습니다.

년도는 잘 모르겠지만 규약 제정일이 1985년이니까 대충 그 근처리라 생각됩니다.

국민들은, 국세청에서 반올림을 적용해 세금을 거두었기 때문에 한가운데인 0.5 경계에 걸친 사람들은 무조건 올림한 세금을 내게 되어 부당 이득을 취했다고 주장하였습니다.

이에 반해 국세청은 반올림에 의하여 절반은 올리고 절반은 내렸기 때문에 부당 이득을 취하지 않았다고 주장했지만, 결국은 패소하게 됩니다.

그 이후 국세청에서는 새로운 반올림 규칙에 대하여 고민하게 됩니다.

그 결과 첫번째 0.5 경계의 숫자는 내리고 그 다음 숫자는 올리는 것을 반복하는 규칙을 만들어 냈습니다.

이것을 규칙화 시킨 것이 ‘가장 가까운 짝수로 반올림한다’는 내용인 것입니다.

5.5 경계에 걸린 사람은 6.0의 세금을 내야하기 때문에 손해이지만,

4.5 경계에 걸린 사람은 4.0의 세금을 내기 때문에 둘이 서로 상쇄되어 국세청의 부당 이득이 사라지게 됩니다.

생각해본 적이 없었는데, 정말로 공정하지 않은 계산이었던 것이다.

조금더 수학적(?)인 내용은 아래 글2이 잘 설명하고 있다.

반올림문제가 그리 큰 문제가 아닌 것처럼 보이지만 수 백억 원이 오고 가는 금융권에서는 이것으로 인해 적지 않은 금액의 계산이 달라집니다.

대개의 프로그래밍 언어들은 반올림의 방법으로서 “Banker’s rounding”를 사용합니다.

많은 사람들이 이 반올림 방법이 상식적으로 틀린 결과를 돌려주기 때문에 싫어하지만 아이러니컬하게도 이것은 가장 정확한 라운딩(rounding)방법으로서 개발된 것입니다.

이 방법은 0.5이하는 버리고 0.5이상이면 올립니다. 그리고 정확하게 0.5이면 가장 가까운 짝수로 올립니다.

가령 12.5에서 0.5는 버려지고 12로 만들지만 13.5는 0.5를 더하여 14가 됩니다.

Bankers rounding은 Gauss법을 사용하는 것으로 이는 0.5인 경우 2로 나누어질 수 있는 가장 가까운 수로 반올림 한다는 것입니다.

1.5 is rounded to 2
2.5 is rounded to 2
3.5 is rounded to 4
4.5 is rounded to 4

종종 이러한 방법이 일상적인 반올림(Standard Rounding) 상식과 어긋나지만 이 방법은 다음과 같은 정당성을 가집니다.

가령 12.0부 터 13.0사이를 0.1씩의 간격으로 나누면 9개의 값이 들어갑니다.

12.1, 12.2, 12.3, 12.4, 12.5, 12.6, 12.7, 12.8, 12.9 그리고 이 값들은 반올림의 대상이 됩니다.

상식적인 반올림이라면 9개의 숫자 중 5개는 올리고 4개는 버리게 됩니다.

그러나 이 방법은 공평하지 않다.

1/9만큼 한쪽은 더 가지고 한쪽은 부족하게 됩니다.

그러나 0.5에서 가장 가까운 짝수로 옮기도록 하게 되면 어떻게 될까요?

12.0 부터 14.0까지 18개의 반올림 대상이 생기고 버리는 쪽이나 올리는 쪽 모두 9개의 숫자를 나누어 갖게 됩니다.

따라서 한쪽에 치우치지 않는 공평한 셈이 됩니다.

‘은행가의 반올림’이라고 해서 마치 수학적으로는 반하는데, 경제학에서만 사용할 것 같은데 그렇지는 않다.

실제로 컴퓨터 연산에서도 아래3처럼 사용되고 있다.

ROUND_HALF_EVEN

Round towards the “nearest neighbor” unless both neighbors are equidistant, in which case, round towards the even neighbor.

Behaves as for ROUND_HALF_UP if the digit to the left of the discarded fraction is odd; behaves as for ROUND_HALF_DOWN if it is even.

This is the rounding mode that minimizes cumulative error when applied repeatedly over a sequence of calculations,

and is sometimes referred to as Banker’s rounding.

또, 엑셀이나 C#등에서도 기본 Rounding으로 제공하고 있다고 한다.

 

 

원래 만리장성chinese wall이라고 하는데,
경제용어로는 부서간/회사간 고객 정보에 대한 고립 또는 정보방화벽이라는 의미로 사용된다.
(그냥 회사에서 비개발 미팅 중에 듣고는 신기해서 기록해둠) 1

경제용어다. 한 회사 또는 한 그룹 안에서 내부 정보가 오고가지 못하도록 차단하는 것을 뜻한다. 흔히 한 회사 안의 정보는 그 내부에서 자유롭게 오갈 수 있다고 생각하기 쉬운데, 그랬을 때에는 오히려 문제를 일으키거나 고객의 이익을 해칠 수 있다. 때에 따라서는 불법이 될 수도 있다. 예를 들어, 어떤 기업이 두 가지 사업을 하고 있는데 A 사업부의 고객 정보를 B 사업부에서 마음대로 가져다가 마케팅에 활용해도 될까? 그래서는 안 된다. 사용자가 동의한 목적을 넘기 때문에 개인정보 침해가 된다. 또한 B 사업부와 같은 분야에서 경쟁하고 있는 다른 회사에서 보았을 때에는 A 사업부의 정보를 이용해서 부당한 이득을 얻는 것이므로 불공정경쟁으로 엮일 수 있다. 따라서 두 사업부 사이에 고객정보가 공유되지 못하도록 차단이 되어 있어야 한다. 이런 겻을 차이니즈 월이라고 한다. 눈에 보이는 벽이 아니라, 분리가 필요하다는 개념의 벽이다.

Lombok을 사용했을 때, @Data를 붙이면 기본생성자로 @NoArgsConstructor를 생성해주는 줄 알았다.

그런데 누가 물어봐서 대답하려고 보니, 명확하게 모르는 것을 깨닫고 찾아보았다. 1

@Data를 붙이게 되면, 일반적으로는 @NoArgsConstructor를 만들어주는 것처럼 보인다.

하지만 엄밀히 말하면 @NoArgsConstructor가 아니라 @RequiredArgsConstructor가 생성된다.

즉, final접근자가 붙어있거나 @Nonnull 애노테이션을 붙인 경우에는 해당 필드를 가진 생성자가 만들어진다.

보통의 경우는 final이나 @Nonnull이 없는 경우라서 @NoArgsConstructor를 만들어 주는 것 같은 착시효과를 보일 뿐이다.

Api를 호출하는 서비스에 대한 테스트는 잘 하지 않는 경우가 많다.

혹은 @Ignore를 붙여놓고 필요시에만 풀어서 쓰는 경우도 있다.

Mock Server를 제공해서 테스트 할 수 있는 걸 알고 있는데, 자주 쓰지 않으니 쓸 때마다 헷갈려서 기록해둔다.

 

새로 스프링 배치 프로그램을 만들었는데, 테스트 환경에서는 특별히 이슈가 없다가 운영 환경에서 오류가 발생했다.

결국은 8080 포트가 충돌나서 오류가 발생한 것이었다.

배치를 여러 번 실행시키는 경우 톰캣이 제대로 shutdown시키지 못해서 발생하는 문제였다.

그런데 스프링 배치인데, 굳이 톰캣을 8080 포트로 실행해야 할 이유가 있을까?

찾아보니 스프링 배치에서는 기본적으로 톰캣을 8080 포트로 로딩하고, 배치 모니터링을 제공한다.

(사실 엄밀히 따지면 배치가 실행되는 동안만 톰캣이 유효하도록 되어 있어서, 내 경우에는 별로 의미가 없는 모니터링이었다)

그래서 찾아봤더니, 아래처럼 설정을 하면 톰캣을 실행시키지 않는다고 한다.

앗, 그런데 deprecate되었다고 warning이 발생한다.

해당 설정을 하는 곳을 찾아봤더니,

위 옵션으로 서블릿을 실행할 것인지 아닌지 처리하는 설정이고,  2.0 버전부터 deprecate되었다고 친절하게 적어놨다.

SpringBoot 2.0 부터는 아래처럼 WebApplicationType을 통해서 설정이 가능하다고 한다.

결국 application.yml에 아래처럼 설정하면 된다.

Java에서 Jpa를 쓰다보면, 항상 select를 선으로 진행하게 되는 경우가 많다.

예를 들어, name과 age를 입력했는데, name이 unique하다면,

DB에서 name으로 select를 해보고, 있으면 setAge()를 통해서 update를 진행해야 하고,

없으면 해당 Entity를 새로 생성해서 insert를 해야한다.

그래서 아래와 같은 로직을 사용하는 경우가 많다.

매번 null check하는 것은 불필요한데, 은근히 많이 사용되는 패턴이다.

그래서 아래처럼 Utility class를 구현했다.

 

와 독일에서 직구한 제품이 있는데, 독일 송장이 엄청 빠르게 진행되기에 “1주일 안에 받겠는데? 정말 지구촌이네” 했더니만, 저 상태로 1주일이 넘게 지나버렸다. 독일은 바다도 없는데, 배로 운송하는 건지… 중간에 제품이 유실된 건지 알 수가 없다.

그런데 인터넷에서 아래 같은 내용을 찾았다. 1
IPZ-Ffrm이 옥천 버뮤다만큼 유명한 곳이구나. 예전에 살 땐 모르고 지나갔었는데 ㅠ.ㅠ

한국에 옥천 버뮤다가 있다면.. 독일에는 택배 숙성 장소 IPZ-Ffm 이 있는듯..

 

추가.

10일 뒤에 관세청에 등록되었다고 연락이 왔다. 난 내 물건들이 국제미아 된 줄 알고 걱정을… 기다리면 관세납부 하라고 연락올 듯.

 

배경

현재 개발하고 있는 것은 ‘서비스의 정산’에 대한 서비스이다. 그런데 정산이라는 것이 결제가 있다면 항상 들어가야 하는 미들웨어와 같은 성격을 지니고 있다.그러다보니 서비스의 설정 정보들이 각 프로젝트에 혼재해있어서 정리를 해보려고 한다.

 

현재 상황

전체적으로 정산에 대한 공통 부분을 사용하기 위해 settlement-common이라는 모듈을 사용하고 있다. 각 서비스에서는 settlement-common에 대한 의존성을 연결하여 사용한다. 그런데 정산에서 사용하는 property 정보들이 모두 하위 서비스에서 정의하여 사용하고 있다. 예를 들면 erp.url은 하위 서비스의 application.yml에 정의되어 있다.

 

하고 싶은 것

보통의 경우에는 common 공통의 설정을 로딩해서 사용하고, 일부의 케이스에 대해서만 하위 서비스에서 설정을 overriding하고 싶었다. application.yml에 다른 설정과 함께 섞여 있는 것도 제거하고 싶었다.

 

@ConfigurationProperties 사용

아래처럼 Property정보를 Bean으로 등록해서 사용하는 것이 @Value를 사용하는 것보다 나을 거란 생각을 했다.
@Component가 없어도 Bean으로 사용할 수 있는데 굳이 쓴 이유는, IDE에서 Bean Mapping이 안된다는 waring이 발생하는 게 싫어서이다. 그리고 @ConfigurationProperties는 setter를 이용해서 값을 주입하는 것도 약간은(?) 신기한 부분이다. 최근 스프링의 추세가 reflection을 통한 강제 주입이라서.

 

파일분리 (application.yml -> settlement.yml)

애사당초 얘기했던 것처럼, 하위 프로젝트의 application.yml에서 모든 것을 정의해서 사용하면, 어떤 property를 어떤 모듈에서 사용하는지 파악하기 힘들고, 이후에 리팩토링할 때에도 장애물이 된다. 그래서 @ConfigurationPropertieslocations attribute를 활용하여 파일을 분리하였다.

 

locations attribute deprecated

그런데 locations attribute가 deprecate되었다. 심지어 최신 버전의 springboot에서는 아예 제거되었다. 1 그래서 별로 권장하지 않는다고는 하지만, @PropertySource를 활용하기로 했다.

 

spring.active.profile 기능 활용

아래처럼 springEL을 활용해서 각 profile별로 필요한 설정을 import할 수 있다. 다만 같은 값의 overriding의 경우 선언한 순서에 영향을 받는다. 2 특히 아래와 같은 경우에는 @PropertySource에 선언된 Array 중에 나중에 선언된 것이 override된다.

 

문제 발생, yml 파싱 불가

위처럼 로직을 짜고 테스트를 돌려보면, NPE가 발생한다.
이유인즉슨, Bean이 제대로 setting이 되지 않았기 때문인데, 디버거를 통해 확인했더니 yml 파일을 인식하지 못한다. 3 그래서 yml 파일을 사용할 수 있도록 Factory 클래스를 추가 구현하였다.

 

yml 파싱은 잘되는데, profile은 어떻게…

application.yml처럼 Array 형식으로 정의하고 profile별로 property를 꺼내 쓰고 싶은데, 위의 YamlPropertySourceFactory를 보면, profile을 null로 넘겼다. 해당 profile을 넣어주면 제대로 파싱될 것 같은데, Factory 파일이 bean도 아니라서 Environment를 사용할 수 없고 어떻게 해야할지는 좀더 생각해봐야 할 것 같다.

 

하위 서비스에서 설정 완료

하위 서비스에서는 아래처럼 추가 설정을 overriding해서 사용할 수 있다.

 

또 다른 문제, 파일이 없을 경우

그런데 settlement-common은 하위 프로젝트들의 모든 spring.profiles를 알 수 없다. 그래서 정의되지 않은 profile이 들어올 경우, 리소스를 읽을 수 없다면서 settlement-${spring.profiles.active}.yml 구문에서 오류가 발생한다. 그래서 아래처럼 @PropertySource를 분리하고, ignoreResourceNotFound 속성을 추가해서 처리하였다.

그런데 계속 오류가 발생한다. 디버거를 통해서 확인하였더니, YamlPropertySourceFactory내에서 YamlPropertySourceLoader를 사용하고 있는데, 여기에서 리소스가 없는 경우에 대해 기존의 경우와 다르게 return을 하고 있다. 결국 NPE가 발생한다. (기본 factory를 사용하는 경우에는 문제없음)

 

결론

yml 포맷이 훨씬 구조화된 형식이라 properties 파일보다 권장되기도 하는데, 사용하려니 현실적인 벽에 많이 부딪혀서 properties 파일을 우선 사용하는 것으로 선회하였다. 끝까지 고쳐보지 못하고, 실패를 결론으로 기록하려니 좀 아쉽긴 한데, 시간도 시간인지라… 나중에는 다시 볼 일이 생기지 않을까 싶은 생각으로 일단 여기까지 기록해둔다.

 

추가1

resource 파일은 classpath로 복사될 때, 같은 이름이면 우선순위에 따라 overwrite된다. 즉, application.properties 또는 application.yml 파일이 settlement-common에 있는데, 하위 모듈에서 동일한 파일이 있는 경우에는 내용에 상관없이 하위 모듈의 것을 사용하게 된다. 심지어 해당 property가 없는 경우라 할지라도 common의 것을 사용할 수 없다. override가 아니라 파일이 overwrite되는 것에 유의!

추가2

파일이 다르고 키가 같은 property의 경우에는 해당 값을 우선순위에 따라 override한다.

 

배달앱에서 동의없이 멋대로 신용카드 결제? … ‘있을 수 없는일’이라는 기사를 보고 결제 시스템 개발자로서 짧게 적어본다.
(참고로 해당 업체와는 아무 관련없음)

부산시 기장군에 사는 최 모(남)씨는 지난 25일 오후 6시경 ‘요기요’ 앱으로 치킨을 주문하려고 앱을 실행했다.
치킨 전문점을 선택해 메뉴를 살피던 중 오류가 발생해 ‘주문이 취소되었습니다’란 문구가 뜨고 앱이 종료됐다.

배달앱을 이용하던 소비자가 ‘결제’ 버튼을 누르지 않았는데도 등록된 신용카드로 결제됐다며 문제 제기했다. 업체 측은 시스템상 일어날 수 없는 오류라고 반박했다.

관계자는 “‘1초 결제’가 신용카드를 등록해놓으면 사인이나 비밀번호 없이 바로 결제되는 구조라 주문을 진행하는 과정서 고객이 오해한 부분이 생겼을 수도 있지 않을까 싶다”며…(후략)

서비스를 개발하면서 수없이 많이 고객문의에 대해 응답을 해봤던 경험으로 보면, 대부분 고객은 정직하다. 그리고 서비스 담당자는 그런 관점에서 오류를 디버깅해 볼 필요가 있다고 생각한다. 혹은 고객이 어뷰징을 한다고 생각한다면, 해당 근거를 착실히 찾아볼 필요가 있다. 그래서 개발자는 로그나 DB데이터를 착실하게 쌓아야 할 의무가 있다. 보통 고객이 어뷰징을 하게 되는 이유는 대부분 프로모션때문이다. 할인이나 쿠폰, 이벤트 참여 등의 어떤 이익을 가지고 어뷰징을 진행한다. 혹은 본인의 실수를 덮기 위해서인 경우도 있고, 정말 고객이 인지하지 못한 채 잘못된 경우도 있긴 하다.

나도 위와 비슷한 경험을 한 적이 있다. 아마존 앱에서 물건을 구입 했을 때 였다. 아마존에서 KRW(원화)로 구입하면 이중환전이 된다는 사실을 알고 있기 때문에 USD(달러)인 것을 확인하고 결제를 진행하였다. 그런데 어느 날 발송된 인보이스를 확인했더니, 아마존 자체 환율(보통 환율보다 100원정도 비쌈)으로 이중환전이 된 것을 확인했다. 마치 ‘결제 버튼을 누르지도 않았는데도 결제가 됐다’며 의문을 제기한 위 기사처럼 ‘proceed with USD’를 확인하고 버튼을 눌렀는데, KRW로 결제가 되어버린 것이다. 나름 개발자로서 몇 가지 테스트를 진행해봤다. 아마존 앱에는 유저의 결제를 빠르게 진행하기 위해서 상단, 중단, 하단에 결제 버튼이 있는데, 그 중에 하나가 오작동하는 모양이다. 라벨은 ‘proceed with USD’로 되어 있는데, 내부 동작이 Locale을 따라 가는 것인지 KRW로 결제되어 버렸다. 명백한 버그라고 생각하고 고객센터에 연락했지만, ‘그럴리 없다'(마치 위 기사처럼)는 답변이었다. 게다가 그 친구들은 Locale이 미국으로 되어 있을테니, 나와 같은 경우가 재현될리도 없다. 아마 테스트도 부족했을 것 같고… 나는 이미 재현되는 상황이었기 때문에 고객센터에서 처리하지 말고, 개발부서로 포워딩을 요청했는데, 사실상 거절당했다. 언어의 장벽일지도 모르겠다.

사실 요기요 케이스가 버그인지, 고객실수인지, 고의적인 어뷰징인지는 알 수 없다. 하지만 행동의 유인이 명확하지 않은 경우에는 항상 ‘무죄추정의 원칙‘을 적용하여 생각해봐야 한다. 아무리 서비스를 잘 만들어도 버그는 존재하기 마련이다.

 

별 건 아닌데, 기존에는 몰랐던 intellij 단축키 중에 편리할 것 같은 걸 찾아서 기록해둔다.

평상시에는 cmd+]cmd+[를 사용해서 코드를 탐색했었는데, 수정된 것만 탐색해주는 기능이 있었다.

 

cmd+shift+back은 가장 최근에 수정하게 된 지점으로 커서를 옮겨준다.

거기에 cmd+option+shift+화살표를 누르면, 수정된 내용까지 diff 해준다. 1