Jini's Blog

Ing...

  • Home
  • Business
    • Internet
    • Market
    • Stock
  • Parent Category
    • Child Category 1
      • Sub Child Category 1
      • Sub Child Category 2
      • Sub Child Category 3
    • Child Category 2
    • Child Category 3
    • Child Category 4
  • Featured
  • Health
    • Childcare
    • Doctors
  • Home
  • Business
    • Internet
    • Market
    • Stock
  • Downloads
    • Dvd
    • Games
    • Software
      • Office
  • Parent Category
    • Child Category 1
      • Sub Child Category 1
      • Sub Child Category 2
      • Sub Child Category 3
    • Child Category 2
    • Child Category 3
    • Child Category 4
  • Featured
  • Health
    • Childcare
    • Doctors
  • Uncategorized

오브젝트 : 코드로 이해하는 객체지향 설계

 Jini     오후 11:03     도서요약     No comments   

오브젝트 - 코드로 이해하는 객체지향 설계 / 조영호 지음 / 위키북스

객체지향에 대해 그 동안 잊고 있었던 것들을 상기시켜주고 새로운 인사이트를 줬으며 그 동안의 설계에 대해 돌이켜 보게 해준 유익한 책.

객체 사이의 의존성을 완전히 없애는 것이 정답은 아니다. 최소한의 의존성만 유지하고 불필요한 의존성을 제거하는 것이다.

객체가 어떤 데이터를 가지느냐보다는 객체에 어떤 책임을 할당할 것이냐에 초점을 맞춰야 한다.

객체지향 프로그램을 작성할 때 가장 먼저 고려하는 것은 무엇인가? 대부분의 사람들은 클래스를 결정한 후에 클래스에 어떤 속성과 메서드가 필요한지 고민한다. 안타깝게도 이것은 객체지향의 본질과는 거리가 멀다. 객체지향 패러다임으로의 전환은 다음의 두 가지에 집중해야 한다.
첫째, 어떤 클래스가 필요한지를 고민하기 전에 어떤 객체들이 필요한지 고민하라.
둘째, 객체를 독립적인 존재가 아니라 기능을 구현하기 위해 협력하는 공동체의 일원으로 봐야 한다.

의존성의 양면성
코드의 의존성과 실행 시점의 의존성이 다르면 다를수록 코드를 이해하기 어려워진다는 것이다. 코드를 이해하기 위해서는 코드뿐만 아니라 객체를 생성하고 연결하는 부분을 찾아야 하기 때문이다. 반면 코드의 의존성과 실행 시점의 의존성이 다르면 다를수록 코드는 더 유연해지고 확장 가능해진다.
설계가 유연해질수록 코드를 이해하고 디버깅하기는 점점 더 어려워진다는 사실을 기억하라. 반면 유연성을 억제하면 코드를 이해하고 디버깅하기는 쉬워지지만 재사용성과 확장 가능성은 낮아진다는 사실도 기억하라. 여러분이 훌륭한 객체지향 설계자로 성장하기 위해서는 항상 유연성과 가독성 사이에서 고민해야 한다. 무조건 유연한 설계도, 무조건 읽기 쉬운 코드도 정답이 아니다. 이것이 객체지향 설계가 어려우면서도 매력적인 이유다.

역할,책임,협력
객체지향 패러다임의 관점에서 핵심은 역할(role), 책임(responsibility), 협력(collaboration)이다.

협력
객체지향 시스템은 자율적인 객체들의 공동체다. 객체는 고립된 존재가 아니라 시스템의 기능이라는 더 큰 목표를 달성하기 위해 다른 객체와 협력하는 사회적인 존재다. 협력은 객체지향의 세계에서 기능을 구현할 수 있는 유일한 방법이다. 두 객체 사이의 협력은 하나의 객체가 다른 객체에게 도움을 요청할 때 시작된다. 메시지 전송은 객체 사이의 협력을 위해 사용할 수 있는 유일한 커뮤니케이션 수단이다. 메시지를 수신한 객체는 메서드를 실행해 요청에 응답한다. 여기서 객체가 메시지를 처리할 방법을 스스로 선택한다는 점이 중요하다.
협력이 설계를 위한 문맥을 결정한다. 객체의 행동을 결정하는 것은 객체가 참여하고 있는 협력이다. 협력이 바뀌면 객체가 제공해야 하는 행동 역시 바뀌어야 한다. 협력은 객체가 필요한 이유와 객체가 수행하는 행동의 동기를 제공한다.

책임
객체의 책임은 '무엇을 알고 있는가'와 '무엇을 할 수 있는가'로 구성된다. 크레이그 라만은 이러한 분류 체계에 따라 객체의 책임을 크게 '하는 것(doing)'과 '아는 것(knowing)'의 두 가지 범주로 나누어 세분화하고 있다.
객체는 자신이 맡은 책임을 수행하는 데 필요한 정보를 알고 있을 책임이 있다. 또한 객체는 자신이 할 수 없는 작업을 도와줄 객체를 알고 있을 책임이 있다. 어떤 책임을 수행하기 위해서는 그 책임을 수행하는데 필요한 정보도 함께 알아야 할 책임이 있는 것이다.

책임할당
자율적인 객체를 만드는 가장 기본적인 방법은 정보를 가장 잘 알고 있는 전문가에게 그 책임을 할당하는 것이다. 이를 책임 할당을 위한 정보전문가 패턴이라고 부른다.

책임주도설계
책임을 찾고 책임을 수행할 적절한 객체를 찾아 책임을 할당하는 방식으로 협력을 설계하는 방법을 책임 주도 설계라고 부른다.
책임을 할당할 때 고려해야 하는 두 가지 요소
1. 메시지가 객체를 결정한다 - 객체에게 책임을 할당하는 데 필요한 메시지를 먼저 식별하고 메시지를 처리할 객체를 나중에 선택하는 것이 중요하다.
2. 행동이 상태를 결정한다 - 객체지향 패러다임에 갓 입문한 사람들이 가장 쉽게 빠지는 실수는 객체의 행동이 아니라 상태에 초점을 맞추는 것이다. 초보자들은 먼저 객체에 필요한 상태가 무엇인지를 결정하고, 그 후에 상태에 필요한 행동을 결정한다. 이런 방식은 객체의 내부 구현이 객체의 퍼블릭 인터페이스에 노출되도록 만들기 때문에 캡슐화를 저해한다.

역할 - 추상화
객체가 어떤 특정한 협력 안에서 수행하는 책임의 집합을 역할이라고 부른다. 예) 배역과 배우. 배역과 배우 사이의 특성은 동일한 배역을 여러 명우 배우들이 연기할 수 있다는 것이다.

캡슐화, 응집도와 결합도
캡슐화의 정도가 응집도와 결합도에 영향일 미친다는 사실을 강조하고 싶다. 캡슐화를 지키면 모듈 안의 응집도는 높아지고 모듈 사이의 결합도는 낮아진다. 캡슐화를 위반하면 모듈 안의 응집도는 낮아지고 모듈 사이의 결합도는 높아진다. 따라서 응집도와 결합도를 고려하기 전에 먼저 캡슐화를 향상시키기 위해 노력하라. 캡슐화는 설계의 제1원리다.

책임 주도 설계를 향해
데이터보다 행동을 먼저 결정하라. 데이터 중심의 설계에서는 이 객체가 포함해야 하는 데이터가 무엇인가를 결정한 후에 데이터를 처리하는 데 필요한 오퍼레이션은 무엇인가를 결정한다. 반면 책임 중심의 설계에서는 이 객체가 수행해야 하는 책임은 무엇인가를 결정한 후에 이 책임을 수행하는 데 필요한 데이터는 무엇인가를 결정한다.

협력이라는 문맥 안에서 책임을 결정하라
협력을 시작하는 주체는 메시지 전송자이기 때문에 협력에 적합한 책임이란 메시지 수신자가 아니라 메시지 전송자에게 적합한 책임을 의미한다. 다시 말해서 메시지를 전송하는 클라이언트의 의도에 적합한 책임을 할당해야 한다는 것이다. 객체를 결정한 후에 메시지를 선택하는 것이 아니라 메시지를 결정한 후에 객체를 선택해야 한다.
객체를 가지고 있기 때문에 메시지를 보내는 것이 아니다. 메시지를 전송하기 때문에 객체를 갖게 된 것이다.

퍼블릭 인터페이스
디미터법칙 - 객체의 내부 구조에 강하게 결합되지 않도록 협력 경로를 제한하라는 것이다. 오직 하나의 도트만 사용하라는 말로 요약되기도 한다. 무비판적으로 디미터 법칙을 수용하면 퍼블릭 인터페이스 관점에서 객체의 응집도가 낮아질 수도 있다. 기차충돌처럼 보이는 코드라도 객체의 내부 구현에 대한 어떤 정보도 외부로 노출하지 않는다면 그것은 디미터 법칙을 준수한 것이다.
묻지말고 시켜라 - 절차적인 코드는 정보를 얻은 후에 결정한다. 객체지향 코드는 객체에게 그것을 하도록 시킨다. 내부의 상태를 묻는 오퍼레이션을 인터페이스에 포함시키고 있다면 더 나은 방법은 없는지 고민해 보라. 내부의 상태를 이용해 어떤 결정을 내리는 로직이 객체 외부에 존재하는가? 그렇다면 해당 객체가 책임져야 하는 어떤 행동이 객체 외부로 누수된 것이다.
의도를 드러내는 인터페이스 - 무엇을 하는지를 드러내는 이름은 코드를 읽고 이해하기 쉽게 만들뿐만 아니라 유연한 코드를 낳는 지름길이다.
명령-쿼리 분리

순수한 가공물에게 책임 할당하기
크레이그 라만은 시스템을 객체로 분해하는 데는 크게 두 가지 방식이 존재한다고 설명한다. 하나는 표현적 분해이고 다른 하나는 행위적 분해다. 표현적 분해는 도메인 모델에 담겨 있는 개념과 관계를 따른다. 그러나 종종 도메인 개념을 표현하는 객체에게 책임을 할당하는 것만으로는 부족한 경우가 발생한다. 모든 책임을 도메인 객체에게 할당하면 낮은 응집도, 높은 결합도, 재사용성 저하와 같은 심각한 문제점에 봉착하게 될 가능성이 높아진다. 이 경우 도메인 개념을 표현한 객체가 아닌 설계자의 편의를 위해 임의로 만들어낸 가공의 객체에게 책임을 할당해서 문제를 해결해야 한다. 크레이그 라만은 이처럼 책임을 할당하기 위해 창조되는 도메인과 무관한 인공적인 객체를 순수한 가공물이라고 부른다. 순수한 가공물은 표현적 분해보다 행위적 분해에 의해 생성되는 것이 일반적이다.
이런 측면에서 객체지향이 실세계의 모방이라는 말은 옳지 않다. 만약 도메인 개념이 만족스럽지 못하다면 주저하지 말고 인공적인 객체를 창조하라. 객체지향이 실세계를 모방해야 한다는 헛된 주장에 현혹될 필요가 없다. 우리가 애플리케이션을 구축하는 것은 사용자들이 원하는 기능을 제공하기 위해서지 실세계를 모방하거나 시뮬레이션하기 위한 것이 아니다.

유연성에 대한 조언
유연한 설계는 유연성이 필요할 때만 옳다 - 유연상은 항상 복잡성을 수반한다. 유연하지 않은 설계는 단순하고 명확하다. 유연한 설계는 복잡하고 암시적이다. 설계가 유연할수록 클래스 구조와 객체 구조 사이의 거리는 점점 멀어진다. 따라서 유연함은 단순성과 명확성의 희생 위에서 자라난다. 단순하고 명확한 해법이 그런대로 만적스럽다면 유연성을 제거하라. 유연성은 코드를 읽는 사람들이 복잡함을 수용할 수 있을 때만 가치가 있다.
협력과 책임이 중요하다 - 초보자가 자주 저지르는 실수 중 하나는 객체의 역할과 책임의 자리를 잡기 전에 너무 성급하게 객체 생성에 집중하는 것이다. 중요한 비지니스 로직을 처리하기 위해 책임을 할당하고 협력의 균형을 맞추는 것이 객체 생성에 관한 책임을 할당하는 것보다 우선이다. 불필요한 싱글턴 패턴은 객체 생성에 관해 너무 이른 시기에 고민하고 결정할 때 도입되는 경향이 있다. 핵심은 객체를 생성하는 방법에 대한 결정은 모든 책임이 자리를 잡은 후 가장 마지막 시점에 내리는 것이 적절하다는 것이다.

언제 상속을 사용해야 하는가?
상속을 사용하는 일차적인 목표는 코드 재사용이 아니라 타입 계층을 구현하는 것이어야 한다. 타입 사이의 관계를 고려하지 않은 채 단순히 코드를 재사용하기 위해 상속을 사용해서는 안 된다.
마틴 오더스키는 다음과 같은 질문을 해보고 두 질문에 모두 "예"라고 답할 수 있는 경우에만 상속을 사용하라고 조언한다.
1. 상속 관계가 is-a 관계를 모델링하는가?
2. 클라이언트 입장에서 부모 클래스의 타입으로 자식 클래스를 사용해도 무방한가?

is-a관계 - 생각처럼 직관적이고 명쾌한 것은 아니다. 어휘적인 정의가 아니라 기대되는 행동에 따라 결정해야 한다. 어휘적으로 펭귄은 새지만 새의 정의에 날 수 있다는 행동이 포함된다면 펭귄은 새의 서브타입이 될 수 없다. 어떤 두 대상을 언어적으로 is-a라고 표현할 수 있더라도 일단은 상속을 사용할 예비 후보 정도로만 생각하라.
행동 호환성 - 개념적으로 어떤 연관성이 있다고 하더라도 행동에 연관성이 없다면 is-a 관계를 사용하지 말아야 한다. 행동이 호환될 경우에만 타입 계층으로 묶어야 한다. 여기서 중요한 것은 행동의 호환 여부를 판단하는 기준은 클라이언트의 관점이라는 것이다. 자연어에 현혹되지 말고 요구사항 속에서 클라이언트가 기대하는 행동에 집중하라.

Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Google+
  •  Stumble
  •  Digg

정말 죄송하지만, 여러분이 얼마나 열심히 노력했는지는 1도 중요하지 않아요.

 Jini     오전 11:46     일상, 자기계발, 책     No comments   

커리어리앱에서 본 글인데 자극적인 제목이긴 하지만 맞는 말 이라고 생각한다.

1. 목표를 달성하는 사람들은, (탁월한 성취를 이루어내는 사람들은), '공헌'에 초점을 맞춘다. 자신이 지금 하고 있는 일에서 더 높은 곳을 바라보고, 목표를 달성하기 위해 자기 자신이 아니라  외부로 눈을 놀린다.

2. 그들은 다음과 같은 질문을 한다. "내가 속한 조직이 성과를 올리고 좋은 결과를 내는 데 나는 어떤 공헌을 할 수 있는가?"

3. 목표를 달성하는 사람들은 책임을 중요하게 여기며, 이렇게 공헌에 초점을 맞추는 것이 목표 달성의 열쇠다.

4. 하지만 대부분의 평범한 지식노동자들은 자기 능력보다 낮은 수준에 초점을 맞추는 경향이 있다. 그리고 이들은 "결과"가 아니라, "노력" 자체에 더 관심을 가진다.

5. 즉, 이들은 어떤 결과를 만들어냈는지가 아니라, 자신이 얼마나 노력했는지를 알아주기를 바란다.

6. 그 결과, 이들은 목표를 달성하지 못한다. 그리고 직함과 지위가 아무리 높아도, 공헌과 책임이 아니라 "자기 얼마나 노력했는가"에만 초점을 맞추는 사람든 결국에는 다른 사람의 부하에 지나지 않는다.

7. 그러나 공헌과 기여에 초점을 맞추고 결과에 책임지는 사람은 아무리 하위 관리자라도 진정한 의미에서 "최고 경영자"다. 그는 조직 전체의 성과에 대해 스스로 책임감을 느끼는 사람이기 때문이다.

8. 특히 자신보다 큰 조직에 공헌할 목표에 초점을 맞추면 자신의 전문 분야, 한정된 기술, 그리고 자신이 속한 부서에만 관심을 두던 지식노동자도 조직 전체의 성과로 관심을 넓힌다. 왜냐하면 외부 세계야말로 결과가 있는 곳이기 때문이다.

9. 그러다 보면 자연스럽게 자신이 얼마나 노력했는지가 아니라 소비자, 단골 고객, 환자의 입장에서 생각하게 된다. 그게 조직이 존재하는 궁극적인 이유이기 때문이다. 그렇게 자기 자신의 노력이 아니라, 더 넓은 관점에서 세상을 바라보게 된다.

10. 즉 탁월한 성취를 이루는 사람들은 "자신이 얼마나 노력했는가" 따위에 매몰되지 않는다. 그 노력이 사람들에게 어떤 기여, 어떤 공헌을 하고 있는지를 살펴보고 분석한다. 그래서 그들은 얼마나 노력했는지가 아니라, 어떤 기여를 했는지를 말한다.

11. 바꿔 말해, 아무리 열심히 노력해도 기여한 것이 없다면 잔인하더라도 그건 별 의미가 없다. 그리고 이를 받아들일 수 있어야 더 나은 방식으로 일을 할 수 있다.

- 피터 드러커 / 자기경영노트 중


일반 조직원의 입장에서 조직기여, 외부세계 공헌과 같은 것은 와닿지 않을 수도 있다고 생각한다. 하지만 노력과 기여에 초점을 맞춰서 생각해보면 그 동안 나도 노력에 초점을 맞추는 경우가 더 많지 않았나 생각이 든다.

예를 들어 보통의 조직원들은(나 역시도) 회사 평가에 대해서 이렇게 생각한다.
"내가 얼마나 열심히 했는데 평가는 이것밖에 안주나?"

그런데 글을 읽고 가만히 생각해보면 나는 회사에 어떠한 기여를 했다고 당당히 말할 수 있을까 라는 생각을 하게 된다. 물론 평가체계까지 이상한 것을 내 탓이라 할 수는 없다. 하지만 당당하게 내가 어떤 성과를 냈다고 말할 수 있어야 평가체계는 물론 회사를 탓할 수 있지 않을까 라는 생각을 해본다. 내가 얼마나 열심히 했는데라는 말은 사실 회사 입장에서 의미는 없는 것 같다. 노력은 많은 사람이 하고 있으니 말이다.

실제 하고 싶은 얘기는 평가에 대한 것은 아니다. 일상 생활에서도 주로 노력에만 집중하지 않았나 하는 생각을 하게 됐다. 간단하게 책을 읽는다고 해보자. 보통은 책을 읽는 행위(노력)에 초점을 맞추는 경우가 많다. "난 이 책을 읽었다" 라는 행위(노력)에 말이다.

하지만 위 글을 적용해보면 내가 책을 읽는 노력을 통해서 얻은 성과는 무엇이냐? 에 초점을 맞춰서 책을 읽는게 맞지 않을까 생각해본다. 그 성과가 단순히 감동을 얻었음도 괜찮고, 기억에 남은 한 단락이 될 수도 있다. 성과의 크고 작음은 큰 문제는 아니라 생각한다. 중요한 것은 노력이 아니라 성과라는 것을 기억하는 것이다. 그렇게 보면 성과=목표 라고 볼 수도 있을 것 같다. 하지만 다른 점은 목표라고 하면 미리미리 세워야 하는 것, 거창해야 할 것 같고 작은 일에 하기에는 좀 오버스러운 것 같다고 생각되는 반면, 성과는 내가 행위를 하는 중에도 혹은 그 이후에도 생각해 볼 수 있는 것이라 목표보다 덜 거창하고 덜 번거로운 것 같다.

노력도 중요하지만 성과라는 관점에서 접근해보는 습관을 들여보는 것도 재미있을 것 같다.

Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Google+
  •  Stumble
  •  Digg

순수한 가공물에게 책임 할당하기

 Jini     오후 10:21     객체지향     No comments   

 부제 : 객체지향이 실세계의 모방이라는 말에 대하여

객체를 얘기할 때 실세계에 존재하는 무엇과 연관지으라는 얘기들을 많이 한다. 하지만 막상 클래스를 설계하고 객체를 생성하다보면 실세계와 맞지 않는 경우가 많이 생긴다. 이게 문제 되는 것은 항상 나를 의심하게 만들고 완성 이후에도 무언가 찝찝함이 남는다는 점이다. 

이런 나의 찝찝함을 덜어주는 내용이 있어 공유해본다.

크레이그 라만은 시스템을 객체로 분해하는 데는 크게 두 가지 방식이 존재한다고 설명한다. 하나는 표현적 분해이고 다른 하나는 행위적 분해다.

표현적 분해는 도메인 모델에 담겨 있는 개념과 관계를 따른다. 그러나 종종 도메인 개념을 표현하는 객체에게 책임을 할당하는 것만으로는 부족한 경우가 발생한다.

모든 책임을 도메인 객체에게 할당하면 낮은 응집도, 높은 결합도, 재사용성 저하와 같은 심각한 문제점에 봉착하게 될 가능성이 높아진다. 이 경우 도메인 개념을 표현한 객체가 아닌 설계자가 편의를 위해 임의로 만들어낸 가공의 객체에게 책임을 할당해서 문제를 해결해야 한다. 크레이그 라만은 이처럼 책임을 할당하기 위해 창조되는 도메인과 무관한 인공적인 객체를 PURE FABRICATION(순수한 가공물)  이라고 부른다.

(중략)

먼저 도메인이의 본질적인 개념을 표현하는 추상화를 이용해 애플리케이션을 구축하기 시작하라. 만약 도메인 개념이 만족스럽지 못하다면 주저하지 말고 인공적인 객체를 창조하라. 객체지향이 실세계를 모방해야 한다는 헛된 주장에 현혹될 필요가 없다. 우리가 애플리케이션을 구축하는 것은 사용자들이 원하는 기능을 제공하기 위해서지 실세계를 모방하거나 시뮬레이션하기 위한 것이 아니다.

출처 : 오브젝트 / 코드로 이해하는 객체지향 설계 / 조영호 지음

현실/도메인 개념과 맞지 않는 객체가 나오는 것은 자연스러운 현상이라는 것을 기억하자.


Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Google+
  •  Stumble
  •  Digg

키크론 K8 Pro VIA 프로그램 인식

 Jini     오후 5:21     키보드, 키크론, K8 PRO RGB, troubleshooting     No comments   

•케이블 모드 설정으로 바꾸고 USB 케이블 연결 후 VIA 프로그램 실행
•DESIGN 탭에서 Use V2 definitions(deprecated)를 on시키고 JSON 파일을 LOAD
Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Google+
  •  Stumble
  •  Digg

MongoDB 모델링

 Jini     오후 7:46     mongodb     No comments   

도큐먼트 크기

  1. 아주 빈번하게 변경되는 데이터만 분리하는 것은 안정적 서비스를 위해 고려해볼 만하다.
  2. 기능별로 조회되는 부분이 완전히 다르다면 (액세스 패턴이 다르다면) 분리하는 것도 좋은 방법이다.
  3. 각 필드를 그룹핑해서 서브 도큐먼트를 만들고 응용프로그램에서 필요 서브도큐먼트만 선별해서 가져가는 것이 네트워크 사용량을 줄이는데 도움이 될 것이다.
    (뒤에 조금 더 자세히 설명한다.)

정규화(Document Referencing) vs 역정규화(Embedding)

  1. 역정규화는 데이터가 얼마나 커지는지, 데이터를 어떻게 읽어가는지에 따라 할지 말지 결정해라.
  2. 가능하면 몽고DB도 정규화를 사용하라. join이 안된다고 Embedding을 먼저 생각하지는 말자.

서브 도큐먼트

  1. 데이터를 성격별로 묶어서 그룹핑하는 것도 고려하자.
    • 서브 도큐먼트 없이 일반 필드로만 구성
      {
          _id: 1,
          user_name: "matt.lee",
          user_real_name: "SeongUck Lee",
          
          contact_office_number: "010-xxxx-xxxx",
          contact_house_number: "02-xxx-xxxx",
          
          address_zip_code: "00000",
          address_detail:"ABC Apt Seongsu-1ga",
          address_city: "Seoul",
          address_country: "Korea",
          ...
      }
      
    • 서브 도큐먼트로 필드 그룹
      {
          _id: 1,
          user_name: "matt.lee",
          user_real_name: "SeongUck Lee",
          
          contact: {
            office_number: "010-xxxx-xxxx",
            house_number: "02-xxx-xxxx"
          },
          
          address: {
            zip_code: "00000",
            detail: "ABC Apt Seongsu-1ga",
            city: "Seoul",
            country: "Korea"
          },
          ...
      }
      
  2. 다음과 같은 장점이 있다.
    • 도큐먼트의 가독성과 식별성을 높여줄 수 있다.
    • 필드의 이름에서 반복된 단어를 생략할 수 있다.
    • 미세하지만 성능적인 장점도 얻을 수 있다.

인덱스

  1. 인덱스는 가능하면 "ESR" Rule대로 만들자(E:Equal, S:sort, R:range)
  2. 불필요한 인덱스는 만들지 말자. 인덱스 생성으로 인해 느려질 수 있다.
  3. 몽고의 Sort는 메모리에서 일어나며 sort를 위해 일반적으로 32mb의 공간만 할당하고 있다. 즉 32mb 이상을 읽어서 sort하면 에러가 발생한다. 이런 이유로 sort는 index를 이용하는 것을 추천한다.

배열

  1. 제한 없이 증가하는 데이터면 사용하지 말 것.
  2. 배열 관련 연산자는 $push, $pop이 성능상 좋다. $addToSet이나 $pull 연산이 빈번한 모델이면 배열 타입 사용은 한 번 더 고민해보자.
  3. 배열이 너무 크면 oplog 생성으로 인한 부하가 크다. 배열 사이즈는 적당히 유지되어야 한다.

필드 이름

  1. 데이터양이 아주 많은 경우 긴 필드이름은 성능에 영향을 미칠 수 있다.

도큐먼트 유효성

  1. 몽고DB는 컬렉션에 속한 필드 값에 대해서는 기본적으로 어떠한 제약도 가지지 않지만 필요시 RDBMS에서와 같은 정규화된 제약을 설정할 수 있다.
  2. 유효성 체크 규칙은 컬렉션 생성할 때 뿐만 아니라 이미 사용되고 있는 컬렉션에 대해서도 설정 가능하다.

조인

  1. 몽고DB는 기본적으로 조인을 지원하지 않았으나 3.2 버전부터는 $lookup이라는 보조적인 조인 기능을 제공한다.
  2. 하지만 몇 가지 제약사항이 있다.
    • INNER JOIN은 지원하지 않으며, OUTER JOIN만 지원한다.
    • 조인되는 대상 컬렉션은 같은 데이터베이스에 있어야 한다.
    • 샤딩되지 않은 컬렉션만 "$lookup" 오퍼레이션을 사용할 수 있다.

[출처]
대용량 데이터 처리를 위한 Real MongoDB / 위키북스 / 이성욱 지음
Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Google+
  •  Stumble
  •  Digg

Java 버전별 특징 및 변경사항

 Jini     오후 10:28     JAVA     No comments   

1. Java7

• 타입추론

List<String> list = new ArrayList<>();

• 이진수 리터럴, 숫자 리터럴에 _ 지원

• Switch문 문자열 가능

• try-with-resources 문 : AutoCloseable 인터페이스를 구현하는 클래스에 속하는 경우 다음과 같은 코드 패턴에 대한 단축 기능을 제공한다.

//JAVA 7 이전
//리소스를 연다
try {
    리소스를 이용해 작업한다.
}
finally {
    리소스를 닫는다.
}

//JAVA 7 이후
try (Resource res = ...) {
    res를 이용해 작업한다.
}

//여러 리소스를 지정할 수 있다.
try (Scanner in = new Scanner(Paths.get("/usr/share/dict/words"));
      PrintWriter out = new PrintWriter("/tmp/out.txt")) {
    while (in.hasNext()) {
      out.println(in.next().toLowerCase());
    }
}

• 여러 예외 잡기


try {
  //예외를 던질 수 있는 코드
}
catch (FileNotFoundException | UnknownHostException ex) {
  //파일 누락 및 알려지지 않은 호스트에 대한 긴급 액션
}
catch (IOException ex) {
  //그 외의 모든 I/O 문제에 대한 긴급 액션
}

2. Java8

• 32비트를 지원하는 마지막 공식 버전

• Lambda

• Stream

• Default Method

• LocalDate, LocalTime

• Optional


3. Java9

• default GC : G1GC

• Module System

• Collections of(불변)

List<String> list1 = List.of("a", "b");

• Jshell 추가

• try-with-resources 향상

// try() block 밖에 선언된 변수에 대해서는 try-with-resources 사용불가능했으나 java9부터 가능
// BufferedReader is declared outside try() block
BufferedReader br = new BufferedReader(new FileReader("C://readfile/input.txt"));

// Java 9 make it simple
try (br) {
    String line;
    while (null != (line = br.readLine())) {
        // processing each line of file
        System.out.println(line);
    }
} catch (IOException e) {
    e.printStackTrace();
}

• 인터페이스에 private 메소드 사용가능


4. Java10

• var 사용가능

• JVM 힙 영역을 시스템 메모리가 아닌 다른 종류의 메모리에도 할당 할 수 있게 되었다.


5. Java11

• 새로운 가비지 컬렉터 등장 : ZGC, Epsilon

Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Google+
  •  Stumble
  •  Digg

WebFlux Error Handling

 Jini     오후 10:07     error, exception, Webflux     No comments   

WebFlux Error Handling

  • doOnError : 예외 발생시 특정행위를 수행한다.
  • onErrorReturn : 예외 발생시 특정 값을 return 한다.
  • onErrorResume : 예외 발생시 다른 Flux형태로 return 한다.
  • onErrorContinue : 예외 발생시 멈추지 않고 해당 영역만 skip 한다(별도 처리 하지 않는 이상 정상 응답을 return한다).
  • onErrorMap : 예외 발생시 다른 Exception으로 변환한다.

doOnError

Source:
@RequestMapping("/error/test")
public Flux<Integer> getNum() {
  return Flux.range(0, 5)
      .map(x -> {
        if (x == 3) {
          throw new IllegalArgumentException("invalid num");
        }
        return x;
      })
      .doOnError(x -> log.error("num generate error"))
      .log();
}
Result:

 INFO 5276 --- [ctor-http-nio-2] reactor.Flux.PeekFuseable.1              : | onSubscribe([Fuseable] FluxPeekFuseable.PeekFuseableSubscriber)
 INFO 5276 --- [ctor-http-nio-2] reactor.Flux.PeekFuseable.1              : | request(unbounded)
 INFO 5276 --- [ctor-http-nio-2] reactor.Flux.PeekFuseable.1              : | onNext(0)
 INFO 5276 --- [ctor-http-nio-2] reactor.Flux.PeekFuseable.1              : | onNext(1)
 INFO 5276 --- [ctor-http-nio-2] reactor.Flux.PeekFuseable.1              : | onNext(2)
ERROR 5276 --- [ctor-http-nio-2] c.e.demo.r4.controller.R4Controller      : num generate error
ERROR 5276 --- [ctor-http-nio-2] reactor.Flux.PeekFuseable.1              : | onError(java.lang.IllegalArgumentException: invalid num)
ERROR 5276 --- [ctor-http-nio-2] reactor.Flux.PeekFuseable.1              : 

onErrorReturn

Source:

@RequestMapping("/error/test")
public Flux<Integer> getNum() {
  return Flux.range(0, 5)
      .map(x -> {
        if (x == 3) {
          throw new IllegalArgumentException("invalid num");
        }
        return x;
      })
      .onErrorReturn(99)
      .log();
}
Result:

 INFO 12068 --- [ctor-http-nio-2] reactor.Flux.OnErrorResume.1             : onSubscribe(FluxOnErrorResume.ResumeSubscriber)
 INFO 12068 --- [ctor-http-nio-2] reactor.Flux.OnErrorResume.1             : request(unbounded)
 INFO 12068 --- [ctor-http-nio-2] reactor.Flux.OnErrorResume.1             : onNext(0)
 INFO 12068 --- [ctor-http-nio-2] reactor.Flux.OnErrorResume.1             : onNext(1)
 INFO 12068 --- [ctor-http-nio-2] reactor.Flux.OnErrorResume.1             : onNext(2)
 INFO 12068 --- [ctor-http-nio-2] reactor.Flux.OnErrorResume.1             : onNext(99)
 INFO 12068 --- [ctor-http-nio-2] reactor.Flux.OnErrorResume.1             : onComplete()

onErrorResume

Source:

@RequestMapping("/error/test")
public Flux<Integer> getNum() {
  return Flux.range(0, 5)
      .map(x -> {
        if (x == 3) {
          throw new IllegalArgumentException("invalid num");
        }
        return x;
      })
      .onErrorResume(exec -> {
        log.error("num generate error");
        return Mono.just(99); //어떤 값으로 return을 할지 상황에 맞게 지정한다.
      })
      .log();
}
Result:

 INFO 8444 --- [ctor-http-nio-2] reactor.Flux.OnErrorResume.1             : onSubscribe(FluxOnErrorResume.ResumeSubscriber)
 INFO 8444 --- [ctor-http-nio-2] reactor.Flux.OnErrorResume.1             : request(unbounded)
 INFO 8444 --- [ctor-http-nio-2] reactor.Flux.OnErrorResume.1             : onNext(0)
 INFO 8444 --- [ctor-http-nio-2] reactor.Flux.OnErrorResume.1             : onNext(1)
 INFO 8444 --- [ctor-http-nio-2] reactor.Flux.OnErrorResume.1             : onNext(2)
ERROR 8444 --- [ctor-http-nio-2] c.e.demo.r4.controller.R4Controller      : num generate error
 INFO 8444 --- [ctor-http-nio-2] reactor.Flux.OnErrorResume.1             : onNext(99)
 INFO 8444 --- [ctor-http-nio-2] reactor.Flux.OnErrorResume.1             : onComplete()

OnErrorContinue

Source:

@RequestMapping("/error/test")
public Flux<Integer> getNum() {
  return Flux.range(0, 5)
      .map(x -> {
        if (x == 3) {
          throw new IllegalArgumentException("invalid num");
        }
        return x;
      })
      .onErrorContinue((exec, num) ->
          log.error("num generate error, number is : {}, error msg : {}", num, exec.getMessage()))
      .log();
}
Result:

 INFO 9728 --- [ctor-http-nio-2] reactor.Flux.ContextStart.1              : | onSubscribe([Fuseable] FluxContextStart.ContextStartSubscriber)
 INFO 9728 --- [ctor-http-nio-2] reactor.Flux.ContextStart.1              : | request(unbounded)
 INFO 9728 --- [ctor-http-nio-2] reactor.Flux.ContextStart.1              : | onNext(0)
 INFO 9728 --- [ctor-http-nio-2] reactor.Flux.ContextStart.1              : | onNext(1)
 INFO 9728 --- [ctor-http-nio-2] reactor.Flux.ContextStart.1              : | onNext(2)
ERROR 9728 --- [ctor-http-nio-2] c.e.demo.r4.controller.R4Controller      : num generate error, number is : 3, error msg : invalid num
 INFO 9728 --- [ctor-http-nio-2] reactor.Flux.ContextStart.1              : | onNext(4)
 INFO 9728 --- [ctor-http-nio-2] reactor.Flux.ContextStart.1              : | onComplete()

onErrorMap

Source:

@RequestMapping("/error/test")
public Flux<Integer> getNum() {
  return Flux.range(0, 5)
      .map(x -> {
        if (x == 3) {
          throw new IllegalArgumentException("invalid num");
        }
        return x;
      })
      .onErrorMap(exec -> {
        log.error("num generate error, error msg : {}", exec.getMessage());
        throw new RuntimeException("change error type");
      })
      .log();
}
Result:

 INFO 14204 --- [ctor-http-nio-2] reactor.Flux.OnErrorResume.1             : onSubscribe(FluxOnErrorResume.ResumeSubscriber)
 INFO 14204 --- [ctor-http-nio-2] reactor.Flux.OnErrorResume.1             : request(unbounded)
 INFO 14204 --- [ctor-http-nio-2] reactor.Flux.OnErrorResume.1             : onNext(0)
 INFO 14204 --- [ctor-http-nio-2] reactor.Flux.OnErrorResume.1             : onNext(1)
 INFO 14204 --- [ctor-http-nio-2] reactor.Flux.OnErrorResume.1             : onNext(2)
ERROR 14204 --- [ctor-http-nio-2] c.e.demo.r4.controller.R4Controller      : num generate error, error msg : invalid num
ERROR 14204 --- [ctor-http-nio-2] reactor.Flux.OnErrorResume.1             : onError(java.lang.RuntimeException: change error type)

[참고링크]
[Webflux Tip] 3. Webflux Error 처리! . (n.d.). https://akageun.github.io/2019/07/26/spring-webflux-tip-3.html.
Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Google+
  •  Stumble
  •  Digg

MongoDB Array Query(With MongoTemplate)

 Jini     오후 11:08     mongodb, mongotemplate     No comments   

Mongo Collection 구조

컬렉션명 : bookstore
{
  "_id": "1234567890",
  "books": [
    {
      "bookId": "1",
      "storeList": ["용산", "구로", "신도림A", "신도림B"]
    },
    {
      "bookId": "2",
      "storeList": ["서울역", "영등포A"]
    },
    {
      "bookId": "3",
      "bookNm": "재건축",
      "storeList": ["강남", "개포동"]
    }    
  ]
}

1. books element 추가

Monogo Query:

db.getCollection('bookstore').update(
  { _id: "1234567890" },
  { $push: { books: { bookId: "4", storeList: [ "신사", "대학로", "신촌" ] } } }
)
MongoTemplate:

var query = Query.query(Criteria.where("_id").is("1234567890"));
var update = new Update();
var book = new Book();
book.setBookId("4")
book.setStoreList(Arrays.asList("신사", "대학로", "신촌"));
update.push("books", book);

mongoTemplate.updateFirst(query, update, "bookstore");

2. books element 삭제

Monogo Query:

db.getCollection('bookstore').update(
  { _id: "1234567890" },
  { $pull: { books: { bookId: "4" } } }
)
MongoTemplate:

var query = Query.query(Criteria.where("_id").is("1234567890"));
var pullQuery = new Query();
pullQuery.addCriteria(Criteria.where("bookId").is("4"));
var update = new Update();
update.pull("books", pullQuery);

mongoTemplate.updateFirst(query, update, "bookstore");

3. storeList의 element 추가 - 값이 없는 경우에만

Monogo Query:

db.getCollection('bookstore').update(
  { _id: "1234567890", books: { $elemMatch: { bookId: "3" } } },
  { $addToSet: { "books.$.storeList": "신사동" } }
)
MongoTemplate:

var query = new Query();
var criteria = Criteria.where("_id").is("1234567890")
                  .and("books").elemMatch(Criteria.where("bookId").is("3"));
query.addCriteria(criteria);
var update = new Update();
update.addToSet("books.$.storeList", "신사동");

mongoTemplate.updateFirst(query, update, "bookstore");

4. storeList의 element 삭제

Monogo Query:

db.getCollection('bookstore').update(
  { _id: "1234567890", books: { $elemMatch: { bookId: "3" } } },
  { $pull: { "books.$.storeList": "신사동" } }
)
MongoTemplate:

var query = new Query();
var criteria = Criteria.where("_id").is("1234567890")
                  .and("books").elemMatch(Criteria.where("bookId").is("3"));
query.addCriteria(criteria);
var update = new Update();
update.pull("books.$.storeList", "신사동");

mongoTemplate.updateFirst(query, update, "bookstore");

5. storeList의 element 변경

Monogo Query:

db.getCollection('bookstore').updateOne(
    {_id: "1234567890", books: {$elemMatch:{bookId: "3"}}},
    {$set: {"books.$.bookNm": "재개발"}}
)
MongoTemplate:

var query = new Query();
var criteria = Criteria.where("_id").is("1234567890")
                  .and("books").elemMatch(Criteria.where("bookId").is("3"));
query.addCriteria(criteria);
var update = new Update();
update.set("books.$.bookNm", "재개발");

mongoTemplate.updateFirst(query, update, "bookstore");
Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Google+
  •  Stumble
  •  Digg

Kafka session.timeout.ms와 max.poll.interval.ms의 차이

 Jini     오후 10:52     kafka     No comments   

session.timeout.ms

컨슈머가 GroupCoordinator에게 해당시간내에 하트비트를 전송하지 않으면 리밸런싱이 일어난다. 컨슈머가 죽었으나 연결이 정상적으로 끊어지지 않는 상황이 발생하면 해당 값에 의해서 인지가 가능하다.
group.min.session.timeout.ms와 group.max.session.timeout.ms 범위 값만 설정가능
default : 10000(10초)

max.poll.interval.ms

다음 poll까지 대기하는 시간. 값이 300000ms인데 poll 내부로직이 300000ms가 넘어가는 경우 리밸런싱이 일어난다.
default : 300000(5분)

아래 코드 참고

@KafkaListener(topics = "xx", groupId = "yy")
public void kafkaListen(String message) throws InterruptedException {
    for (int x = 0; x < 10_000; x++) {
        Thread.sleep(15_000);
    }
}
컨슈머가 GroupCoordinator에게 하트비트는 전송하지만 실제로 아무일도 하지 않는 경우 컨슈머에 문제가 있는 것으로 판단하고 해당 파티션을 제외처리한다.

  • 카프카 0.10.1 버전부터 폴링 간에도 하트비트를 전송할 수 있도록 별도의 하트비트 스레드가 추가되었다고 알고 있음
  • 그래서 poll 내부로직이 오래 걸리더라도 하트비트는 계속 보내기 때문에 리밸런싱이 당연히 발생하지 않을 것으로 예상
  • 하지만 poll 내부로직이 오래 걸리자 리밸런싱이 발생함
  • 의아하게 생각하고 session.timeout.ms를 늘려봤지만 동일한 문제가 발생함
  • 원인을 찾다보니 max.poll.interval.ms 시간을 초과했기 때문이였음
  • poll 내부로직이 오래 걸리면 max.poll.interval.ms 시간을 늘리자
Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Google+
  •  Stumble
  •  Digg

Blog Template

 Jini     오후 10:30     blogspot     No comments   


  • 지금 적용중인 구글 블로그 Template
    • http://gooyaabitemplates.com/livepreview/the-funk/
  • 페이징 수정
  • stackedit로 작성한 글 변경


Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Google+
  •  Stumble
  •  Digg

구글 블로그 페이지 네비게이션 변경

 Jini     오후 9:24     blogspot     No comments   

3단계로 진행


1. CSS추가
2. Script 추가
3. Label 선택 페이지 설정

1. Adding The CSS

]]></b:skin> 상단에 css 붙여넣기.
아래는 현재 적용된 css. 다른 샘플은 출처페이지 참고

#blog-pager{clear:both;margin:30px auto;text-align:center; padding: 7px; }
.blog-pager {background: none;}
.displaypageNum a,.showpage a,.pagecurrent{font-size: 12px;padding: 5px 12px;margin-right:5px; color: #222; background-color:#eee; border: 1px solid #EEEEEE;}
.displaypageNum a:hover,.showpage a:hover, .pagecurrent{background:#E5E5E5;text-decoration:none;color: #222;}
#blog-pager .pagecurrent{font-weight:bold;color: #fff;background:#DB4920;}
.showpageOf{display:none!important}
#blog-pager .pages{border:none;}
"First", "Last" 버튼을 숨기고 싶은 경우 아래 코드를 추가한다.

.firstpage, .lastpage {display: none;}

2. Adding The Script

</body> 상단에 아래 script추가

<b:if cond='data:blog.pageType != &quot;item&quot;'>  
<b:if cond='data:blog.pageType != &quot;static_page&quot;'>  
<script type='text/javascript'>  
/*<![CDATA[*/  
var perPage=3;  
var numPages=3;  
var firstText ='First';  
var lastText ='Last';  
var prevText ='« Previous';  
var nextText ='Next »';  
var urlactivepage=location.href;  
var home_page="/";  
  
if(typeof firstText=="undefined")firstText="First";if(typeof lastText=="undefined")lastText="Last";var noPage;var currentPage;var currentPageNo;var postLabel;pagecurrentg();function looppagecurrentg(pageInfo){var html='';pageNumber=parseInt(numPages / 2);if(pageNumber==numPages-pageNumber){numPages=pageNumber*2+1}  
pageStart=currentPageNo-pageNumber;if(pageStart<1)pageStart=1;lastPageNo=parseInt(pageInfo / perPage)+1;if(lastPageNo-1==pageInfo / perPage)lastPageNo=lastPageNo-1;pageEnd=pageStart+numPages-1;if(pageEnd>lastPageNo)pageEnd=lastPageNo;html+="<span class='showpageOf'>Page "+currentPageNo+' of '+lastPageNo+"</span>";var prevNumber=parseInt(currentPageNo)-1;if(currentPageNo>1){if(currentPage=="page"){html+='<span class="showpage firstpage"><a href="'+home_page+'">'+firstText+'</a></span>'}else{html+='<span class="displaypageNum firstpage"><a href="/search/label/'+postLabel+'?&max-results='+perPage+'">'+firstText+'</a></span>'}}  
if(currentPageNo>2){if(currentPageNo==3){if(currentPage=="page"){html+='<span class="showpage"><a href="'+home_page+'">'+prevText+'</a></span>'}else{html+='<span class="displaypageNum"><a href="/search/label/'+postLabel+'?&max-results='+perPage+'">'+prevText+'</a></span>'}}else{if(currentPage=="page"){html+='<span class="displaypageNum"><a href="#" onclick="redirectpage('+prevNumber+');return false">'+prevText+'</a></span>'}else{html+='<span class="displaypageNum"><a href="#" onclick="redirectlabel('+prevNumber+');return false">'+prevText+'</a></span>'}}}  
if(pageStart>1){if(currentPage=="page"){html+='<span class="displaypageNum"><a href="'+home_page+'">1</a></span>'}else{html+='<span class="displaypageNum"><a href="/search/label/'+postLabel+'?&max-results='+perPage+'">1</a></span>'}}  
if(pageStart>2){html+=' ... '}  
for(var jj=pageStart;jj<=pageEnd;jj++){if(currentPageNo==jj){html+='<span class="pagecurrent">'+jj+'</span>'}else if(jj==1){if(currentPage=="page"){html+='<span class="displaypageNum"><a href="'+home_page+'">1</a></span>'}else{html+='<span class="displaypageNum"><a href="/search/label/'+postLabel+'?&max-results='+perPage+'">1</a></span>'}}else{if(currentPage=="page"){html+='<span class="displaypageNum"><a href="#" onclick="redirectpage('+jj+');return false">'+jj+'</a></span>'}else{html+='<span class="displaypageNum"><a href="#" onclick="redirectlabel('+jj+');return false">'+jj+'</a></span>'}}}  
if(pageEnd<lastPageNo-1){html+='...'}  
if(pageEnd<lastPageNo){if(currentPage=="page"){html+='<span class="displaypageNum"><a href="#" onclick="redirectpage('+lastPageNo+');return false">'+lastPageNo+'</a></span>'}else{html+='<span class="displaypageNum"><a href="#" onclick="redirectlabel('+lastPageNo+');return false">'+lastPageNo+'</a></span>'}}  
var nextnumber=parseInt(currentPageNo)+1;if(currentPageNo<(lastPageNo-1)){if(currentPage=="page"){html+='<span class="displaypageNum"><a href="#" onclick="redirectpage('+nextnumber+');return false">'+nextText+'</a></span>'}else{html+='<span class="displaypageNum"><a href="#" onclick="redirectlabel('+nextnumber+');return false">'+nextText+'</a></span>'}}  
if(currentPageNo<lastPageNo){if(currentPage=="page"){html+='<span class="displaypageNum lastpage"><a href="#" onclick="redirectpage('+lastPageNo+');return false">'+lastText+'</a></span>'}else{html+='<span class="displaypageNum lastpage"><a href="#" onclick="redirectlabel('+lastPageNo+');return false">'+lastText+'</a></span>'}}  
var pageArea=document.getElementsByName("pageArea");var blogPager=document.getElementById("blog-pager");for(var p=0;p<pageArea.length;p++){pageArea[p].innerHTML=html}  
if(pageArea&&pageArea.length>0){html=''}  
if(blogPager){blogPager.innerHTML=html}}  
function totalcountdata(root){var feed=root.feed;var totaldata=parseInt(feed.openSearch$totalResults.$t,10);looppagecurrentg(totaldata)}  
function pagecurrentg(){var thisUrl=urlactivepage;if(thisUrl.indexOf("/search/label/")!=-1){if(thisUrl.indexOf("?updated-max")!=-1){postLabel=thisUrl.substring(thisUrl.indexOf("/search/label/")+14,thisUrl.indexOf("?updated-max"))}else{postLabel=thisUrl.substring(thisUrl.indexOf("/search/label/")+14,thisUrl.indexOf("?&max"))}}  
if(thisUrl.indexOf("?q=")==-1&&thisUrl.indexOf(".html")==-1){if(thisUrl.indexOf("/search/label/")==-1){currentPage="page";if(urlactivepage.indexOf("#PageNo=")!=-1){currentPageNo=urlactivepage.substring(urlactivepage.indexOf("#PageNo=")+8,urlactivepage.length)}else{currentPageNo=1}  
document.write("<script src=\""+home_page+"feeds/posts/summary?max-results=1&alt=json-in-script&callback=totalcountdata\"><\/script>")}else{currentPage="label";if(thisUrl.indexOf("&max-results=")==-1){perPage=20}  
if(urlactivepage.indexOf("#PageNo=")!=-1){currentPageNo=urlactivepage.substring(urlactivepage.indexOf("#PageNo=")+8,urlactivepage.length)}else{currentPageNo=1}  
document.write('<script src="'+home_page+'feeds/posts/summary/-/'+postLabel+'?alt=json-in-script&callback=totalcountdata&max-results=1" ><\/script>')}}}  
function redirectpage(numberpage){jsonstart=(numberpage-1)*perPage;noPage=numberpage;var nameBody=document.getElementsByTagName('head')[0];var newInclude=document.createElement('script');newInclude.type='text/javascript';newInclude.setAttribute("src",home_page+"feeds/posts/summary?start-index="+jsonstart+"&max-results=1&alt=json-in-script&callback=finddatepost");nameBody.appendChild(newInclude)}  
function redirectlabel(numberpage){jsonstart=(numberpage-1)*perPage;noPage=numberpage;var nameBody=document.getElementsByTagName('head')[0];var newInclude=document.createElement('script');newInclude.type='text/javascript';newInclude.setAttribute("src",home_page+"feeds/posts/summary/-/"+postLabel+"?start-index="+jsonstart+"&max-results=1&alt=json-in-script&callback=finddatepost");nameBody.appendChild(newInclude)}  
function finddatepost(root){post=root.feed.entry[0];var timestamp1=post.published.$t.substring(0,19)+post.published.$t.substring(23,29);var timestamp=encodeURIComponent(timestamp1);if(currentPage=="page"){var pAddress="/search?updated-max="+timestamp+"&max-results="+perPage+"#PageNo="+noPage}else{var pAddress="/search/label/"+postLabel+"?updated-max="+timestamp+"&max-results="+perPage+"#PageNo="+noPage}  
location.href=pAddress}  
/*]]>*/  
</script>  
</b:if>  
</b:if>
perPage : 한 페이지당 노출할 post개수, 메인 페이지의 글수와 동일해야 한다.
numPages : 네비게이션 영역에 표시할 페이지 번호 범위(7인 경우 아래와 같이 표시된다.)

3. Labels fix

라벨 선택화면은 위에 적용한 페이지 네비게이션이 적용되지 않는다. 라벨 선택화면에도 페이지 네비게션을 적용하기 위해서는 아래 코드를

expr:href='data:label.url'
아래 코드로 변경한다.

expr:href='data:label.url + "?&max-results=7"'
max-results값은 라벨 선택 페이지에서 노출할 포스트의 개수

4. 추가

필자의 경우 페이징당 노출 포스팅 수를 7로 했는데도 불구하고 첫페이지가 계속 3개만 노출되는 문제가 발생했었다.
두 번째 페이지 부터는 정상적으로 7개씩 노출되었는데 이게 문제가 되는 것이 페이징이 꼬이다보니 노출되지 않는 글이 발생했다.
찾아보니 구글에서 페이지 최적화를 위해 내용이 긴 포스팅 있으면 자동으로 페이지당 노출 글자수를 줄인다고 한다.
그래서 내용이 긴 포스팅(기준은 모르겠다) 때문에 발생하는 문제라면 해당 포스팅에 점프브레이크()를 넣어서 해결하면 된다.


[참고링크]
https://helplogger.blogspot.com/2014/04/how-to-add-numbered-page-navigation-widget-for-blogger.html
Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Google+
  •  Stumble
  •  Digg

26 Reasons Why Using Optional Correctly Is Not Optional

 Jini     오후 2:04     JAVA, optional     No comments   

1. Never Assign Null to an Optional Variable

Avoid:


// AVOID
public Optional<Cart> fetchCart() {
 Optional<Cart> emptyCart = null;
 ...
}

Prefer:


// PREFER
public Optional<Cart> fetchCart() {
 Optional<Cart> emptyCart = Optional.empty();
 ...
}

Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Google+
  •  Stumble
  •  Digg

Why should one use Objects.requireNonNull()?

 Jini     오후 11:12     No comments   

public class Foo {
  private final Bar bar;

  public Foo(Bar bar) {
    Objects.requireNonNull(bar, "bar must not be null");
    this.bar = bar;
  }
}
The major advantages are:
  • as said, controlled behavior
  • easier debugging - because you throw up in the context of the object creation. At a point in time where you have a certain chance that your logs/traces tell you what went wrong!
  • and as shown above: the true power of this idea unfolds in conjunction with final fields. Because now any other code in your class can safely assume that bar isn’t null - and thus you do not need any if (bar == null) checks in other places!
개인적으로 세 번째가 가장 큰 장점이라 생각된다.

[출처]
Why should one use Objects.requireNonNull()?
Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Google+
  •  Stumble
  •  Digg

구글 Blog + Markdown(StackEdit)

 Jini     오후 9:24     markdown, stackedit     No comments   

구글 Blog + Markdown(StackEdit)

StackEdit

  • 구글 블로그는 글에 스타일을 적용하기 힘들다.
  • 쉽게 스타일을 적용하기 위해 Markdown 문법 적용을 고려함.
  • 방법을 찾던 중 StackEdit(https://stackedit.io)를 발견하고 활용하기로 결정
  • StackEdit를 선택한 이유는 작성한 글을 구글 블로그에 쉽게 Publish 할 수 있기 때문

Publish

  1. 구글 계정만 있으면 구글 블로그에 쉽게 Publish 할 수 있다.
  2. Publish 하는 방법은 간단해서 StackEdit 페이지를 방문 후 조금만 살펴보면 쉽게 연동이 가능하다.
  3. StackEdit 연동시 헷갈렸던 점은 아래와 같다.
    • StackEdit는 구글 블로그에 Plug-in 형태로 적용하는 Editor가 아니다. StackEdit는 작성한 글을 구글 블로그에 쉽게 Publish 해주는 기능을 제공하는 사이트이다.
    • 구글 블로그 테마중에서 StackEdit와 호환되지 않는 테마가 있다.

장점

  • Markdown 문법 사용이 가능하다.
  • 표는 물론이고 다이어그램, 복잡한 수학식도 작성할 수 있다.
  • 구글 블로그와 연동하기 쉽다.
  • 한 번 연동된 게시물은 계속적으로 Update가 가능하다.
  • 구글 블로그 뿐만 아니라 Dropbox, GitHub, WordPress, Zendesk와도 연동이 가능하다.
  • 별도 Plug-in을 설치하는게 아니라 사용법이 간단하다.
  • 글 라벨 등록도 가능하다.

단점

  • 구글 테마중에서 SlackEdit로 Publish 했을 때 충돌나는 테마가 있기 때문에 충돌나지 않는 테마만 사용해야 한다.
  • 블로그 전체적으로 “https://stackedit.io/style.css” 가 일괄적으로 적용되므로 구글블로그에서 설정한 값들이 적용되지 않을 수 있다.
  • 이미지 올리는 방식에 대한 확인 필요.

커스터마이징

  • StackEdit 적용 후 블로그에 적용된 "https://stackedit.io/style.css"로 인해 마음에 들지 않는 부분들 수정함.
  • [수정] 본문 font-size : 110% → 1.0em
     .post-body{
        font-size: 1.0em;
        line-height: 1.4;position: relative;
      }
    
  • [추가] 위젯의 font-size : 16px → 13px 로 변경
    div#sidebar-right-1 {
      font-size : 13px;
    }
    
  • [추가] 본문글의 margin-bottom : 180px → auto
    div.stackedit__html {
      margin-bottom: auto;
    }
    

참고사이트

  • Syntax 문법 : https://daringfireball.net/projects/markdown/syntax
  • StackEdit 설명문서 : https://stackedit.io/Welcome document.pdf
  • Code Syntax Highlighting 문법 : https://help.github.com/articles/creating-and-highlighting-code-blocks/

    Note: GitHub’s fenced code blocks are also supported with Highlight.js syntax highlighting:

Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Google+
  •  Stumble
  •  Digg

Git 명령어 모음

 Jini     오후 8:24     GIT     No comments   

■ Commit

  ▷ Commit History를 콘솔형 그래프로 보고자 할 경우 사용가능(--abbrev-commit는 해시ID를 약식으로)
git log --graph --pretty=oneline --abbrev-commit

  ▷ 병합 작업 중에 충돌이 발생한 파일에 영향을 주는 히스토리의 양쪽 파트에 속한 모든 커밋과 각 커밋에서 수행된 실제 변경 사항을 보여준다.
git log --merge --left-right -p [파일명 - optional] 
    · merge 옵션은 충돌이 발생한 파일과 관련된 커밋만 보여준다.
    · left-right 옵션은 커밋이 병합의 왼쪽에서 발생한 것이면 < 기호를 표시하고, 병합의 오른쪽에서 발생한 것이면 > 기호를 표시한다.
    · p옵션은 각 커밋과 관련된 커밋 메시지와 패치를 보여준다.
Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Google+
  •  Stumble
  •  Digg

New Features Java 1.7(JDK 1.7)

 Jini     오전 1:10     JAVA     No comments   

■ The New Objects Class
  1) Objects.requireNonNull(T), Objects.requireNonNull(T, String)
    #. 아래 Person 객체 생성시 newLastName이나 newFirstName에 null이 들어갈 경우 NullPointerException 발생.
    #. Not Null임을 보장하고 있으므로 해당 Property를 사용할 때 Null 검사를 생략할 수 있음.
public class Person {

    private String lastName;
    private String firstName;
 
    public Person(final String newLastName, final String newFirstName) {
        this.lastName = Objects.requireNonNull(newLastName, "Last name cannot be null.");
        this.firstName = Objects.requireNonNull(newFirstName, "First name cannot be null.");
    }
 
    public String getLastName() {
        return lastName;
    }
    
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
    
    public String getFirstName() {
        return firstName;
    }
    
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
}


  2) Objects.toString(Object), Objects.toString(Object, String)
    #. 인자가 하나인 메소드는 전달받은 Object가 Not null인 경우 Object의 toString() 결과를 리턴하며, null인 경우 "null"을 리턴한다.
    #. 인자가 둘인 메소드는 전달받은 Object가 Not null인 경우 Object의 toString() 결과를 리턴하며, null인 경우 전발받은 String 메시지를 리턴한다.
    #. toString() 메소드를 Override할 때 Null 체크를 별도로 하지 않아도 되는 편리함이 있다.
public class Bar {
    private Foo foo;
    private Bar parent;

    @Override
    public String toString(){
        return "Bar {foo = " + (foo == null ? "null" : foo.toString()) + ", parent = " + (parent == null ? "o parent, orphan" : parent.toString()) + "}";
    }
}
위 코드를 아래와 같이 수정할 수 있다.
public class Bar {
    private Foo foo;
    private Bar parent;

    @Override
    public String toString(){
        return "Bar {foo = " + Objects.toString(foo) + ", parent = " + Objects.toString(parent, "no parent, orphan") + "}";
    }
}
소스출처

  3) Objects.hash(Object...), Objects.hashCode(Object)
    #. Objects.hash(Object...) : 입력받은 모든 Object들에 대한 HashCode를 리턴한다.
    #. Objects.hashCode(Object) : Object가 null이면 "0"을 리턴, 그렇지 않으면 Object.hashCode()를 리턴한다.
public class Bar {
    private Foo foo;
    private Bar parent;

    @Override
    public int hashCode(){
        int result = 17;

        result = 31 * result + (foo == null ? 0 : foo.hashCode());
        result = 31 * result + (parent == null ? 0 : parent.hashCode());

        return result;
    }
}
위 코드를 아래와 같이 수정할 수 있다.
public class Bar {
    private Foo foo;
    private Bar parent;

    @Override
    public int hashCode(){
        return Objects.hash(foo, parent);
    }
}
소스출처

  4) Objects.equals(Object,Object), Objects.deepEquals(Object,Object)
    #. Objects.equals(Object,Object) : 두 입력값이 null이면 true를 null이 아니면 Object.equal(Object) 결과를 리턴한다.
    #. Objects.deepEquals(Object,Object) : 두 입력값이 Arrays Type인 것을 제외하고는 Objects.equals() 메소드와 동일하다.
public class Bar {
    private Foo foo;
    private Bar parent;

    @Override
    public boolean equals(Object obj){
        if (obj == this) {
            return true;
        } 

        if (obj instanceof Bar) {
            Bar other = (Bar) obj; 

            if (foo != other.foo) {
                if (foo == null || !foo.equals(other.foo)) {
                    return false;
                }
            } 

            if (parent != other.parent) {
                if (parent == null || !parent.equals(other.parent)) {
                    return false;
                }
            } 

            return true;
        } 

        return false;
    }
}
위 소스를 아래와 같이 변경할 수 있다.
public class Bar {
    private Foo foo;
    private Bar parent;

    @Override
    public boolean equals(Object obj){
        if (obj == this) {
            return true;
        } 

        if (obj instanceof Bar) {
            Bar other = (Bar) obj; 

            return Objects.equals(foo, other.foo) &amp;&amp; Objects.equals(parent, other.parent);
        } 

        return false;
    }
}
소스출처

  5) Objects.compare(T,T,Comparator c) : 입력받은 두 값이 null인 경우에도 정상동작하며, true를 리턴해준다. null이 아닌 경우에는 c.compare(T, T) 결과를 리턴한다.

■ Collections class에 아래 3개의 method 추가 : Iterator를 만들 때 Null인 경우 아래의 Method를 사용할 경우 Null 처리가 간편해짐
  1) Collections.emptyIterator
  2) Collections.emptyEnumeration
  3) Collections.emptyListIterator
import java.util.Collections;
import java.util.Iterator;
public class Main {

  public static void main(String[] args) {
  
  }
  public static Iterator getMy(){
    String nullFlag = null;
    if(nullFlag == null){
      return Collections.emptyIterator();
    }
    return null;
  }
}
소스출처

참고자료 Post

계속...

정리하기 전에 링크부터

http://www.rapidprogramming.com/technology/differences-between-java-16-java-17-jdk-16-jdk-17-934
http://radar.oreilly.com/2011/09/java7-features.html
http://marxsoftware.blogspot.kr/2011/03/jdk-7-new-interfaces-classes-enums-and.html
http://eclipse.or.kr/wiki/Java_1.7c
http://www.oracle.com/technetwork/java/javase/jdk7-relnotes-418459.html
http://en.wikipedia.org/wiki/Java_version_history#Java_SE_7_.28July_28.2C_2011.29
Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Google+
  •  Stumble
  •  Digg

Design Patterns

 Jini     오전 12:20     JAVA     No comments   



01. Iterator Pattern
왜 for문을 사용하지 않고 Iterator를 구현할까? 기존 구문 변경없이 loop 순환이 가능함. 즉 특정 구현에 의존하지 않을 수 있게 디자인이 가능함.


02. Adapter Pattern


03. Template Pattern


04. Factory Method Pattern


05. Singleton Pattern


06. Prototype Pattern


07. Builder Pattern


08. Abstract Factory Pattern


09. Bridge Pattern


10. Strategy Pattern


11. Composite Pattern


12. Decorator Pattern


13. Visitor Pattern


14. Chain of Responsibility Pattern


15. Facade Pattern


16. Mediator Pattern


17. Observer Pattern


18. Memento Pattern


19. State Pattern


20. Flyweight Pattern


21. Proxy Pattern
필요할 때 생성한다.


22. Command Pattern

Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Google+
  •  Stumble
  •  Digg

Redmine Text Format

 Jini     오후 1:22     Redmine     1 comment   

1. 글자색상 변경

%{color:red}dev%



2. 음영색상 변경

%{background:lightgreen} lightgreen%


3. 문단 넘버링(띄어쓰기 주의)

# 큰 제목
## 큰제목의 하위 제목
# 두번째 큰 제목
# 세번째 큰 제목

실제로는 다음과 같이 출력됨

1. 큰 제목
    1. 큰 제목의 하위 제목
2. 두번째 큰 제목
3. 세번째 큰 제목


4. 문단 단계화(띄어쓰기 주의)

* 큰 제목
** 큰제목의 하위 제목
* 두번째 큰 제목
* 세번째 큰 제목

Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Google+
  •  Stumble
  •  Digg

Visual Studio 2013 팁 및 단축키(이클립스랑 비교)

 Jini     오후 9:54     Visual Studio     1 comment   

이클립스에는 있는데 VS에는 없어서 있었으면 하는 기능 중 실제로 있었는데 내가 몰랐던 것들



1. 한 줄 삭제

이클립스는 ctrl + d / VS는 ctrl + shift + l


2. 한 줄 그대로 복사하여 아래로 붙여넣기(위로 붙여넣기는 모르겠다.)

이클립스는 ctrl + alt + ↓ / VS는 복사하고자 하는 라인에서 영역을 선택하지 않은 상태로 ctrl + c 후 다시 ctrl + v



이클립스에는 없는데 VS에는 있는 기능중 깜짝 놀랄만한 것.


1. 코드가 도구상자에 등록이 되고 도구상자에 있는 코드를 드래그 해서 다시 가져올 수 있음.

코드를 드래그 해서 도구상자에 넣으면 끝. 가져올 때도 일반 Control 가져오듯이 드래그해서 가져오면 됨.


그 외 단축키


1. Code Indent

이클립스는 ctrl + I / VS는 ctrl + k 후 ctrl + D


2. 대응하는 괄호 찾기

이클립스는 ctrl + shift + p / VS는 ctrl + [


3. 대소문자 변환

이클립스는 ctrl + shift + y, ctrl + shitf + x / VS는 ctrl + u , ctrl + shift + u


4. 창분할(단축키는 아님)

[세로창분할]
이클립스는 ctrl + "{" / VS는 창 → 새창 → 창 → 새 세로 탭 그룹

[가로창분할]
이클립스는 ctrl + "_" / VS는 창 → 분할

Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Google+
  •  Stumble
  •  Digg

윈도우(Window)에서 사용 포트 확인 및 프로세스 KILL

 Jini     오후 10:11     No comments   

1. 특정포트가 열려있는지 확인
- netstat -na | findstr "포트"

2. 열려 있는 포트의 PID 확인
- netstat -nao | findstr "포트"

3. PID 찾기
- tasklist | findstr "PID번호"

4. 프로세스 죽이기
- taskkill /f /pid "PID번호"
Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Google+
  •  Stumble
  •  Digg
최근 게시물 이전 게시물 홈

Popular Posts

  • Java 버전별 특징 및 변경사항
    1. Java7 • 타입추론 List<String> list = new ArrayList<>(); • 이진수 리터럴, 숫자 리터럴에 _ 지원 • Switch문 문자열 가능 • try-with-resources 문 : Au...
  • 윈도우(Window)에서 사용 포트 확인 및 프로세스 KILL
    1. 특정포트가 열려있는지 확인 - netstat -na | findstr "포트" 2. 열려 있는 포트의 PID 확인 - netstat -nao | findstr "포트" 3. PID 찾기 - task...
  • WebFlux Error Handling
    WebFlux Error Handling doOnError : 예외 발생시 특정행위를 수행한다. onErrorReturn : 예외 발생시 특정 값을 return 한다. onErrorResume : 예외 발생시 다른 Flux형태로 return 한다....
  • Git 명령어 모음
    ■ Commit   ▷ Commit History를 콘솔형 그래프로 보고자 할 경우 사용가능(--abbrev-commit는 해시ID를 약식으로) git log --graph --pretty=oneline --abbrev-commit   ▷ 병합...
  • Visual Studio 2013 팁 및 단축키(이클립스랑 비교)
    이클립스에는 있는데 VS에는 없어서 있었으면 하는 기능 중 실제로 있었는데 내가 몰랐던 것들 1. 한 줄 삭제 이클립스는 ctrl + d / VS는 ctrl + shift + l 2. 한 줄 그대로 복사하여 아래로 붙여넣기(위로 붙여넣기는 모...

Recent Posts

Recent Posts Widget

Blog Archive

  • ▼  2023 (4)
    • ▼  3월 (1)
      • 오브젝트 : 코드로 이해하는 객체지향 설계
    • ►  2월 (1)
    • ►  1월 (2)
  • ►  2022 (1)
    • ►  2월 (1)
  • ►  2020 (8)
    • ►  7월 (1)
    • ►  4월 (3)
    • ►  3월 (4)
  • ►  2018 (1)
    • ►  7월 (1)
  • ►  2015 (1)
    • ►  5월 (1)
  • ►  2014 (5)
    • ►  8월 (1)
    • ►  7월 (1)
    • ►  6월 (1)
    • ►  5월 (1)
    • ►  1월 (1)
  • ►  2013 (10)
    • ►  12월 (1)
    • ►  11월 (1)
    • ►  9월 (2)
    • ►  8월 (3)
    • ►  7월 (3)
  • ►  2012 (1)
    • ►  3월 (1)

Categories

  • 객체지향 (1)
  • 도서요약 (1)
  • 문법에서 문장까지 (2)
  • 일상 (1)
  • 자기계발 (1)
  • 책 (1)
  • 키보드 (1)
  • 키크론 (1)
  • blogspot (2)
  • error (1)
  • exception (1)
  • GIT (1)
  • JAVA (6)
  • JUNIT (1)
  • K8 PRO RGB (1)
  • kafka (1)
  • markdown (1)
  • mongodb (2)
  • mongotemplate (1)
  • optional (1)
  • Oracle (4)
  • Redmine (1)
  • spring (1)
  • stackedit (1)
  • troubleshooting (1)
  • Visual Studio (1)
  • Webflux (1)

Unordered List

Pages

  • 홈

Text Widget

Categories

Tags

Facebook

  • Home
  • Features
  • _Multi DropDown
  • __DropDown 1
  • __DropDown 2
  • __DropDown 3
  • _ShortCodes
  • _SiteMap
  • _Error Page
  • Documentation
  • Video Documentation
  • Download This Template

Footer Menu Widget

  • Home
  • About
  • Contact Us

Social Plugin

JINI. Powered by Blogger.

Copyright © Jini's Blog | Powered by Blogger
Design by Hardeep Asrani | Blogger Theme by NewBloggerThemes.com | Distributed By Gooyaabi Templates