diff --git a/README.md b/README.md deleted file mode 100644 index c58cc88..0000000 --- a/README.md +++ /dev/null @@ -1,27 +0,0 @@ -# Minimal Mistakes remote theme starter - -Click [**Use this template**](https://github.com/mmistakes/mm-github-pages-starter/generate) button above for the quickest method of getting started with the [Minimal Mistakes Jekyll theme](https://github.com/mmistakes/minimal-mistakes). - -Contains basic configuration to get you a site with: - -- Sample posts. -- Sample top navigation. -- Sample author sidebar with social links. -- Sample footer links. -- Paginated home page. -- Archive pages for posts grouped by year, category, and tag. -- Sample about page. -- Sample 404 page. -- Site wide search. - -Replace sample content with your own and [configure as necessary](https://mmistakes.github.io/minimal-mistakes/docs/configuration/). - ---- - -## Troubleshooting - -If you have a question about using Jekyll, start a discussion on the [Jekyll Forum](https://talk.jekyllrb.com/) or [StackOverflow](https://stackoverflow.com/questions/tagged/jekyll). Other resources: - -- [Ruby 101](https://jekyllrb.com/docs/ruby-101/) -- [Setting up a Jekyll site with GitHub Pages](https://jekyllrb.com/docs/github-pages/) -- [Configuring GitHub Metadata](https://github.com/jekyll/github-metadata/blob/master/docs/configuration.md#configuration) to work properly when developing locally and avoid `No GitHub API authentication could be found. Some fields may be missing or have incorrect data.` warnings. diff --git a/_config.yml b/_config.yml index 3da6a50..f6aa8c4 100644 --- a/_config.yml +++ b/_config.yml @@ -1,52 +1,214 @@ # Welcome to Jekyll! # -# This config file is meant for settings that affect your whole blog, values -# which you are expected to set up once and rarely edit after that. If you find -# yourself editing this file very often, consider using Jekyll's data files -# feature for the data you need to update frequently. -# +# This config file is meant for settings that affect your entire site, values +# which you are expected to set up once and rarely need to edit after that. # For technical reasons, this file is *NOT* reloaded automatically when you use -# 'bundle exec jekyll serve'. If you change this file, please restart the server process. - -# Site settings -# These are used to personalize your new site. If you look in the HTML files, -# you will see them accessed via {{ site.title }}, {{ site.email }}, and so on. -# You can create any custom variable you would like, and they will be accessible -# in the templates via {{ site.myvariable }}. -title: MM -email: -description: >- # this means to ignore newlines until "baseurl:" - Write an awesome description for your new site here. You can edit this - line in _config.yml. It will appear in your document head meta (for - Google search results) and in your feed.xml site description. -twitter_username: username -github_username: username -minimal_mistakes_skin: default -search: true - -# Build settings +# `jekyll serve`. If you change this file, please restart the server process. + +# Theme Settings +# +# Review documentation to determine if you should use `theme` or `remote_theme` +# https://mmistakes.github.io/minimal-mistakes/docs/quick-start-guide/#installing-the-theme + +remote_theme : "mmistakes/minimal-mistakes@4.24.0" +minimal_mistakes_skin : "contrast" #"default" , "aqua", "contrast", "dark", "dirt", "neon", "mint", "plum", "sunrise" + +# Site Settings +locale : "en-US" +title : "Kaiden Aura's blog" +title_separator : "-" +subtitle : # site tagline that appears below site title in masthead +name : "KaidenAura" +description : "Blog of KaidenAura, interested in programming, cats, Beatmania IIDX" +url : "https://kaidenAura.github.io" +baseurl : "/" +repository : # GitHub username/repo-name e.g. "mmistakes/minimal-mistakes" +teaser : # path of fallback teaser image, e.g. "/assets/images/500x300.png" +logo : # path of logo image to display in the masthead, e.g. "/assets/images/88x88.png" +masthead_title : # overrides the website title displayed in the masthead, use " " for no title +# breadcrumbs : false # true, false (default) +words_per_minute : 300 +comments: + provider : false #false (default), "disqus", "discourse", "facebook", "staticman", "staticman_v2", "utterances", "custom" + disqus: + shortname : # https://help.disqus.com/customer/portal/articles/466208-what-s-a-shortname- + discourse: + server : # https://meta.discourse.org/t/embedding-discourse-comments-via-javascript/31963 , e.g.: meta.discourse.org + facebook: + # https://developers.facebook.com/docs/plugins/comments + appid : + num_posts : # 5 (default) + colorscheme : # "light" (default), "dark" + utterances: + theme : # "github-light" (default), "github-dark" + issue_term : # "pathname" (default) + staticman: + branch : # "master" + endpoint : # "https://{your Staticman v3 API}/v3/entry/github/" +reCaptcha: + siteKey : + secret : +atom_feed: + path : # blank (default) uses feed.xml +search : # true, false (default) +search_full_content : # true, false (default) +search_provider : # lunr (default), algolia, google +algolia: + application_id : # YOUR_APPLICATION_ID + index_name : # YOUR_INDEX_NAME + search_only_api_key : # YOUR_SEARCH_ONLY_API_KEY + powered_by : # true (default), false +google: + search_engine_id : # YOUR_SEARCH_ENGINE_ID + instant_search : # false (default), true +# SEO Related +google_site_verification : +bing_site_verification : +yandex_site_verification : +naver_site_verification : + +# Social Sharing +#twitter: +# username : +#facebook: +# username : +# app_id : +# publisher : +#og_image : # Open Graph/Twitter default site image +# For specifying social profiles +# - https://developers.google.com/structured-data/customize/social-profiles +#social: +# type : # Person or Organization (defaults to Person) +# name : # If the user or organization name differs from the site's name +# links: # An array of links to social media profiles + +# Analytics +analytics: + provider : false # false (default), "google", "google-universal", "google-gtag", "custom" + google: + tracking_id : + anonymize_ip : # true, false (default) + + +# Site Author +author: + name : "kaidenAura" + avatar : # path of avatar image, e.g. "/assets/images/bio-photo.jpg" + bio : "API server developer" + location : "Seoul, S.Korea" + links: + # - label: "Email" + # icon: "fas fa-fw fa-envelope-square" + # url: "dublineryh@gmail.com" + # - label: "website" + # icon: "fas fa-fw fa-link" + # # url: "https://your-website.com" + # - label: "twitter" + # icon: "fab fa-fw fa-twitter-square" + # # url: "https://twitter.com/" + # - label: "facebook" + # icon: "fab fa-fw fa-facebook-square" + # # url: "https://facebook.com/" + - label: "github" + icon: "fab fa-fw fa-github" + url: "https://github.com/kaidenAura" +# - label: "instagram" +# icon: "fab fa-fw fa-instagram" +# # url: "https://instagram.com/" + +# Site Footer +footer: + links: +# - label: "Twitter" +# icon: "fab fa-fw fa-twitter-square" +# # url: +# - label: "Facebook" +# icon: "fab fa-fw fa-facebook-square" +# # url: +# - label: "GitHub" +# icon: "fab fa-fw fa-github" +# # url: +# - label: "GitLab" +# icon: "fab fa-fw fa-gitlab" +# # url: +# - label: "Bitbucket" +# icon: "fab fa-fw fa-bitbucket" +# # url: +# - label: "Instagram" +# icon: "fab fa-fw fa-instagram" +# # url: + + +# Reading Files +include: + - .htaccess + - _pages +exclude: + - "*.sublime-project" + - "*.sublime-workspace" + - vendor + - .asset-cache + - .bundle + - .jekyll-assets-cache + - .sass-cache + - assets/js/plugins + - assets/js/_main.js + - assets/js/vendor + - Capfile + - CHANGELOG + - config + - Gemfile + - Gruntfile.js + - gulpfile.js + - LICENSE + - log + - node_modules + - package.json + - package-lock.json + - Rakefile + - README + - tmp + - /docs # ignore Minimal Mistakes /docs + - /test # ignore Minimal Mistakes /test +keep_files: + - .git + - .svn +encoding: "utf-8" +markdown_ext: "markdown,mkdown,mkdn,mkd,md" + + +# Conversion markdown: kramdown -remote_theme: mmistakes/minimal-mistakes +highlighter: rouge +lsi: false +excerpt_separator: "\n\n" +incremental: false + + +# Markdown Processing +kramdown: + input: GFM + hard_wrap: false + auto_ids: true + footnote_nr: 1 + entity_output: as_char + toc_levels: 1..6 + smart_quotes: lsquo,rsquo,ldquo,rdquo + enable_coderay: false + + +# Sass/SCSS +sass: + sass_dir: _sass + style: compressed # https://sass-lang.com/documentation/file.SASS_REFERENCE.html#output_style + + # Outputting permalink: /:categories/:title/ paginate: 5 # amount of posts to show paginate_path: /page:num/ timezone: # https://en.wikipedia.org/wiki/List_of_tz_database_time_zones -include: - - _pages - -# Exclude from processing. -# The following items will not be processed, by default. Create a custom list -# to override the default setting. -# exclude: -# - Gemfile -# - Gemfile.lock -# - node_modules -# - vendor/bundle/ -# - vendor/cache/ -# - vendor/gems/ -# - vendor/ruby/ # Plugins (previously gems:) plugins: @@ -54,39 +216,55 @@ plugins: - jekyll-sitemap - jekyll-gist - jekyll-feed - - jemoji - jekyll-include-cache -author: - name : "First Lastname" - avatar : "/assets/images/bio-photo.jpg" - bio : "My awesome biography constrained to a sentence or two goes here." - links: - - label: "Website" - icon: "fas fa-fw fa-link" - url: "https://" - - label: "Twitter" - icon: "fab fa-fw fa-twitter-square" - url: "https://twitter.com/" - - label: "GitHub" - icon: "fab fa-fw fa-github" - url: "https://github.com/" - - label: "Instagram" - icon: "fab fa-fw fa-instagram" - url: "https://instagram.com/" +# mimic GitHub Pages with --safe +whitelist: + - jekyll-paginate + - jekyll-sitemap + - jekyll-gist + - jekyll-feed + - jekyll-include-cache -footer: - links: - - label: "Twitter" - icon: "fab fa-fw fa-twitter-square" - url: "https://twitter.com/" - - label: "GitHub" - icon: "fab fa-fw fa-github" - url: "https://github.com/" - - label: "Instagram" - icon: "fab fa-fw fa-instagram" - url: "https://instagram.com/" +# Archives +# Type +# - GitHub Pages compatible archive pages built with Liquid ~> type: liquid (default) +# - Jekyll Archives plugin archive pages ~> type: jekyll-archives +# Path (examples) +# - Archive page should exist at path when using Liquid method or you can +# expect broken links (especially with breadcrumbs enabled) +# - /tags/my-awesome-tag/index.html ~> path: /tags/ +# - /categories/my-awesome-category/index.html ~> path: /categories/ +# - /my-awesome-category/index.html ~> path: / +category_archive: + type: liquid + path: /categories/ +tag_archive: + type: liquid + path: /tags/ +# https://github.com/jekyll/jekyll-archives +# jekyll-archives: +# enabled: +# - categories +# - tags +# layouts: +# category: archive-taxonomy +# tag: archive-taxonomy +# permalinks: +# category: /categories/:name/ +# tag: /tags/:name/ + + +# HTML Compression +# - https://jch.penibelst.de/ +compress_html: + clippings: all + ignore: + envs: development + + +# Defaults defaults: # _posts - scope: @@ -97,19 +275,5 @@ defaults: author_profile: true read_time: true comments: true - share: true - related: true - # _pages - - scope: - path: "_pages" - type: pages - values: - layout: single - author_profile: true - -category_archive: - type: liquid - path: /categories/ -tag_archive: - type: liquid - path: /tags/ + share: false + related: true \ No newline at end of file diff --git a/_posts/2010-01-07-post-modified.md b/_posts/2010-01-07-post-modified.md deleted file mode 100644 index c09d324..0000000 --- a/_posts/2010-01-07-post-modified.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -title: "Post: Modified Date" -last_modified_at: 2016-03-09T16:20:02-05:00 -categories: - - Blog -tags: - - Post Formats - - readability - - standard ---- - -This post has been updated and should show a modified date if used in a layout. - -All children, except one, grow up. They soon know that they will grow up, and the way Wendy knew was this. One day when she was two years old she was playing in a garden, and she plucked another flower and ran with it to her mother. I suppose she must have looked rather delightful, for Mrs. Darling put her hand to her heart and cried, "Oh, why can't you remain like this for ever!" This was all that passed between them on the subject, but henceforth Wendy knew that she must grow up. You always know after you are two. Two is the beginning of the end. \ No newline at end of file diff --git a/_posts/2010-01-07-post-standard.md b/_posts/2010-01-07-post-standard.md deleted file mode 100644 index fc25616..0000000 --- a/_posts/2010-01-07-post-standard.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -title: "Post: Standard" -excerpt_separator: "" -categories: - - Blog -tags: - - Post Formats - - readability - - standard ---- - -All children, except one, grow up. They soon know that they will grow up, and the way Wendy knew was this. One day when she was two years old she was playing in a garden, and she plucked another flower and ran with it to her mother. I suppose she must have looked rather delightful, for Mrs. Darling put her hand to her heart and cried, "Oh, why can't you remain like this for ever!" This was all that passed between them on the subject, but henceforth Wendy knew that she must grow up. You always know after you are two. Two is the beginning of the end. - -Mrs. Darling first heard of Peter when she was tidying up her children's minds. It is the nightly custom of every good mother after her children are asleep to rummage in their minds and put things straight for next morning, repacking into their proper places the many articles that have wandered during the day. - - - -This post has a manual excerpt `` set after the second paragraph. The following YAML Front Matter has also be applied: - -```yaml -excerpt_separator: "" -``` - -If you could keep awake (but of course you can't) you would see your own mother doing this, and you would find it very interesting to watch her. It is quite like tidying up drawers. You would see her on her knees, I expect, lingering humorously over some of your contents, wondering where on earth you had picked this thing up, making discoveries sweet and not so sweet, pressing this to her cheek as if it were as nice as a kitten, and hurriedly stowing that out of sight. When you wake in the morning, the naughtiness and evil passions with which you went to bed have been folded up small and placed at the bottom of your mind and on the top, beautifully aired, are spread out your prettier thoughts, ready for you to put on. - -I don't know whether you have ever seen a map of a person's mind. Doctors sometimes draw maps of other parts of you, and your own map can become intensely interesting, but catch them trying to draw a map of a child's mind, which is not only confused, but keeps going round all the time. There are zigzag lines on it, just like your temperature on a card, and these are probably roads in the island, for the Neverland is always more or less an island, with astonishing splashes of colour here and there, and coral reefs and rakish-looking craft in the offing, and savages and lonely lairs, and gnomes who are mostly tailors, and caves through which a river runs, and princes with six elder brothers, and a hut fast going to decay, and one very small old lady with a hooked nose. It would be an easy map if that were all, but there is also first day at school, religion, fathers, the round pond, needle-work, murders, hangings, verbs that take the dative, chocolate pudding day, getting into braces, say ninety-nine, three-pence for pulling out your tooth yourself, and so on, and either these are part of the island or they are another map showing through, and it is all rather confusing, especially as nothing will stand still. - -Of course the Neverlands vary a good deal. John's, for instance, had a lagoon with flamingoes flying over it at which John was shooting, while Michael, who was very small, had a flamingo with lagoons flying over it. John lived in a boat turned upside down on the sands, Michael in a wigwam, Wendy in a house of leaves deftly sewn together. John had no friends, Michael had friends at night, Wendy had a pet wolf forsaken by its parents, but on the whole the Neverlands have a family resemblance, and if they stood still in a row you could say of them that they have each other's nose, and so forth. On these magic shores children at play are for ever beaching their coracles [simple boat]. We too have been there; we can still hear the sound of the surf, though we shall land no more. - -Of all delectable islands the Neverland is the snuggest and most compact, not large and sprawly, you know, with tedious distances between one adventure and another, but nicely crammed. When you play at it by day with the chairs and table-cloth, it is not in the least alarming, but in the two minutes before you go to sleep it becomes very real. That is why there are night-lights. - -Occasionally in her travels through her children's minds Mrs. Darling found things she could not understand, and of these quite the most perplexing was the word Peter. She knew of no Peter, and yet he was here and there in John and Michael's minds, while Wendy's began to be scrawled all over with him. The name stood out in bolder letters than any of the other words, and as Mrs. Darling gazed she felt that it had an oddly cocky appearance. \ No newline at end of file diff --git a/_posts/2010-01-08-post-chat.md b/_posts/2010-01-08-post-chat.md deleted file mode 100644 index 9092634..0000000 --- a/_posts/2010-01-08-post-chat.md +++ /dev/null @@ -1,134 +0,0 @@ ---- -title: "Post: Chat" -categories: - - Blog -tags: - - chat - - Post Formats ---- - -Abbott: Strange as it may seem, they give ball players nowadays very peculiar names. - -Costello: Funny names? - -Abbott: Nicknames, nicknames. Now, on the St. Louis team we have Who's on first, What's on second, I Don't Know is on third-- - -Costello: That's what I want to find out. I want you to tell me the names of the fellows on the St. Louis team. - -Abbott: I'm telling you. Who's on first, What's on second, I Don't Know is on third-- - -Costello: You know the fellows' names? - -Abbott: Yes. - -Costello: Well, then who's playing first? - -Abbott: Yes. - -Costello: I mean the fellow's name on first base. - -Abbott: Who. - -Costello: The fellow playin' first base. - -Abbott: Who. - -Costello: The guy on first base. - -Abbott: Who is on first. - -Costello: Well, what are you askin' me for? - -Abbott: I'm not asking you--I'm telling you. Who is on first. - -Costello: I'm asking you--who's on first? - -Abbott: That's the man's name. - -Costello: That's who's name? - -Abbott: Yes. - -Costello: When you pay off the first baseman every month, who gets the money? - -Abbott: Every dollar of it. And why not, the man's entitled to it. - -Costello: Who is? - -Abbott: Yes. - -Costello: So who gets it? - -Abbott: Why shouldn't he? Sometimes his wife comes down and collects it. - -Costello: Who's wife? - -Abbott: Yes. After all, the man earns it. - -Costello: Who does? - -Abbott: Absolutely. - -Costello: Well, all I'm trying to find out is what's the guy's name on first base? - -Abbott: Oh, no, no. What is on second base. - -Costello: I'm not asking you who's on second. - -Abbott: Who's on first! - -Costello: St. Louis has a good outfield? - -Abbott: Oh, absolutely. - -Costello: The left fielder's name? - -Abbott: Why. - -Costello: I don't know, I just thought I'd ask. - -Abbott: Well, I just thought I'd tell you. - -Costello: Then tell me who's playing left field? - -Abbott: Who's playing first. - -Costello: Stay out of the infield! The left fielder's name? - -Abbott: Why. - -Costello: Because. - -Abbott: Oh, he's center field. - -Costello: Wait a minute. You got a pitcher on this team? - -Abbott: Wouldn't this be a fine team without a pitcher? - -Costello: Tell me the pitcher's name. - -Abbott: Tomorrow. - -Costello: Now, when the guy at bat bunts the ball--me being a good catcher--I want to throw the guy out at first base, so I pick up the ball and throw it to who? - -Abbott: Now, that's he first thing you've said right. - -Costello: I DON'T EVEN KNOW WHAT I'M TALKING ABOUT! - -Abbott: Don't get excited. Take it easy. - -Costello: I throw the ball to first base, whoever it is grabs the ball, so the guy runs to second. Who picks up the ball and throws it to what. What throws it to I don't know. I don't know throws it back to tomorrow--a triple play. - -Abbott: Yeah, it could be. - -Costello: Another guy gets up and it's a long ball to center. - -Abbott: Because. - -Costello: Why? I don't know. And I don't care. - -Abbott: What was that? - -Costello: I said, I DON'T CARE! - -Abbott: Oh, that's our shortstop! \ No newline at end of file diff --git a/_posts/2010-02-05-post-notice.md b/_posts/2010-02-05-post-notice.md deleted file mode 100644 index 392f2cd..0000000 --- a/_posts/2010-02-05-post-notice.md +++ /dev/null @@ -1,68 +0,0 @@ ---- -title: "Post: Notice" -categories: - - Blog -tags: - - Post Formats - - notice ---- - -A notice displays information that explains nearby content. Often used to call attention to a particular detail. - -When using Kramdown `{: .notice}` can be added after a sentence to assign the `.notice` to the `

` element. - -**Changes in Service:** We just updated our [privacy policy](#) here to better service our customers. We recommend reviewing the changes. -{: .notice} - -**Primary Notice:** Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. [Praesent libero](#). Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. -{: .notice--primary} - -**Info Notice:** Lorem ipsum dolor sit amet, [consectetur adipiscing elit](#). Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. -{: .notice--info} - -**Warning Notice:** Lorem ipsum dolor sit amet, consectetur adipiscing elit. [Integer nec odio](#). Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. -{: .notice--warning} - -**Danger Notice:** Lorem ipsum dolor sit amet, [consectetur adipiscing](#) elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. -{: .notice--danger} - -**Success Notice:** Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at [nibh elementum](#) imperdiet. -{: .notice--success} - -Want to wrap several paragraphs or other elements in a notice? Using Liquid to capture the content and then filter it with `markdownify` is a good way to go. - -```html -{% raw %}{% capture notice-2 %} -#### New Site Features - -* You can now have cover images on blog pages -* Drafts will now auto-save while writing -{% endcapture %}{% endraw %} - -
{% raw %}{{ notice-2 | markdownify }}{% endraw %}
-``` - -{% capture notice-2 %} -#### New Site Features - -* You can now have cover images on blog pages -* Drafts will now auto-save while writing -{% endcapture %} - -
- {{ notice-2 | markdownify }} -
- -Or you could skip the capture and stick with straight HTML. - -```html -
-

Message

-

A basic message.

-
-``` - -
-

Message

-

A basic message.

-
\ No newline at end of file diff --git a/_posts/2010-02-05-post-quote.md b/_posts/2010-02-05-post-quote.md deleted file mode 100644 index fda06e9..0000000 --- a/_posts/2010-02-05-post-quote.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -title: "Post: Quote" -categories: - - Blog -tags: - - Post Formats - - quote ---- - -> Only one thing is impossible for God: To find any sense in any copyright law on the planet. - -> Mark Twain \ No newline at end of file diff --git a/_posts/2010-03-07-post-link.md b/_posts/2010-03-07-post-link.md deleted file mode 100644 index 6d99180..0000000 --- a/_posts/2010-03-07-post-link.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -title: "Post: Link" -categories: - - Blog -tags: - - link - - Post Formats -link: https://github.com ---- - -This theme supports **link posts**, made famous by John Gruber. To use, just add `link: http://url-you-want-linked` to the post's YAML front matter and you're done. - -> And this is how a quote looks. - -Some [link](#) can also be shown. \ No newline at end of file diff --git a/_posts/2019-04-18-welcome-to-jekyll.md b/_posts/2019-04-18-welcome-to-jekyll.md deleted file mode 100644 index 30fdbf3..0000000 --- a/_posts/2019-04-18-welcome-to-jekyll.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -title: "Welcome to Jekyll!" -date: 2019-04-18T15:34:30-04:00 -categories: - - blog -tags: - - Jekyll - - update ---- - -You'll find this post in your `_posts` directory. Go ahead and edit it and re-build the site to see your changes. You can rebuild the site in many different ways, but the most common way is to run `jekyll serve`, which launches a web server and auto-regenerates your site when a file is updated. - -To add new posts, simply add a file in the `_posts` directory that follows the convention `YYYY-MM-DD-name-of-post.ext` and includes the necessary front matter. Take a look at the source for this post to get an idea about how it works. - -Jekyll also offers powerful support for code snippets: - -```ruby -def print_hi(name) - puts "Hi, #{name}" -end -print_hi('Tom') -#=> prints 'Hi, Tom' to STDOUT. -``` - -Check out the [Jekyll docs][jekyll-docs] for more info on how to get the most out of Jekyll. File all bugs/feature requests at [Jekyll’s GitHub repo][jekyll-gh]. If you have questions, you can ask them on [Jekyll Talk][jekyll-talk]. - -[jekyll-docs]: https://jekyllrb.com/docs/home -[jekyll-gh]: https://github.com/jekyll/jekyll -[jekyll-talk]: https://talk.jekyllrb.com/ diff --git a/_posts/2020-10-24-Exception-Handling.md b/_posts/2020-10-24-Exception-Handling.md new file mode 100644 index 0000000..399b39a --- /dev/null +++ b/_posts/2020-10-24-Exception-Handling.md @@ -0,0 +1,158 @@ +--- +title: "Spring Boot로 Exception 정의하기" +date: 2020-10-24 15:20:00 +0900 +categories: Spring boot +--- + +REST API에서는 에러가 발생할 경우 404 같은 일반적인 status code가 아닌, 서버 쪽에서 정의한 error code를 별도로 리턴하는 경우가 있다. 서버쪽에서 error code를 별도로 정의하면 예를 들어 같은 404 에러라도 세분화하여 에러를 구분할 수 있고, 클라이언트 측에서도 상황별로 에러 처리를 하기가 편해진다. + +보통은 @ExceptionHandler 및 @ControllerAdvice를 사용하는 듯 하지만, 이번에는 ErrorAttribute를 재정의하는 방법을 사용해본다. + +## DefaultErrorAttributes +Spring boot 기본 설정에서는 @RestController에서 예외가 발생하면 클라이언트에게 다음과 같은 형식으로 기본 error response가 전달된다.
+ +```json +{ + "timestamp": "2020-10-20T11:57:49.947+00:00", + "status": 404, + "error": "Not found", + "message": "blahblah", + "path": "/user/abc" +} +``` + +이 에러형식은 ErrorAttribute를 구현한 DefaultErrorAttributes를 따른다. 따라서 이 DefaultErrorAttribute를 상속하여 구현하고 컴포넌트로 등록해주면 다음과 같이 error response를 커스텀할 수 있다. + +```json +{ + "errorCode": "TEST-40401", + "message": "No such user - user" +} +``` + +## Exception을 상속받는 커스텀 Exception 클래스 정의 +DefaultErrorAttributes를 정의하기 전에 먼저 커스텀 Exception을 먼저 정의하자. Exception을 상속받은 커스텀 클래스를 만들면 된다. + +```java +package com.expyh.exceptiontest.exception; + +public class CustomException extends Exception{ + CustomError error; + String message; + + public CustomException(CustomError error, String... args) { + this.error = error; + if (args.length > 0){ + this.message = String.format(error.getMessage(), args); + } + else { + this.message = error.getMessage(); + } + } + + public CustomError getError() { + return error; + } + + public void setError(CustomError error) { + this.error = error; + } + + @Override + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } +} +``` + +이제 CustomException을 던질 때 매개변수로 넣어줄 Error를 정의하자. Error는 enum 형태로 정의하면 새로운 error code를 정의할 때나 Exception을 던질 때 편하다. + +```java +public enum CustomError{ + //java의 에러가 아닌 erorCode로 표현되는 에러를 표현하는 것임. + + NO_SUCH_USER("TEST-40401", HttpStatus.BAD_REQUEST.value(), "No such user - %s"), + + String errorCode; + int status; + String message; + + CustomError(String errorCode, int status, String message) { + this.errorCode = errorCode; + this.status = status; + this.message = message; + } + + public String getErrorCode() { + return errorCode; + } + + public void setErrorCode(String errorCode) { + this.errorCode = errorCode; + } + + public int getStatus() { + return status; + } + + public void setStatus(int status) { + this.status = status; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } +} +``` + +이제 모든 준비가 끝났고 DefaultErrorAttributes만 재정의해주면 된다. getErrorAttributes()를 이용해서 error response의 항목들을 가져온 다음 여기에 새로 추가하거나 제거함으로써 error response를 조정할 수 있다. + +원하는 로직을 적용하고 @Component를 이용해서 등록해준다. +```java +@Component +public class CustomErrorAttribute extends DefaultErrorAttributes { + + @Override + public Map getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) { + Map errorAttributes = super.getErrorAttributes(webRequest, options); + Throwable error = super.getError(webRequest); + if (error instanceof CustomException) { + errorAttributes.put("errorCode", ((CustomException) error).getError().getErrorCode() ); + } + else { + errorAttributes.put("errorCode", "TEST-50001"); + } + errorAttributes.remove("timestamp"); + errorAttributes.remove("status"); + errorAttributes.remove("error"); + errorAttributes.remove("path"); + + return errorAttributes; + } + +} +``` + +실제 컨트롤러에서 Exception을 던질 때는 CustomException의 매개변수로 enum으로 선언한 CustomError를 넣어주면 된다. 이렇게 되면 CustomErrorAttribute가 error response를 조작하여 우리가 원하는 형태로 클라이언트에게 전달해준다. + +```java +@RestController +public class HelloController { + List users = Arrays.asList("expyh"); + @RequestMapping("/test/{user}") + public String index(@PathVariable("user") String user) throws CustomException { + if (!users.contains(user)){ + throw new CustomException(CustomError.NO_SUCH_USER, user); + } + return "Found : " + user ; + } +} +``` \ No newline at end of file diff --git "a/_posts/2020-12-07-Main\355\225\250\354\210\230-Overinding&Overloading.md" "b/_posts/2020-12-07-Main\355\225\250\354\210\230-Overinding&Overloading.md" new file mode 100644 index 0000000..c330ae6 --- /dev/null +++ "b/_posts/2020-12-07-Main\355\225\250\354\210\230-Overinding&Overloading.md" @@ -0,0 +1,113 @@ +--- +title: "Main함수의 Overriding / Overloading" +date: 2020-12-07 15:00:00 +0900 +categories: java +--- + +main함수는 main함수는 java 프로그램의 시작이자 끝의 역할을 하고 있어 다른 함수들과는 다른 취급을 받을거라는 인상을 준다. + +```java +public class Main { + public static void main(String[] args){ + System.out.println("Hello World"); + } +} +``` +
+그렇다면 이 함수는 Overriding / Overloading이 될까? +
+ +# 1) Overriding + +당연히 안된다. 이유는 main 메소드는 static이고 static 메소드는 overriding이 안되기 때문이다. + +```java +//실패 +public class ChildMain extends Main{ + //Method does not override method from its superclass + @Override + public static void main(String[] args) { + System.out.println("hello world2"); + } +} +``` +
+static 메소드가 overriding이 안되는 이유는 다음과 같다 +- static 메소드는 클래스가 어떤 메소드를 호출할 지 runtime이 아닌 compile 타임에 결정하기 때문에 overriding할 수 없음. +- static 메소드는 애초에 자식 클래스에 상속하는 메소드가 아님(!) + +[//]: # (Hello) +
+ +그래서 아래같이 @Override를 빼면 컴파일 에러는 나지 않는다. 다만 Main의 main 메소드와 ChildMain의 main 메소드는 관련이 전혀 없는 아예 별개의 메소드이다. super(); 로 부모 클래스의 메소드를 호출하는 것도 물론 불가능하다. + +
+ +```java +public class ChildMain extends Main{ + + //실패 + /* + public static void main(String[] args) { + super(); + } + */ + + //성공 + public static void main(String[] args) { + System.out.println("hello world2"); + } +} +``` +
+ +# 2) Overloading + +overloading은 의외로 가능하다. 하지만 프로그램 실행 시에는 String[] args 를 매개변수로 가지는 main 메소드가 동작하게 된다. +
+ +```java +public class Main { + public static void main(String[] args) { + System.out.println("hello world"); + } + + public static void main(int[] args) { + System.out.println("hello world"); + } + + public static void main(double[] args) { + System.out.println("hello world"); + } +} +``` +
+굳이 다른 main 메소드를 쓰고 싶다면 다음과 같이 main(String[] args) 안에서 호출하는 수밖에 없다. +
+ +```java +public class Main { + public static void main(String[] args) { + int[] integersArray = new int[args.length]; + for (int i = 0 ; i < args.length ; ++i){ + integersArray[i] = Integer.parseInt(args[i]); + } + main(integersArray); + } + + public static void main(int[] args) { + for (int arg : args) + System.out.println(arg); + } +} +``` +
+ +# 결론 +- main 메소드는 사실상 다른 메소드와 동일한 규칙을 따른다. +- main 메소드는 static이기 때문에 @Override가 먹히지 않는다. +- main 메소드는 Overloading이 되지만 굳이 그럴 이유는 없어보인다 +
+
+ +지식이 늘었다. diff --git "a/_posts/2021-05-19-Heap\354\227\220\354\204\234-\354\244\221\352\260\204-\354\233\220\354\206\214-\354\202\255\354\240\234\355\225\230\352\270\260.md" "b/_posts/2021-05-19-Heap\354\227\220\354\204\234-\354\244\221\352\260\204-\354\233\220\354\206\214-\354\202\255\354\240\234\355\225\230\352\270\260.md" new file mode 100644 index 0000000..0ac60a4 --- /dev/null +++ "b/_posts/2021-05-19-Heap\354\227\220\354\204\234-\354\244\221\352\260\204-\354\233\220\354\206\214-\354\202\255\354\240\234\355\225\230\352\270\260.md" @@ -0,0 +1,100 @@ +--- +title: "Heap에서 중간 원소 삭제하기" +date: 2021-05-19 12:00:00 +0900 +categories: Data Structure +--- + +요즘 삼성전자 SW 역량테스트 B급을 공부할 일이 생겼다. (https://swexpertacademy.com/main/sst/intro.do) 삼성에 3급 공채로 입사할 때 보는 시험은 A급이라고 보면 되고, 삼성 입사 후에 추가로 B급까지 가거나 욕심이 더 있으신 분들은 C급까지 가는 시험이다. + +요즘 역량테스트 B급의 트렌드는 인덱싱(Trie 또는 Hashing) + 우선순위 탐색 (Heap 등)이다. 우선순위 하면 Heap이 먼저 떠오르기야 하지만, SW 역량테스트의 경우 자료구조를 직접 구현해야하는 귀찮음이 있기 때문에 Heap 사용하는 것을 지양하는 편이었다. + +그러나 결국 Heap을 구현할 일이 생겨버렸다. 코드가 잘 기억이 나지 않아서 인터넷의 예시 코드들을 가져다 썼는데, 이 코드들은 삭제 / 삽입을 반복하면서 우선순위를 제대로 유지하지 못했고 디버깅에만 6시간 넘는 시간을 소비하게 만들었다. 확인 결과 예시 코드의 단순함 + Heapify 작업에 대한 미숙한 이해 이렇게 두 가지 이유가 있었고, 이번 기회에 정리하고 넘어가야 겠다는 생각이 들었다. + +
+ +## Heap이란 + +완전 이진트리의 일종으로, 우선순위 큐를 구현할 때 사용하는 자료구조이다. 루트 노드가 우선순위가 가장 높다는 것은 보장되지만, 모든 원소가 완전히 정렬된 상태는 아니다. 다만 아래와 같은 성질이 성립한다. + +> A가 B의 부모노드(parent node) 이면, A의 키(key)값과 B의 키값 사이에는 대소관계가 성립한다.
+ +## Heapify란? + +Heap에 원소를 삽입하거나 빼낸 후, 위에서 언급한 성질을 만족하도록 Heap 내 원소들을 재정렬하는 작업이다. 대부분의 예제코드에서 이 작업을 Heapify()라는 함수에 넣어둔다. + +Heap에서는 루트 노드가 가장 우선순위가 높은 원소를 가지고 있기 때문에, Heap의 사용 예시나 코드들도 이 부분에 초점이 맞춰져 있다. 여기서 일반적인 Heapify 코드들의 문제가 발생한다. **일반적인 Heap의 삭제는 루트 노드에 대해서만 이뤄지고, 예제 코드들의 Heapify() 루트 노드를 기준으로 자식 노드들에 대해서만 비교하도록 작성된다는 것이다.**
+ +* 1) Push 이후의 Heapify + +원소를 Heap에 삽입하는걸 Push라고 한다. 다음과 같이 이뤄진다. + +> Heap의 가장 마지막 노드 뒤에다가 원소를 넣는다. +> +> Heap의 size를 1 증기사킨다. +> +> 해당 노드에 대해서, 아래 과정을 진행한다. +> +>1 ) 자신(a)의 부모 노드(p)를 찾는다. +> +>2-1) p가 존재하지 않는다면 이 과정을 종료한다. +> +>2-2) p의 우선순위가 가장 높다면 이 과정을 종료한다. +> +>2-3) a의 우선순위가 높다면, p와 a의 자리를 바꾼다. 1)로 돌아간다. +> + +맨 마지막 자리로 원소가 들어간 후, 부모 노드와 비교해가며 자신의 자리를 찾아가는 과정이다.
+ +* 2) Pop 이후의 Heapify + +Heap에서 원소를 삭제한다고 하면 보통 Pop이다. Heap에서 루트 노드의 값을 빼오는 것을 Pop이라 한다. Pop이 되면 다음과 같이 Heapify를 진행한다. + +> Heap의 가장 마지막 원소를 루트 노드에다가 가져온다. +> +> Heap의 size를 1 줄인다. +> +> 루트노드부터 아래 과정을 진행한다. +> +> 1 ) 자신(a)의 우선순위를 자신의 왼쪽 자식 노드(l)와 자신의 오른쪽 자식 노드(r)가 있는지 확인한다. l 또는 r이 존재한다면, 둘과 a의 우선순위를 비교한다. +> +> 2-2) a의 우선순위가 가장 높다면 이 과정을 끝낸다. +> +> 2-3) l 또는 r이 a보다 우선순위가 높다면, 둘 중 우선순위가 더 높은 노드와 a의 위치를 바꾼다. 바뀐 두 노드 중 자식 노드가 새로운 a가 된다. 1)로 돌아간다. +> + +위 Heapify의 과정을 보면 자신과 자신의 자식 노드에 대해서만 재귀적으로 이뤄진다는 것을 알 수 있다. 하지만 이 Heapify를 중간에 삭제된 노드에 대해서도 똑같이 적용하면 오류가 발생한다.
+ + +## 내가 겪은 상황 +아래는 내가 실제로 구현한 Heap의 구조이다. Key와 우선순위로 이뤄져 있는데, 우선순위가 높은 순서대로, 우선순위가 같다면 Key값이 높은 순서대로 정렬되어야한다. + +![image1](https://github.com/EXPYH/expyh.github.io/blob/gh-pages/_posts/images/210519/210519-01.PNG?raw=true) + +지금 이 상황에서는 Pop을 차례대로 하다보면 우선순위가 높은 순서대로 원소들이 나온다. 문제는 Key : 72인 노드 삭제 이후에 발생했다. + +여기서 자식 노드에 대해서만 Heapify를 진행했더니 아래와 같이 Key : 39인 노드가 자리 배치가 제대로 이뤄지지 않았다. 이 때문에 Pop을 여러번 진행할 때 Key : 8 이후에 반환되어야 하는 Key : 39 대신 엉뚱한 Key가 반환 되었다. + +![image2](https://github.com/EXPYH/expyh.github.io/blob/gh-pages/_posts/images/210519/210519-02.PNG?raw=true) + +
+ +## 임의 원소 삭제 이후의 Heapify +위 문제의 원인은 노드의 삭제를 단순히 Pop과 동일시하여 Pop heapify만 진행한 것이다. + +루트 노드가 아닌 곳에서 삭제가 이뤄진 경우 다음과 같이 진행해야 한다. + +> Heap의 마지막 노드에 존재하는 원소를 삭제한 노드로 옮긴다. +> +> Heap의 size를 1 줄인다. +> +> 삭제했던 노드 위치를 루트 노드로 하는 Pop Heapify를 진행한다. +> +> 삭제했던 노드 위치를 시작으로 Push Heapify를 진행한다. + +다시 위 자료구조를 제대로 Heapify를 하면 다음과 같다. +![image3](https://github.com/EXPYH/expyh.github.io/blob/gh-pages/_posts/images/210519/210519-03.PNG?raw=true) + +
+ +## 결론 +Heapify 는 일반적으로 부모 노드 방향으로, 또는 자식 노드 방향으로만 이뤄진다. 그러나 Heap의 중간 노드 삭제의 경우는 양방향으로 Heapify를 진행해야 한다. \ No newline at end of file diff --git "a/_posts/2022-03-14-Pydeps\353\241\234-\354\235\230\354\241\264\354\204\261-\355\214\214\354\225\205\355\225\230\352\270\260.md" "b/_posts/2022-03-14-Pydeps\353\241\234-\354\235\230\354\241\264\354\204\261-\355\214\214\354\225\205\355\225\230\352\270\260.md" new file mode 100644 index 0000000..f72cae8 --- /dev/null +++ "b/_posts/2022-03-14-Pydeps\353\241\234-\354\235\230\354\241\264\354\204\261-\355\214\214\354\225\205\355\225\230\352\270\260.md" @@ -0,0 +1,162 @@ +--- +title: "Pydeps로 Python 의존성 파악하기" +date: 2022-03-14 12:00:00 +0900 +categories: Python +--- + +파이썬으로 코드를 작성하면서 import를 남발하다 보면 circular import 에(순환참조)가 발생할 때가 있다. 이런 오류가 발생했을 때는 이미 클래스가 수십개가 되어버려서 어느 부분에서 순환참조가 일어난 것인지 찾기 쉽지 않다. + +최근에 회사에서 순환참조 오류를 겪으면서 확인해야할 일이 있었는데 이 때 **Pydeps** 를 알게 되었고 순환참조 해결 뿐만이 아니라 리펙토링 시에도 많은 도움을 받았다. + +## Pydeps란 +**Pydeps**란 Python의 클래스 간 의존성을 시각적으로 표시해주는 라이브러리이다. + +Github 주소 : [https://github.com/thebjorn/pydeps](https://github.com/thebjorn/pydeps) + +아래는 Pydeps 라이브러리를 Pydeps로 분석한 결과이다. + +![image1](https://raw.githubusercontent.com/thebjorn/pydeps/master/docs/_static/pydeps.svg?sanitize=true)
+ +## Pydeps 설치 +간단하게 pip 를 이용해서 설치한다. **이 때 graphviz가 이미 설치되어 있어야한다**. graphviz는 추상 그래프와 네트워크 등을 다이어그램으로 표현해주는 그래프 비주얼라이제이션 소프트웨어이다. pydeps로 의존성을 분석해서 graphviz가 해석할 수 있는 파일(svg파일이나 dot파일 등) 생성한 후 graphviz를 호출해서 의존성 그래프를 표시해주는 것으로 보인다. + +우선 아래 링크를 통해서 graphviz를 설치한다. 나는 윈도우라서 EXE installer를 사용했다. + +graphviz : [http://www.graphviz.org/download/](http://www.graphviz.org/download/) + +graphviz 설치 후 다음과 같이 pip로 pydeps를 설치한다. + +``` +pip install pydeps +``` + + +## 간단 예시 + +아래와 같이 간단한 python 프로젝트를 만들어보았다. +circular import 에러를 유발하기 위해 억지로 만든 프로젝트이기 때문에 내용에 큰 의미는 없다. + +```python +# a.py +import b + +today = "2022-03-14" + +def helloworld(): + print(b.message) +``` +```python +# b.py +import c + +message = "hello word : " + c.name +``` +```python +# c.py +import a + +name = "expyh" + a.today +``` +```python +# main.py +import a + +if __name__ == "__main__": + a.helloworld() +``` + +maim.py를 실행시켜보면 circular import 에러 구문이 정상적으로 발생한다 +``` +python main.py + +Traceback (most recent call last): + File "C:\Users\expyh\Desktop\test\main.py", line 1, in + import a + File "C:\Users\expyh\Desktop\test\a.py", line 1, in + import b + File "C:\Users\expyh\Desktop\test\b.py", line 1, in + import c + File "C:\Users\expyh\Desktop\test\c.py", line 3, in + name = "expyh" + a.today +AttributeError: partially initialized module 'a' has no attribute 'today' (most likely due to a circular import) +``` + +순환 참조를 확인하기 위해 터미널에서 pydeps 명령어를 사용해보자 +``` +pydeps main.py +``` +![image2](https://github.com/EXPYH/expyh.github.io/blob/gh-pages/_posts/images/220315/220315-01.png?raw=true)
+ +main.py 에서 import a를 했다는 것을 표현해준다. 하지만 이것으론 순환참조 관계를 알 수 없다. 더 깊이 탐색하기 위해서는 --max-bacon 옵션을 사용해야한다. + +> --max-bacon INT
+> exclude nodes that are more than n hops away (default=2, 0 -> infinite) + +여기에 추가로 화살표 방향을 reverse를 적용하면 클래스 다이어그램에서 표현하는 의존관계 방향과 동일하게 표시할 수 있다.. + +``` +pydeps main.py --max-bacon 0 --reverse +``` +![image3](https://github.com/EXPYH/expyh.github.io/blob/gh-pages/_posts/images/220315/220315-02.png?raw=true)
+ +a.py / b.py / c.py가 서로 참조하고 있음을 확인할 수 있다. + +파일이 정말 많아서 육안으로 파악하기 어렵다면 --show-cycles 로 사이클만 표시할 수도 있다. + + +``` +pydeps main.py --max-bacon 0 --reverse --show-cycles +``` +![image4](https://github.com/EXPYH/expyh.github.io/blob/gh-pages/_posts/images/220315/220315-03.png?raw=true)
+ +## 의존성 그래프를 파일로 출력하기 +개인적으로는 회사에서 사용했을 때 pydeps가 graphviz를 인식하지 못해서 다음과 같은 에러가 발생했었다. + +``` + ERROR: + cannot find 'dot' + + pydeps calls dot (from graphviz) to create svg diagrams, + please make sure that +``` + +이럴 때는 궁여지책으로 pydpes로 svg 또는 dot 파일을 생성한 후, graphviz의 dot 명령어를 별도로 실행해서 그래프를 확인해야한다. --noshow로 외부 프로그램 호출하지 않도록 하고 --show-dot으로 dot 결과물을 생성하도록 한 후 이걸 > 를 사용해서 graph.dot 파일로 리다이렉트 한다. + +>--noshow
+>don't call external program to display graph
+>
+>--show-dot
+>show output of dot conversion
+ + + +``` +pydeps main.py --max-bacon 0 --reverse --noshow --show-dot > output.dot +``` + +output.dot은 다음과 같이 생성된다. +``` +digraph G { + concentrate = true; + + rankdir = BT; + node [style=filled,fillcolor="#ffffff",fontcolor="#000000",fontname=Helvetica,fontsize=10]; + + a [fillcolor="#c04040",fontcolor="#ffffff"]; + b [fillcolor="#80b34c"]; + c [fillcolor="#4cb3b3"]; + main [fillcolor="#7f4cb3",fontcolor="#ffffff"]; + c -> a [fillcolor="#4cb3b3"]; + main -> a [fillcolor="#7f4cb3"]; + a -> b [fillcolor="#c04040"]; + b -> c [fillcolor="#80b34c"]; +} +``` + +graphviz를 이미 설치했으므로 터미널에서 dot 명령어를 사용할 수 있다. 아래와 같이 실행하면 그래프를 svg파일로 표시할 수 있다. 다른 파일 형태로 표시하고 싶다면 아래 링크에서 -T 옵션을 참고하도록 한다. + +참고 : [https://graphviz.org/doc/info/command.html](https://graphviz.org/doc/info/command.html) + +``` +dot -Tsvg output.dot > output.svg +``` \ No newline at end of file diff --git "a/_posts/2022-09-05-CKA \354\213\234\355\227\230 \355\233\204\352\270\260.md" "b/_posts/2022-09-05-CKA \354\213\234\355\227\230 \355\233\204\352\270\260.md" new file mode 100644 index 0000000..6b6f7bb --- /dev/null +++ "b/_posts/2022-09-05-CKA \354\213\234\355\227\230 \355\233\204\352\270\260.md" @@ -0,0 +1,76 @@ +--- +title: "CKA 시험 후기" +date: 2022-09-05 12:00:00 +0900 +categories: CKA +--- + +저번 주 토요일에 CKA 시험을 치르고 다음 날인 일요일에 합격 통보를 받았다. 간단히 시험 준비과정과 CKA 시험을 준비하면서 겪은 애로사항 및 느낀 점을 공유하려고 한다. + +## 1. 시험 준비 이유 / 준비 전 나의 상태 +나는 일반적으로 Springboot를 활용하여 API 서버를 개발한다. 가끔 Python도 사용한다. AWS를 사용하여 API 서버를 운영하고 있다. AWS도 리소스를 처음부터 직접 구성해본 적은 없다. Shell을 이용하는데는 익숙하지만 Linux OS 지식이 깊지는 않다. + +현업에서 나는 k8s를 전혀 사용하고 있지 않았다. Kubernetes (이하 k8s)쪽 지식도 전무했었고 Docker라는 것이 컨테이너 기반의 기술이라고만 막연하게 알고있었다. 그러다가 최근에 새로 맡게 된 업무에서는 모든 코드가 k8s를 포함한 온갖 CI/CD 툴을 기반으로 돌아가고 있었다. 처음 Github 접근권한을 받고 난생 처음보는 확장자의 코드들을 구글링하다가 단편적인 지식으로 코드를 이해하는 것엔 한계를 느꼈다. + +결국 기초 개념부터 인터넷 강의나 책 등으로 공부해야겠다는 생각을 하게 되었는데, 주변에 개발자 일을 하는 동기들이 CKA라는 시험을 추천해줬다. Linux foundation에서 제공하는 인증시험인 점, 그리고 시험에서 요구하는 지식들이 현업과 밀접한 연관이 있는 것처럼 보였다. 그래서 약 2달 전부터 본격적으로 준비를 시작하였다. 이 정도 지식으로도 현업을 겸하면서 2달 정도 걸려서 취득하였다. + +## 2. 공부 / 참고자료 + +아래는 내가 공부하면서 사용했던 강의들 / 자료들 및 k8s 환경들이다. 이 중 강의들은 자동 생성된 자막으로 단어들이 종종 엉망이기 때문에 유의하여야 한다. + +### 1) 강의 +- [Certified Kubernetes Administrator (CKA) with Practice Tests](https://www.udemy.com/share/101WmE/) + +CKA 시험을 위한 기초지식부터 다 알려주고 또 연습환경과 연습문제도 죄다 제공하는, CKA 합격 후기마다 등장하는 "그 강의"다. 할인할 때를 노리면 5만원 이하로도 수강 가능한 것 같다(나는 회사에서 제공하는 Udemy 수강권으로 무료로 수강하였다). 이 강의만 잘 들어도 충분히 합격할 수 있다. + +- [Kubernetes for the Absolute Beginners - Hands-on](https://www.udemy.com/share/1013LO/) + +위 강의가 조금 부담된다면 같은 강사가 개설한 이 강의로 k8s를 시작하는 것도 좋다. yaml 작성 위주이긴한데 기초 개념을 잡기에는 좋았다. + +### 2) 참고자료 + +- [쿠버네티스 공식 문서](https://kubernetes.io/docs/home/) + +시험때 참고할 수 있는 사실상 유일한 참고자료이다. 평소에 연습문제를 풀 때도 힌트나 솔루션을 보기 전에 키워드로 docs 검색하는 습관을 들이는 것이 좋다. + +- [Managing Kubernetes Objects Using Imperative Commands](https://kubernetes.io/docs/tasks/manage-kubernetes-objects/imperative-command/) + +17문제를 2시간에 풀려면 시간이 빠듯하다. yaml파일을 일일히 작성하는 것보다는 run과 create, expose 명령어에 익숙해지는게 좋다. 시험때 명령어 옵션이 잘 기억나지 않는다면 -h 옵션으로 도움말을 참고한다. + +- 각종 블로그 후기글들 / 중국 블로그들 + +후기 글들은 PSI 기반 시험으로 변경된 이후의 글들만 참고하자. 그리고 시험 끝나고 알게 된 것인데, 중국 블로그에 시험문제 및 풀이가 그대로 있는 경우가 많다. 시험 후 복기할 때 몇 가지 키워드로 구글링해본 결과 중국어로 작성된 블로그들에 실제로 출제된 문제와 풀이가 그대로 있는 경우를 직접 확인했다. + + +### 3) 시험환경 +- [Minikube](https://minikube.sigs.k8s.io/docs/start/) + +내 컴퓨터를 마스터노드 1개로 구성된 k8s 클러스터로 만들어준다. 대부분의 k8s 실습은 이걸 통해서 진행할 수 있는데, 여러 노드가 필요한 실습(kubeadm을 이용한 cluster 구성 등...)을 할 수가 없는게 단점이다. 또한 k8s 설정파일들을 자세하게 들여다보고 싶을 때 Permission denied가 발생하는 경우가 종종 있었다. + +- [Killercoda](https://killercoda.com/) + +시험 치르고 나서야 발견했던 사이트. 온라인에서 k8s실습을 할 수 있도록 환경을 제공해준다. 심지어 속도가 빨라서 놀랐다. + +연습하고싶은 주제로 검색하면 그에 맞는 환경을 구축해서 제공해준다(ingress같은). 어차피 시험보면 Killer.sh 도 들어가게될테니 겸사겸사 같이 사용하면 좋을 것 같다. + +## 3. 시험장소 물색 +시험볼 때 제일 신경쓰였던 부분이 시험장소였다. 주변에 사람이 있으면 안되는건 물론이고, 벽에 글자가 붙어있거나 해서도 안되고, 책상이나 책상 밑에 아무것도 없어야되고, 무엇보다 조용해야한다. + +나는 집이 너저분하고 또 책장이 여기저기 꽃혀있어서 원하는 장소를 찾는데 어려움이 많았다. 결국은 스터디룸을 구했는데, 다음에 유의하며 장소를 구성하였다. + +- 사방이 완전한 벽으로 되어있는 곳 +- 문을 잠글 수 있으며(사람 들어오는 거 방지), 또 문에 유리창(불투명유리 포함)이 없을 것 +- 너무 시끄럽지 않은 곳 (나는 옆방에서 자꾸 화이트보드 두들겨서 짜증났었음) +- 와이파이가 잘 통하고 필요시 유선랜도 가능한 곳 +- 벽에 미술작품이 없고 안내문이 최소한인 곳 + +거의 1주일을 뒤져서 여기를 만족하는 곳을 찾았었는데, 시험 전 주에 한 번 가서 사전답사를 했고 만족스러워서 시험 당일에도 그 곳을 이용했었다. + +다만 마지막 항목이 힘든 부분이었는데, 나는 그냥 A4용지와 테이프를 들고가서 안내문 (와이파이 비밀번호라던지 퇴실시 주의사항 등)을 다 덮어버렸더니 시험관이 별 말 없이 넘어갔었다. + +꼭 사전답사를 해서 와이파이 등을 비롯한 시험 환경을 조사하고, 당일날에도 랜 연결용 잭과 충전기를 챙기도록 하자. + +참고로 내가 시험을 치른 장소는 [여기](https://thegoalstudy.modoo.at/)다. + +## 4. Don't panic, don't panic, don't panic. + +Certified Kubernetes Administrator (CKA) with Practice Tests 강의 마지막에 나오는 Certification tips의 한 구절인데, 나에게는 큰 도움이 되었다. 100점 만점에 66점만 넘기는 시험이라고 생각하고 쉽게쉽게 보자. diff --git a/_posts/2023-08-25-Java-Record.md b/_posts/2023-08-25-Java-Record.md new file mode 100644 index 0000000..240c098 --- /dev/null +++ b/_posts/2023-08-25-Java-Record.md @@ -0,0 +1,66 @@ +--- +title: "Record 정리" +date: 2023-08-25 15:17:00 +0900 +categories: Java +--- + +토비님의 유튜브에서 스프링 강의를 듣고 있는데 Record에 관련된 이야기가 나왔다. Java 스프링으로 밥 벌어먹고 살고 있는 사람으로서 처음 들어보는 키워드였기 때문에 적잖이 당황했다. 구글링을 해보니 class, interface 등과 동급인, 꽤 중요한 키워드인 것 같아서 여기에 정리해본다. + +## Java Record의 정의 +찾아보니 Record는 Java 14에서 처음 나왔고, 16에서부터 정식 포함되었다. JEP 359에서 정의를 가져왔다. 번역은 구글번역을 이용했다. + +> *Records* are a new kind of type declaration in the Java language. Like an `enum`, a `record` is a restricted form of class. It declares its representation, and commits to an API that matches that representation. Records give up a freedom that classes usually enjoy: the ability to decouple API from representation. In return, records gain a significant degree of concision. +> +> 레코드는 Java 언어의 새로운 유형 선언입니다. 열거형과 마찬가지로 레코드는 제한된 형태의 클래스입니다. 표현을 선언하고 해당 표현과 일치하는 API에 커밋합니다. 레코드는 클래스가 일반적으로 누리는 자유, 즉 표현에서 API를 분리하는 기능을 포기합니다. 그 대가로 기록은 상당한 수준의 간결성을 얻습니다. + +## 왜 만들었는가 +단순히 Data만 표현하는 클래스들을 만들기 위해서도 기존 자바에서는 수 많은 코드를 (이른바 boilerplate들. equals() 라던제 getter(), setter() 등…) 작성해야한다. 그러면 다음과 같은 문제점이 있다. + +1. boilerplate 코드 작성 도중에 에러가 발생할 여지가 있다. +2. 요즘은 Intellij 같은 IDE에서 금방 코드들을 생성할 수 있지만, 여전히 장황하기 때문에 다른 프로그래머 입장에서는 이 클래스가 그냥 Data만 표현하는 클래스인지, 아니면 또 무슨 함수나 로직같은게 들어가있는지 한 눈에 알기 힘들다. + +## 어떻게 쓰는가 + +코드는 다음과 같이 작성된다. type 선언인데 함수와 비슷한 형태로 선언되는 것이 특징이다. 괄호 안에 Record에 포함하고 싶은 필드들을 선언하면 된다. 그러면 다음과 같은 일이 일어난다. + +- 괄호 안 필드들이 final로 선언된다. +- public 접근자가 자동 선언된다. 그런데 일반적인 getterField()와 같은 식으로 쓰지 않고 그냥 필드 이름 그대로 field() 와 같이 접근한다. +- canonical 생성자가 자동으로 선언된다. Record에 선언된 필드들을 순서대로 인자로 받는 생성자이다. +- toString(), hashCode(), equals()도 자동 생성된다. equals의 경우 같은 record이고 field들의 값이 모두 같은 경우 true를 반환한다. + +추가적으로 static 변수나 함수정도는 class와 동일하게 선언 가능한 것으로 보인다. abstract는 될 수 없는데, 인터페이스를 구현하는건 가능한 것 같고... 구현이 필요할 때 마다 명세를 찾아보거나 IDE로 확인해봐야겠다. + +생성자를 추가로 생성할 수도 있다. 인자를 직접 선언할 수도 있고, 인자를 선언하지 않으면 기본 canonical 생성자를 대체한다. + +인자를 직접 선언한 생성자는 this()를 통해서 결국 canonical 생성자를 호출해야하는데, 이 때 this()는 생성자의 첫 줄에만 선언이 가능해다. 따라서 this() 호출 전 별도 로직을 추가하는 것이 불가능해 보인다. 인자가 있는 생성자는 결국 단순히 canonical 생성자에 변수만 단순히 넘겨주고, Data 체크 로직은 결국 canonical 생성자에서 진행해야 하는 것으로 보인다 + +```java +// Java Record 선언 및 Constructor 선언 +public record Point( + int x, + int y, + int radius) { + + public static String gender = "Male"; + + //public Point(int x, int y){ + // if (x > y) throw new IllegalArgumentException(); + // this(x, y, x+y); // Error : Call to 'this()' must be first statement in constructor body + //} + public Point(int x, int y){ + this(x, y, x+y); + } + public Point { + if (x > y) throw new IllegalArgumentException(); + } +} +``` + + +간단히 알아보았는데, 개인적으로는 사용할 것 같지 않다. 이유는 다음과 같다. + +- JPA를 위한 Entity 로 사용할 수 없음 → getter / setter함수가 없는데다가 final 필드만 가져서 그런 것 같다. +- Lombok으로도 충분히 boilerplate 함수들을 많이 줄일 수 있다. +- 아직 회사에서 Java 8을 쓰고있다(앗!). + +우리 회사뿐만 아니라 대부분의 코드들이 아직도 Java 8로 동작한다고 들었다. Java 16 이상이 주류가 되면 쓰일까? 솔직히 잘 모르겠다. 그럼에도 immutable 한 자료구조로써 직접 Java에서 지원을 한다는 것 자체에 의의를 두어야 할 것 같다. diff --git a/_posts/2023-09-25-Java-Optional.md b/_posts/2023-09-25-Java-Optional.md new file mode 100644 index 0000000..331ef1f --- /dev/null +++ b/_posts/2023-09-25-Java-Optional.md @@ -0,0 +1,146 @@ +--- +title: "Optional 정리" +date: 2023-09-22 13:11:00 +0900 +categories: Java +--- + +약 6년 된 코드를 리팩토링 하고 있는데, Optional을 씌우고 벗기고 하는 과정이 너무 많았다. 굳이 Optional을 안써도 되는데 쓰는건지, 아니면 제대로 쓰고 있는건데 내가 이해를 못하는건지 궁금해졌다. 그래서 공부하고 정리한다. + +## Java Optional의 정의 +자바 8에서 Lambda를 도입하면서 함께 도입된 개념 같다. JEP는 따로 없는 것 같고 대신 API문서의 설명을 가져왔다. + +> A container object which may or may not contain a non-null value. If a value is present, isPresent() will return true and get() will return the value. +> +> null이 아닌 값을 포함하거나 포함하지 않을 수 있는 컨테이너 개체입니다. 값이 있으면 isPresent()는 true를 반환하고 get()은 값을 반환합니다. + +## 왜 만들었는가 +Java 아키텍트인 Brian Goetz는 이렇게 설명한다. [해당 문서 36페이지에 나와있다.](https://stuartmarks.files.wordpress.com/2015/10/con6851-api-design-v2.pdf) + +> Optional is intended to provide a limited mechanism for library method return types where there is a clear need to represent "no result," and where using null for that is overwhelmingly likely to cause errors. +> +> Optional은 "결과 없음"을 표시해야 하는 명확한 필요성이 있고 이에 대해 null을 사용하면 오류가 발생할 가능성이 압도적으로 높은 라이브러리 메서드 반환 유형에 대한 제한된 메커니즘을 제공하기 위한 것입니다. + +## 기본 개념 / 생성법 +Java에서 객체는 크게 값을 가지는 경우와 가지지 않는 경우(null)로 나눈다. 이를 Optional로 객체를 감싸면 값이 있는 경우와 비어있는 경우로 나뉜다. +Optional 을 생성하는 방법은 3가지이다. + +- Optional.of(object)를 이용한다. null이 아닌게 확실할 때만 쓸 수 있다. 여기에 null을 넣으면 NullPointerException이 발생한다. +- Optional.ofNullable(object)을 이용한다. null인지 아닌지 잘 모르겠으면 이걸 쓴다. 여기에 null을 넣으면 Optional.empty가 된다. +- Optional.empty()를 이용하여 Optional.empty를 만든다. + +Optional이 비어있는지 아닌지는 isPresent() 를 사용하면 된다. Optional에서 값을 다시 가져올 때는 get()을 사용하면 된다. + +아래 코드를 참고한다. +```java +package org.example; + +import java.util.Optional; + +public class Main { + + public static void main(String[] args) { + Point validPoint = new Point(0, 0); + Point nullPoint = null; + Optional maybeValidPoint = Optional.of(validPoint); + Optional maybeEmptyPoint = Optional.ofNullable(nullPoint); + Optional maybeEmptyPoint = Optional.of(nullPoint); //NullPointerException + Optional emptyPoint = Optional.empty(); + + System.out.println(maybeValidPoint); //Optional[org.example.Point@2752f6e2] + System.out.println(maybeEmptyPoint); //Optional.empty + System.out.println(emptyPoint); //Optional.empty + + System.out.println(maybeValidPoint.isPresent()); //true + System.out.println(maybeEmptyPoint.isPresent()); //false + System.out.println(emptyPoint.isPresent()); //false + + System.out.println(maybeValidPoint.get()); //org.example.Point@2752f6e2 + System.out.println(maybeEmptyPoint.get()); //NoSuchElementException: No value present + System.out.println(emptyPoint.get()); //NoSuchElementException: No value present + } +} + +``` +추가로, 비어있는 Optional에서 get()을 하면 NoSuchElementException이 발생한다. 그러니까 굳이 get()으로 꺼내오고 싶다면 우선 isPresent()로 확인하는 것이 필수이다. + + +## Optional의 이유 + +중요하게 봐야할 것은 역시 Optional.ofNullable()일 것이다. 값이 있는지 없는지 확실히 아는 상황이라면 굳이 감쌀 이유가 없기 때문이다.
+ +Optional은 "null인지 아닌지 확실치 않은 객체"를 다루는 API를 제공한다. 기존에는 이런 확실치 않은 상황에 대해서 null 체크를 일일히 하면서 진행했어야 했고, 코드량이 많아질 뿐더러 에러를 유발하기도 쉬웠다. 이를 Optional은 좀 더 우아하게 처리해준다. +아래는 대포적인 함수인 map() 및 orElse()를 활용한 Optional 활용 코드와 기존 if문을 활용한 코드가 동일하게 동작하도록 작성해 본 것이다. + + +```java +// Java Record 선언 및 Constructor 선언 +package org.example; + +import java.util.Optional; + +public class Main { + + public static void main(String[] args) { + + Employee employee = getEmployeeFromDB("John"); + + String userCountry = getUserCountryByIf(employee); + String userCountry2 = getUserCountryByOptional(employee); + + System.out.println(userCountry); + System.out.println(userCountry2); + } + + + private static Employee getEmployeeFromDB(String name) { + Address addressWithNullCountry = new Address(null); + Employee employee = new Employee(addressWithNullCountry); + return employee; + } + + private static String getUserCountryByOptional(Employee employee) { + if (employee != null ) { + Address address = employee.getAddress(); + if (address != null){ + String country = address.getCountry(); + if (country != null) { + return country; + } + } + } + return "not specified"; + } + + private static String getUserCountryByIf(Employee employee) { + return Optional.ofNullable(employee) + .map(Employee::getAddress) + .map(Address::getCountry) + .orElse("not specified"); + } + +} +``` + +먼저, Optional에서 자주 사용하는 함수인 map()은 다음과 같이 동작한다. +- 입력 Optional이 값을 가지고 있으면, 함수를 실행하여 얻은 결과값을 다시 Optional로 감싸준다. +- 입력 Optional이 empty라면, 함수를 실행하지 않고 그대로 빈 Optional을 넘겨준다. + +위 예시를 보면 Optional api를 사용하여 중복 if문을 없애고 함수 체이닝으로 가독성 좋게 처리한 것을 알 수 있다. +다른 filter() 같은 함수들도 Optional을 받아서 Optional을 리턴하거나 하므로 함수 체이닝을 활용할 수 있다. + +그리고 마지막에 orElse()는 이러한 Optional 연산의 최종 결과물에 따라 다음과 같이 동작한다. +- 입력이 값이 있는 Optional이라면 그대로 리턴한다. +- 입력이 빈 Optional이라면 지정된 값을 리턴한다. + +따라서 위의 경우 Address::getCountry 값이 있으면 해당 값을, 아니면 "not specified"를 리턴한다. + + +## 결론 +null을 한 번 감싸서 nullpointer 예외를 방지해주는 점에선 환영할 만한 키워드이지만 그만큼 오용도 많이 되는 키워드이다. +Optional로 감싸고 벗기는 데에 컴퓨팅 연산이 들어갈 뿐더러 다른 종류의 예외가 여전히 발생할 수 있기 때문이다(NoSuchElementException).
+ +매개변수로 Optional을 받거나, 리턴을 Optional로 하는 경우가 종종 있는데 호출자 입장에서 추가적인 로직을 강요하고 혼동을 야기할 수 있기 때문에 +이런 건 지양하고 Optional 연산은 체이닝등을 이용해서 한 문장 내에서 끝내는게 좋을 것 같다. +단순한 null 체크용이라면 기존처럼 == 를 쓰는 것이 좋아보인다.
+ +[Optional 사용 시 유읙사항과 관련된 글](https://www.latera.kr/blog/2019-07-02-effective-optional/)들이 온라인에 많이 있으니 참고하면 좋겠다. \ No newline at end of file diff --git a/_posts/images/210519/210519-01.PNG b/_posts/images/210519/210519-01.PNG new file mode 100644 index 0000000..a717841 Binary files /dev/null and b/_posts/images/210519/210519-01.PNG differ diff --git a/_posts/images/210519/210519-02.PNG b/_posts/images/210519/210519-02.PNG new file mode 100644 index 0000000..595994d Binary files /dev/null and b/_posts/images/210519/210519-02.PNG differ diff --git a/_posts/images/210519/210519-03.PNG b/_posts/images/210519/210519-03.PNG new file mode 100644 index 0000000..a43f669 Binary files /dev/null and b/_posts/images/210519/210519-03.PNG differ diff --git a/_posts/images/220315/220315-01.png b/_posts/images/220315/220315-01.png new file mode 100644 index 0000000..efefe82 Binary files /dev/null and b/_posts/images/220315/220315-01.png differ diff --git a/_posts/images/220315/220315-02.png b/_posts/images/220315/220315-02.png new file mode 100644 index 0000000..0d17437 Binary files /dev/null and b/_posts/images/220315/220315-02.png differ diff --git a/_posts/images/220315/220315-03.png b/_posts/images/220315/220315-03.png new file mode 100644 index 0000000..1508930 Binary files /dev/null and b/_posts/images/220315/220315-03.png differ