diff --git a/404.html b/404.html new file mode 100644 index 000000000..c393c43a6 --- /dev/null +++ b/404.html @@ -0,0 +1 @@ + 404: Page not found | 디피의 개발일지
Home 404: Page not found
404: Page not found
Cancel
diff --git a/app.js b/app.js new file mode 100644 index 000000000..b2ab751c6 --- /dev/null +++ b/app.js @@ -0,0 +1 @@ +/* Registering Service Worker */ if('serviceWorker' in navigator) { navigator.serviceWorker.register('/sw.js'); }; diff --git a/archives/index.html b/archives/index.html new file mode 100644 index 000000000..03eb8c46e --- /dev/null +++ b/archives/index.html @@ -0,0 +1 @@ + Archives | 디피의 개발일지
Home Archives
Archives
Cancel

Archives

2024202320222021
diff --git a/assets/css/style.css b/assets/css/style.css new file mode 100644 index 000000000..ccd94777f --- /dev/null +++ b/assets/css/style.css @@ -0,0 +1,9 @@ +/*! + * The styles for Jekyll theme Chirpy + * + * Chirpy v3.3.2 (https://github.com/cotes2020/jekyll-theme-chirpy) + * © 2019 Cotes Chung + * MIT Licensed + */@import"https://fonts.googleapis.com/css2?family=Lato&family=Source+Sans+Pro:wght@400;600;900&display=swap";#search-results a,h5,h4,h3,h2,h1{color:var(--heading-color);font-weight:400;font-family:"Lato","Microsoft Yahei",sans-serif}.post-tag:hover,.tag:hover{background:var(--tag-hover);transition:background .35s ease-in-out}.table-wrapper>table tbody tr td,.table-wrapper>table thead th{padding:.4rem 1rem;font-size:95%;white-space:nowrap}#page-category a:hover,#page-tag a:hover,.license-wrapper>a:hover,#post-list .post-preview a:hover,#search-results a:hover,#topbar #breadcrumb a:hover,.post-content a:not(.img-link):hover,.post-meta a:hover,.post a:hover code,#access-lastmod a:hover,footer a:hover{color:#d2603a !important;border-bottom:1px solid #d2603a;text-decoration:none}#search-results a,.post-content a:not(.img-link),.post-meta a,a{color:var(--link-color)}.post-content a:not(.img-link),.post-meta a{border-bottom:1px solid var(--link-underline-color)}#page-category ul>li>a,#page-tag ul>li>a,#page .categories a:not(:hover),#page #tags a:not(:hover),#page #archives a:not(:hover),#search-results a,#access-lastmod a{border-bottom:none}#post-wrapper h5,#post-wrapper h4,#post-wrapper h3,#post-wrapper h2{line-height:1.2;margin-bottom:1rem}h5,h4,h3,h2{padding-top:3.5rem;margin-top:-2.5rem}html:not([mode]),html[mode=light]{--highlight-bg-color: #f7f7f7;--highlighter-rouge-color: #2f2f2f;--highlight-lineno-color: #c2c6cc;--highlight-lineno-border-color: #e9ecef;--inline-code-bg: #f3f3f3}html:not([mode]) .highlight .hll,html[mode=light] .highlight .hll{background-color:#ffc}html:not([mode]) .highlight .c,html[mode=light] .highlight .c{color:#998;font-style:italic}html:not([mode]) .highlight .err,html[mode=light] .highlight .err{color:#a61717;background-color:#e3d2d2}html:not([mode]) .highlight .k,html[mode=light] .highlight .k{color:#000;font-weight:bold}html:not([mode]) .highlight .o,html[mode=light] .highlight .o{color:#000;font-weight:bold}html:not([mode]) .highlight .cm,html[mode=light] .highlight .cm{color:#998;font-style:italic}html:not([mode]) .highlight .cp,html[mode=light] .highlight .cp{color:#999;font-weight:bold;font-style:italic}html:not([mode]) .highlight .c1,html[mode=light] .highlight .c1{color:#998;font-style:italic}html:not([mode]) .highlight .cs,html[mode=light] .highlight .cs{color:#999;font-weight:bold;font-style:italic}html:not([mode]) .highlight .gd,html[mode=light] .highlight .gd{color:#d01040;background-color:#fdd}html:not([mode]) .highlight .ge,html[mode=light] .highlight .ge{color:#000;font-style:italic}html:not([mode]) .highlight .gr,html[mode=light] .highlight .gr{color:#a00}html:not([mode]) .highlight .gh,html[mode=light] .highlight .gh{color:#999}html:not([mode]) .highlight .gi,html[mode=light] .highlight .gi{color:teal;background-color:#dfd}html:not([mode]) .highlight .go,html[mode=light] .highlight .go{color:#888}html:not([mode]) .highlight .gp,html[mode=light] .highlight .gp{color:#555}html:not([mode]) .highlight .gs,html[mode=light] .highlight .gs{font-weight:bold}html:not([mode]) .highlight .gu,html[mode=light] .highlight .gu{color:#aaa}html:not([mode]) .highlight .gt,html[mode=light] .highlight .gt{color:#a00}html:not([mode]) .highlight .kc,html[mode=light] .highlight .kc{color:#000;font-weight:bold}html:not([mode]) .highlight .kd,html[mode=light] .highlight .kd{color:#000;font-weight:bold}html:not([mode]) .highlight .kn,html[mode=light] .highlight .kn{color:#000;font-weight:bold}html:not([mode]) .highlight .kp,html[mode=light] .highlight .kp{color:#000;font-weight:bold}html:not([mode]) .highlight .kr,html[mode=light] .highlight .kr{color:#000;font-weight:bold}html:not([mode]) .highlight .kt,html[mode=light] .highlight .kt{color:#458;font-weight:bold}html:not([mode]) .highlight .m,html[mode=light] .highlight .m{color:#099}html:not([mode]) .highlight .s,html[mode=light] .highlight .s{color:#d01040}html:not([mode]) .highlight .na,html[mode=light] .highlight .na{color:teal}html:not([mode]) .highlight .nb,html[mode=light] .highlight .nb{color:#0086b3}html:not([mode]) .highlight .nc,html[mode=light] .highlight .nc{color:#458;font-weight:bold}html:not([mode]) .highlight .no,html[mode=light] .highlight .no{color:teal}html:not([mode]) .highlight .nd,html[mode=light] .highlight .nd{color:#3c5d5d;font-weight:bold}html:not([mode]) .highlight .ni,html[mode=light] .highlight .ni{color:purple}html:not([mode]) .highlight .ne,html[mode=light] .highlight .ne{color:#900;font-weight:bold}html:not([mode]) .highlight .nf,html[mode=light] .highlight .nf{color:#900;font-weight:bold}html:not([mode]) .highlight .nl,html[mode=light] .highlight .nl{color:#900;font-weight:bold}html:not([mode]) .highlight .nn,html[mode=light] .highlight .nn{color:#555}html:not([mode]) .highlight .nt,html[mode=light] .highlight .nt{color:navy}html:not([mode]) .highlight .nv,html[mode=light] .highlight .nv{color:teal}html:not([mode]) .highlight .ow,html[mode=light] .highlight .ow{color:#000;font-weight:bold}html:not([mode]) .highlight .w,html[mode=light] .highlight .w{color:#bbb}html:not([mode]) .highlight .mf,html[mode=light] .highlight .mf{color:#099}html:not([mode]) .highlight .mh,html[mode=light] .highlight .mh{color:#099}html:not([mode]) .highlight .mi,html[mode=light] .highlight .mi{color:#099}html:not([mode]) .highlight .mo,html[mode=light] .highlight .mo{color:#099}html:not([mode]) .highlight .sb,html[mode=light] .highlight .sb{color:#d01040}html:not([mode]) .highlight .sc,html[mode=light] .highlight .sc{color:#d01040}html:not([mode]) .highlight .sd,html[mode=light] .highlight .sd{color:#d01040}html:not([mode]) .highlight .s2,html[mode=light] .highlight .s2{color:#d01040}html:not([mode]) .highlight .se,html[mode=light] .highlight .se{color:#d01040}html:not([mode]) .highlight .sh,html[mode=light] .highlight .sh{color:#d01040}html:not([mode]) .highlight .si,html[mode=light] .highlight .si{color:#d01040}html:not([mode]) .highlight .sx,html[mode=light] .highlight .sx{color:#d01040}html:not([mode]) .highlight .sr,html[mode=light] .highlight .sr{color:#009926}html:not([mode]) .highlight .s1,html[mode=light] .highlight .s1{color:#d01040}html:not([mode]) .highlight .ss,html[mode=light] .highlight .ss{color:#990073}html:not([mode]) .highlight .bp,html[mode=light] .highlight .bp{color:#999}html:not([mode]) .highlight .vc,html[mode=light] .highlight .vc{color:teal}html:not([mode]) .highlight .vg,html[mode=light] .highlight .vg{color:teal}html:not([mode]) .highlight .vi,html[mode=light] .highlight .vi{color:teal}html:not([mode]) .highlight .il,html[mode=light] .highlight .il{color:#099}html[mode=dark]{--highlight-bg-color: #252525;--highlighter-rouge-color: #de6b18;--highlight-lineno-color: #6c6c6d;--highlight-lineno-border-color: #303435;--inline-code-bg: #272822}html[mode=dark] .highlight .gp{color:#818c96}html[mode=dark] pre{color:#bfbfbf}html[mode=dark] kbd{background-color:#000}html[mode=dark] .highlight pre{background-color:var(--highlight-bg-color)}html[mode=dark] .highlight .hll{background-color:var(--highlight-bg-color)}html[mode=dark] .highlight .c{color:#75715e}html[mode=dark] .highlight .err{color:#960050;background-color:#1e0010}html[mode=dark] .highlight .k{color:#66d9ef}html[mode=dark] .highlight .l{color:#ae81ff}html[mode=dark] .highlight .n{color:#f8f8f2}html[mode=dark] .highlight .o{color:#f92672}html[mode=dark] .highlight .p{color:#f8f8f2}html[mode=dark] .highlight .cm{color:#75715e}html[mode=dark] .highlight .cp{color:#75715e}html[mode=dark] .highlight .c1{color:#75715e}html[mode=dark] .highlight .cs{color:#75715e}html[mode=dark] .highlight .ge{color:inherit;font-style:italic}html[mode=dark] .highlight .gs{font-weight:bold}html[mode=dark] .highlight .kc{color:#66d9ef}html[mode=dark] .highlight .kd{color:#66d9ef}html[mode=dark] .highlight .kn{color:#f92672}html[mode=dark] .highlight .kp{color:#66d9ef}html[mode=dark] .highlight .kr{color:#66d9ef}html[mode=dark] .highlight .kt{color:#66d9ef}html[mode=dark] .highlight .ld{color:#e6db74}html[mode=dark] .highlight .m{color:#ae81ff}html[mode=dark] .highlight .s{color:#e6db74}html[mode=dark] .highlight .na{color:#a6e22e}html[mode=dark] .highlight .nb{color:#f8f8f2}html[mode=dark] .highlight .nc{color:#a6e22e}html[mode=dark] .highlight .no{color:#66d9ef}html[mode=dark] .highlight .nd{color:#a6e22e}html[mode=dark] .highlight .ni{color:#f8f8f2}html[mode=dark] .highlight .ne{color:#a6e22e}html[mode=dark] .highlight .nf{color:#a6e22e}html[mode=dark] .highlight .nl{color:#f8f8f2}html[mode=dark] .highlight .nn{color:#f8f8f2}html[mode=dark] .highlight .nx{color:#a6e22e}html[mode=dark] .highlight .py{color:#f8f8f2}html[mode=dark] .highlight .nt{color:#f92672}html[mode=dark] .highlight .nv{color:#f8f8f2}html[mode=dark] .highlight .ow{color:#f92672}html[mode=dark] .highlight .w{color:#f8f8f2}html[mode=dark] .highlight .mf{color:#ae81ff}html[mode=dark] .highlight .mh{color:#ae81ff}html[mode=dark] .highlight .mi{color:#ae81ff}html[mode=dark] .highlight .mo{color:#ae81ff}html[mode=dark] .highlight .sb{color:#e6db74}html[mode=dark] .highlight .sc{color:#e6db74}html[mode=dark] .highlight .sd{color:#e6db74}html[mode=dark] .highlight .s2{color:#e6db74}html[mode=dark] .highlight .se{color:#ae81ff}html[mode=dark] .highlight .sh{color:#e6db74}html[mode=dark] .highlight .si{color:#e6db74}html[mode=dark] .highlight .sx{color:#e6db74}html[mode=dark] .highlight .sr{color:#e6db74}html[mode=dark] .highlight .s1{color:#e6db74}html[mode=dark] .highlight .ss{color:#e6db74}html[mode=dark] .highlight .bp{color:#f8f8f2}html[mode=dark] .highlight .vc{color:#f8f8f2}html[mode=dark] .highlight .vg{color:#f8f8f2}html[mode=dark] .highlight .vi{color:#f8f8f2}html[mode=dark] .highlight .il{color:#ae81ff}html[mode=dark] .highlight .gu{color:#75715e}html[mode=dark] .highlight .gd{color:#f92672;background-color:#561c08}html[mode=dark] .highlight .gi{color:#a6e22e;background-color:#0b5858}@media(prefers-color-scheme: dark){html:not([mode]),html[mode=dark]{--highlight-bg-color: #252525;--highlighter-rouge-color: #de6b18;--highlight-lineno-color: #6c6c6d;--highlight-lineno-border-color: #303435;--inline-code-bg: #272822}html:not([mode]) .highlight .gp,html[mode=dark] .highlight .gp{color:#818c96}html:not([mode]) pre,html[mode=dark] pre{color:#bfbfbf}html:not([mode]) kbd,html[mode=dark] kbd{background-color:#000}html:not([mode]) .highlight pre,html[mode=dark] .highlight pre{background-color:var(--highlight-bg-color)}html:not([mode]) .highlight .hll,html[mode=dark] .highlight .hll{background-color:var(--highlight-bg-color)}html:not([mode]) .highlight .c,html[mode=dark] .highlight .c{color:#75715e}html:not([mode]) .highlight .err,html[mode=dark] .highlight .err{color:#960050;background-color:#1e0010}html:not([mode]) .highlight .k,html[mode=dark] .highlight .k{color:#66d9ef}html:not([mode]) .highlight .l,html[mode=dark] .highlight .l{color:#ae81ff}html:not([mode]) .highlight .n,html[mode=dark] .highlight .n{color:#f8f8f2}html:not([mode]) .highlight .o,html[mode=dark] .highlight .o{color:#f92672}html:not([mode]) .highlight .p,html[mode=dark] .highlight .p{color:#f8f8f2}html:not([mode]) .highlight .cm,html[mode=dark] .highlight .cm{color:#75715e}html:not([mode]) .highlight .cp,html[mode=dark] .highlight .cp{color:#75715e}html:not([mode]) .highlight .c1,html[mode=dark] .highlight .c1{color:#75715e}html:not([mode]) .highlight .cs,html[mode=dark] .highlight .cs{color:#75715e}html:not([mode]) .highlight .ge,html[mode=dark] .highlight .ge{color:inherit;font-style:italic}html:not([mode]) .highlight .gs,html[mode=dark] .highlight .gs{font-weight:bold}html:not([mode]) .highlight .kc,html[mode=dark] .highlight .kc{color:#66d9ef}html:not([mode]) .highlight .kd,html[mode=dark] .highlight .kd{color:#66d9ef}html:not([mode]) .highlight .kn,html[mode=dark] .highlight .kn{color:#f92672}html:not([mode]) .highlight .kp,html[mode=dark] .highlight .kp{color:#66d9ef}html:not([mode]) .highlight .kr,html[mode=dark] .highlight .kr{color:#66d9ef}html:not([mode]) .highlight .kt,html[mode=dark] .highlight .kt{color:#66d9ef}html:not([mode]) .highlight .ld,html[mode=dark] .highlight .ld{color:#e6db74}html:not([mode]) .highlight .m,html[mode=dark] .highlight .m{color:#ae81ff}html:not([mode]) .highlight .s,html[mode=dark] .highlight .s{color:#e6db74}html:not([mode]) .highlight .na,html[mode=dark] .highlight .na{color:#a6e22e}html:not([mode]) .highlight .nb,html[mode=dark] .highlight .nb{color:#f8f8f2}html:not([mode]) .highlight .nc,html[mode=dark] .highlight .nc{color:#a6e22e}html:not([mode]) .highlight .no,html[mode=dark] .highlight .no{color:#66d9ef}html:not([mode]) .highlight .nd,html[mode=dark] .highlight .nd{color:#a6e22e}html:not([mode]) .highlight .ni,html[mode=dark] .highlight .ni{color:#f8f8f2}html:not([mode]) .highlight .ne,html[mode=dark] .highlight .ne{color:#a6e22e}html:not([mode]) .highlight .nf,html[mode=dark] .highlight .nf{color:#a6e22e}html:not([mode]) .highlight .nl,html[mode=dark] .highlight .nl{color:#f8f8f2}html:not([mode]) .highlight .nn,html[mode=dark] .highlight .nn{color:#f8f8f2}html:not([mode]) .highlight .nx,html[mode=dark] .highlight .nx{color:#a6e22e}html:not([mode]) .highlight .py,html[mode=dark] .highlight .py{color:#f8f8f2}html:not([mode]) .highlight .nt,html[mode=dark] .highlight .nt{color:#f92672}html:not([mode]) .highlight .nv,html[mode=dark] .highlight .nv{color:#f8f8f2}html:not([mode]) .highlight .ow,html[mode=dark] .highlight .ow{color:#f92672}html:not([mode]) .highlight .w,html[mode=dark] .highlight .w{color:#f8f8f2}html:not([mode]) .highlight .mf,html[mode=dark] .highlight .mf{color:#ae81ff}html:not([mode]) .highlight .mh,html[mode=dark] .highlight .mh{color:#ae81ff}html:not([mode]) .highlight .mi,html[mode=dark] .highlight .mi{color:#ae81ff}html:not([mode]) .highlight .mo,html[mode=dark] .highlight .mo{color:#ae81ff}html:not([mode]) .highlight .sb,html[mode=dark] .highlight .sb{color:#e6db74}html:not([mode]) .highlight .sc,html[mode=dark] .highlight .sc{color:#e6db74}html:not([mode]) .highlight .sd,html[mode=dark] .highlight .sd{color:#e6db74}html:not([mode]) .highlight .s2,html[mode=dark] .highlight .s2{color:#e6db74}html:not([mode]) .highlight .se,html[mode=dark] .highlight .se{color:#ae81ff}html:not([mode]) .highlight .sh,html[mode=dark] .highlight .sh{color:#e6db74}html:not([mode]) .highlight .si,html[mode=dark] .highlight .si{color:#e6db74}html:not([mode]) .highlight .sx,html[mode=dark] .highlight .sx{color:#e6db74}html:not([mode]) .highlight .sr,html[mode=dark] .highlight .sr{color:#e6db74}html:not([mode]) .highlight .s1,html[mode=dark] .highlight .s1{color:#e6db74}html:not([mode]) .highlight .ss,html[mode=dark] .highlight .ss{color:#e6db74}html:not([mode]) .highlight .bp,html[mode=dark] .highlight .bp{color:#f8f8f2}html:not([mode]) .highlight .vc,html[mode=dark] .highlight .vc{color:#f8f8f2}html:not([mode]) .highlight .vg,html[mode=dark] .highlight .vg{color:#f8f8f2}html:not([mode]) .highlight .vi,html[mode=dark] .highlight .vi{color:#f8f8f2}html:not([mode]) .highlight .il,html[mode=dark] .highlight .il{color:#ae81ff}html:not([mode]) .highlight .gu,html[mode=dark] .highlight .gu{color:#75715e}html:not([mode]) .highlight .gd,html[mode=dark] .highlight .gd{color:#f92672;background-color:#561c08}html:not([mode]) .highlight .gi,html[mode=dark] .highlight .gi{color:#a6e22e;background-color:#0b5858}html[mode=light]{--highlight-bg-color: #f7f7f7;--highlighter-rouge-color: #2f2f2f;--highlight-lineno-color: #c2c6cc;--highlight-lineno-border-color: #e9ecef;--inline-code-bg: #f3f3f3}html[mode=light] .highlight .hll{background-color:#ffc}html[mode=light] .highlight .c{color:#998;font-style:italic}html[mode=light] .highlight .err{color:#a61717;background-color:#e3d2d2}html[mode=light] .highlight .k{color:#000;font-weight:bold}html[mode=light] .highlight .o{color:#000;font-weight:bold}html[mode=light] .highlight .cm{color:#998;font-style:italic}html[mode=light] .highlight .cp{color:#999;font-weight:bold;font-style:italic}html[mode=light] .highlight .c1{color:#998;font-style:italic}html[mode=light] .highlight .cs{color:#999;font-weight:bold;font-style:italic}html[mode=light] .highlight .gd{color:#d01040;background-color:#fdd}html[mode=light] .highlight .ge{color:#000;font-style:italic}html[mode=light] .highlight .gr{color:#a00}html[mode=light] .highlight .gh{color:#999}html[mode=light] .highlight .gi{color:teal;background-color:#dfd}html[mode=light] .highlight .go{color:#888}html[mode=light] .highlight .gp{color:#555}html[mode=light] .highlight .gs{font-weight:bold}html[mode=light] .highlight .gu{color:#aaa}html[mode=light] .highlight .gt{color:#a00}html[mode=light] .highlight .kc{color:#000;font-weight:bold}html[mode=light] .highlight .kd{color:#000;font-weight:bold}html[mode=light] .highlight .kn{color:#000;font-weight:bold}html[mode=light] .highlight .kp{color:#000;font-weight:bold}html[mode=light] .highlight .kr{color:#000;font-weight:bold}html[mode=light] .highlight .kt{color:#458;font-weight:bold}html[mode=light] .highlight .m{color:#099}html[mode=light] .highlight .s{color:#d01040}html[mode=light] .highlight .na{color:teal}html[mode=light] .highlight .nb{color:#0086b3}html[mode=light] .highlight .nc{color:#458;font-weight:bold}html[mode=light] .highlight .no{color:teal}html[mode=light] .highlight .nd{color:#3c5d5d;font-weight:bold}html[mode=light] .highlight .ni{color:purple}html[mode=light] .highlight .ne{color:#900;font-weight:bold}html[mode=light] .highlight .nf{color:#900;font-weight:bold}html[mode=light] .highlight .nl{color:#900;font-weight:bold}html[mode=light] .highlight .nn{color:#555}html[mode=light] .highlight .nt{color:navy}html[mode=light] .highlight .nv{color:teal}html[mode=light] .highlight .ow{color:#000;font-weight:bold}html[mode=light] .highlight .w{color:#bbb}html[mode=light] .highlight .mf{color:#099}html[mode=light] .highlight .mh{color:#099}html[mode=light] .highlight .mi{color:#099}html[mode=light] .highlight .mo{color:#099}html[mode=light] .highlight .sb{color:#d01040}html[mode=light] .highlight .sc{color:#d01040}html[mode=light] .highlight .sd{color:#d01040}html[mode=light] .highlight .s2{color:#d01040}html[mode=light] .highlight .se{color:#d01040}html[mode=light] .highlight .sh{color:#d01040}html[mode=light] .highlight .si{color:#d01040}html[mode=light] .highlight .sx{color:#d01040}html[mode=light] .highlight .sr{color:#009926}html[mode=light] .highlight .s1{color:#d01040}html[mode=light] .highlight .ss{color:#990073}html[mode=light] .highlight .bp{color:#999}html[mode=light] .highlight .vc{color:teal}html[mode=light] .highlight .vg{color:teal}html[mode=light] .highlight .vi{color:teal}html[mode=light] .highlight .il{color:#099}}figure.highlight,.highlight,.highlighter-rouge,div>pre{background:var(--highlight-bg-color)}.highlight,.highlighter-rouge,div>pre{border-radius:6px}div[class^=highlighter-rouge] td.rouge-code,div.language-plaintext.highlighter-rouge td.rouge-code,div.language-console.highlighter-rouge td.rouge-code,div.language-terminal.highlighter-rouge td.rouge-code,div>pre{padding:1.5rem}.highlighter-rouge{color:var(--highlighter-rouge-color);margin-top:.5rem;margin-bottom:1.2em}.highlight{overflow:auto}.highlight .lineno{margin-left:.2rem;padding-right:.5rem;min-width:2.2rem;text-align:right;color:var(--highlight-lineno-color);border-right:1px solid var(--highlight-lineno-border-color);-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;user-select:none}.highlight pre{margin-bottom:0;font-size:.85rem;line-height:1.4rem;word-wrap:normal}.highlight table{padding:0;border:0}.highlight table td pre{overflow:visible;word-break:normal}.highlight td{padding:0;border:0}code{-webkit-hyphens:none;-ms-hyphens:none;-moz-hyphens:none;hyphens:none}code.highlighter-rouge{font-size:.85rem;padding:3px 5px;border-radius:4px;background-color:var(--inline-code-bg)}a>code.highlighter-rouge{padding-bottom:0;color:inherit}a:hover>code.highlighter-rouge{border-bottom:none}blockquote code.highlighter-rouge{color:inherit}td.rouge-code{padding:1.5rem 1.5rem 1.5rem 1rem}td.rouge-code a{color:inherit !important;border-bottom:none !important;pointer-events:none}div[class^=highlighter-rouge] pre.lineno,div.language-plaintext.highlighter-rouge pre.lineno,div.language-console.highlighter-rouge pre.lineno,div.language-terminal.highlighter-rouge pre.lineno{display:none}div[class^=language-]::before{content:attr(lang);position:absolute;right:2rem;margin-top:3px;font-size:.7rem;font-weight:600;color:var(--highlight-lineno-color);text-transform:uppercase}@media(min-width: 768px){div[class^=language-]::before{right:3.1rem}}@media(min-width: 1650px){div[class^=language-]::before{right:3.5rem}}html:not([mode]),html[mode=light]{--body-bg: #fafafa;--mask-bg: #c1c3c5;--main-wrapper-bg: white;--main-border-color: #f3f3f3;--btn-border-color: #e9ecef;--text-color: #34343c;--heading-color: black;--blockquote-border-color: #eee;--blockquote-text-color: #9a9a9a;--link-color: #2a408e;--link-underline-color: #dee2e6;--text-muted-color: gray;--tb-odd-bg: #fbfcfd;--tb-border-color: #eaeaea;--button-bg: #fff;--btn-backtotop-color: #686868;--btn-backtotop-border-color: #f1f1f1;--btn-box-shadow: #eaeaea;--checkbox-color: #c5c5c5;--checkbox-checked-color: #07a8f7;--sidebar-bg: radial-gradient( circle, rgba(42, 30, 107, 1) 0%, rgba(35, 37, 46, 1) 100%);--nav-cursor-color: #fcfcfc;--topbar-wrapper-bg: white;--topbar-text-color: rgb(78, 78, 78);--search-wrapper-bg: #f5f5f5;--search-tag-bg: #f8f9fa;--search-icon-color: #c2c6cc;--input-focus-border-color: var(--btn-border-color);--post-list-text-color: dimgray;--btn-patinator-text-color: #555555;--btn-paginator-hover-color: #e9ecef;--btn-active-bg: #2a408e;--btn-active-border-color: #007bff;--btn-text-color: #f8f8f8;--btn-paginator-border-color: #f1f1f1;--btn-paginator-shadow: #4b92d2;--pin-bg: #f5f5f5;--pin-color: #999fa4;--btn-share-hover-color: var(--link-color);--card-border-color: #f1f1f1;--card-box-shadow: rgba(234, 234, 234, 0.7686274509803922);--label-color: #616161;--relate-post-date: rgba(30, 55, 70, 0.4);--tag-bg: rgba(0, 0, 0, 0.075);--tag-border: #dee2e6;--tag-shadow: var(--btn-border-color);--tag-hover: rgb(222, 226, 230);--categories-hover-bg: var(--btn-border-color);--dash-color: silver;--timeline-color: rgba(0, 0, 0, 0.075);--timeline-node-bg: #c2c6cc;--timeline-year-dot-color: #ffffff;--footer-bg-color: #ffffff;--footnote-target-bg: lightcyan;--footer-link: #424242}html:not([mode]) .mode-toggle,html[mode=light] .mode-toggle{transform:none}html[mode=dark]{--main-wrapper-bg: rgb(27, 27, 30);--body-bg: var(--main-wrapper-bg);--topbar-wrapper-bg: rgb(39, 40, 43);--search-wrapper-bg: rgb(34, 34, 39);--search-icon-color: rgb(100, 102, 105);--input-focus-border-color: rgb(112, 114, 115);--mask-bg: rgb(68, 69, 70);--footer-bg-color: var(--main-wrapper-bg);--text-color: rgb(175, 176, 177);--heading-color: #cccccc;--text-muted-color: rgb(107, 116, 124);--link-color: rgb(138, 180, 248);--link-underline-color: rgb(82, 108, 150);--main-border-color: rgb(44, 45, 45);--button-bg: rgb(39, 40, 43);--blockquote-border-color: rgb(66, 66, 66);--blockquote-text-color: rgb(117, 117, 117);--btn-border-color: rgb(63, 65, 68);--btn-backtotop-color: var(--text-color);--btn-backtotop-border-color: var(--btn-border-color);--btn-box-shadow: var(--main-wrapper-bg);--card-header-bg: rgb(51, 50, 50);--label-color: rgb(108, 117, 125);--checkbox-color: rgb(118 120 121);--checkbox-checked-color: var(--link-color);--nav-cursor-color: rgb(183, 182, 182);--sidebar-bg: radial-gradient(circle, #242424 0%, #1d1f27 100%);--topbar-text-color: var(--text-color);--post-list-text-color: rgb(175, 176, 177);--btn-patinator-text-color: var(--text-color);--btn-paginator-hover-color: rgb(64, 65, 66);--btn-active-bg: rgba(28, 52, 94, 1);--btn-active-border-color: rgb(66, 94, 138);--btn-text-color: var(--text-color);--btn-paginator-border-color: var(--btn-border-color);--btn-paginator-shadow: var(--main-wrapper-bg);--pin-bg: rgb(34 35 37);--pin-color: inherit;--toc-highlight: rgb(116, 178, 243);--tag-bg: rgb(41, 40, 40);--tag-hover: rgb(43, 56, 62);--tb-odd-bg: rgba(42, 47, 53, 0.52);--tb-even-bg: rgb(31, 31, 34);--tb-border-color: var(--tb-odd-bg);--footnote-target-bg: rgb(63, 81, 181);--btn-share-color: #6c757d;--btn-share-hover-color: #bfc1ca;--relate-post-date: var(--text-muted-color);--card-bg: rgb(39, 40, 43);--card-border-color: rgb(53, 53, 60);--card-box-shadow: var(--main-wrapper-bg);--tag-border: rgb(59, 79, 88);--tag-shadow: rgb(32, 33, 33);--search-tag-bg: var(--tag-bg);--dash-color: rgb(63, 65, 68);--categories-border: rgb(64, 66, 69);--categories-hover-bg: rgb(73, 75, 76);--timeline-node-bg: rgb(150, 152, 156);--timeline-color: rgb(63, 65, 68);--timeline-year-dot-color: var(--timeline-color);--footer-link: rgb(171, 171, 171)}html[mode=dark] .post-content img{filter:brightness(90%)}html[mode=dark] hr{border-color:var(--main-border-color)}html[mode=dark] nav[data-toggle=toc] .nav-link.active,html[mode=dark] nav[data-toggle=toc] .nav-link.active:focus,html[mode=dark] nav[data-toggle=toc] .nav-link.active:hover,html[mode=dark] nav[data-toggle=toc] .nav>li>a:focus,html[mode=dark] nav[data-toggle=toc] .nav>li>a:hover{color:var(--toc-highlight) !important;border-left-color:var(--toc-highlight) !important}html[mode=dark] .categories.card,html[mode=dark] .list-group-item{background-color:var(--card-bg)}html[mode=dark] .categories .card-header{background-color:var(--card-header-bg)}html[mode=dark] .categories .list-group-item{border-left:none;border-right:none;padding-left:2rem;border-color:var(--categories-border)}html[mode=dark] .categories .list-group-item:last-child{border-bottom-color:var(--card-bg)}html[mode=dark] #archives li:nth-child(odd){background-image:linear-gradient(to left, rgb(26, 26, 30), rgb(39, 39, 45), rgb(39, 39, 45), rgb(39, 39, 45), rgb(26, 26, 30))}html[mode=dark] .mode-toggle{transform:rotateY(180deg)}@media(prefers-color-scheme: dark){html:not([mode]),html[mode=dark]{--main-wrapper-bg: rgb(27, 27, 30);--body-bg: var(--main-wrapper-bg);--topbar-wrapper-bg: rgb(39, 40, 43);--search-wrapper-bg: rgb(34, 34, 39);--search-icon-color: rgb(100, 102, 105);--input-focus-border-color: rgb(112, 114, 115);--mask-bg: rgb(68, 69, 70);--footer-bg-color: var(--main-wrapper-bg);--text-color: rgb(175, 176, 177);--heading-color: #cccccc;--text-muted-color: rgb(107, 116, 124);--link-color: rgb(138, 180, 248);--link-underline-color: rgb(82, 108, 150);--main-border-color: rgb(44, 45, 45);--button-bg: rgb(39, 40, 43);--blockquote-border-color: rgb(66, 66, 66);--blockquote-text-color: rgb(117, 117, 117);--btn-border-color: rgb(63, 65, 68);--btn-backtotop-color: var(--text-color);--btn-backtotop-border-color: var(--btn-border-color);--btn-box-shadow: var(--main-wrapper-bg);--card-header-bg: rgb(51, 50, 50);--label-color: rgb(108, 117, 125);--checkbox-color: rgb(118 120 121);--checkbox-checked-color: var(--link-color);--nav-cursor-color: rgb(183, 182, 182);--sidebar-bg: radial-gradient(circle, #242424 0%, #1d1f27 100%);--topbar-text-color: var(--text-color);--post-list-text-color: rgb(175, 176, 177);--btn-patinator-text-color: var(--text-color);--btn-paginator-hover-color: rgb(64, 65, 66);--btn-active-bg: rgba(28, 52, 94, 1);--btn-active-border-color: rgb(66, 94, 138);--btn-text-color: var(--text-color);--btn-paginator-border-color: var(--btn-border-color);--btn-paginator-shadow: var(--main-wrapper-bg);--pin-bg: rgb(34 35 37);--pin-color: inherit;--toc-highlight: rgb(116, 178, 243);--tag-bg: rgb(41, 40, 40);--tag-hover: rgb(43, 56, 62);--tb-odd-bg: rgba(42, 47, 53, 0.52);--tb-even-bg: rgb(31, 31, 34);--tb-border-color: var(--tb-odd-bg);--footnote-target-bg: rgb(63, 81, 181);--btn-share-color: #6c757d;--btn-share-hover-color: #bfc1ca;--relate-post-date: var(--text-muted-color);--card-bg: rgb(39, 40, 43);--card-border-color: rgb(53, 53, 60);--card-box-shadow: var(--main-wrapper-bg);--tag-border: rgb(59, 79, 88);--tag-shadow: rgb(32, 33, 33);--search-tag-bg: var(--tag-bg);--dash-color: rgb(63, 65, 68);--categories-border: rgb(64, 66, 69);--categories-hover-bg: rgb(73, 75, 76);--timeline-node-bg: rgb(150, 152, 156);--timeline-color: rgb(63, 65, 68);--timeline-year-dot-color: var(--timeline-color);--footer-link: rgb(171, 171, 171)}html:not([mode]) .post-content img,html[mode=dark] .post-content img{filter:brightness(90%)}html:not([mode]) hr,html[mode=dark] hr{border-color:var(--main-border-color)}html:not([mode]) nav[data-toggle=toc] .nav-link.active,html:not([mode]) nav[data-toggle=toc] .nav-link.active:focus,html:not([mode]) nav[data-toggle=toc] .nav-link.active:hover,html:not([mode]) nav[data-toggle=toc] .nav>li>a:focus,html:not([mode]) nav[data-toggle=toc] .nav>li>a:hover,html[mode=dark] nav[data-toggle=toc] .nav-link.active,html[mode=dark] nav[data-toggle=toc] .nav-link.active:focus,html[mode=dark] nav[data-toggle=toc] .nav-link.active:hover,html[mode=dark] nav[data-toggle=toc] .nav>li>a:focus,html[mode=dark] nav[data-toggle=toc] .nav>li>a:hover{color:var(--toc-highlight) !important;border-left-color:var(--toc-highlight) !important}html:not([mode]) .categories.card,html:not([mode]) .list-group-item,html[mode=dark] .categories.card,html[mode=dark] .list-group-item{background-color:var(--card-bg)}html:not([mode]) .categories .card-header,html[mode=dark] .categories .card-header{background-color:var(--card-header-bg)}html:not([mode]) .categories .list-group-item,html[mode=dark] .categories .list-group-item{border-left:none;border-right:none;padding-left:2rem;border-color:var(--categories-border)}html:not([mode]) .categories .list-group-item:last-child,html[mode=dark] .categories .list-group-item:last-child{border-bottom-color:var(--card-bg)}html:not([mode]) #archives li:nth-child(odd),html[mode=dark] #archives li:nth-child(odd){background-image:linear-gradient(to left, rgb(26, 26, 30), rgb(39, 39, 45), rgb(39, 39, 45), rgb(39, 39, 45), rgb(26, 26, 30))}html:not([mode]) .mode-toggle,html[mode=dark] .mode-toggle{transform:rotateY(180deg)}html[mode=light]{--body-bg: #fafafa;--mask-bg: #c1c3c5;--main-wrapper-bg: white;--main-border-color: #f3f3f3;--btn-border-color: #e9ecef;--text-color: #34343c;--heading-color: black;--blockquote-border-color: #eee;--blockquote-text-color: #9a9a9a;--link-color: #2a408e;--link-underline-color: #dee2e6;--text-muted-color: gray;--tb-odd-bg: #fbfcfd;--tb-border-color: #eaeaea;--button-bg: #fff;--btn-backtotop-color: #686868;--btn-backtotop-border-color: #f1f1f1;--btn-box-shadow: #eaeaea;--checkbox-color: #c5c5c5;--checkbox-checked-color: #07a8f7;--sidebar-bg: radial-gradient( circle, rgba(42, 30, 107, 1) 0%, rgba(35, 37, 46, 1) 100%);--nav-cursor-color: #fcfcfc;--topbar-wrapper-bg: white;--topbar-text-color: rgb(78, 78, 78);--search-wrapper-bg: #f5f5f5;--search-tag-bg: #f8f9fa;--search-icon-color: #c2c6cc;--input-focus-border-color: var(--btn-border-color);--post-list-text-color: dimgray;--btn-patinator-text-color: #555555;--btn-paginator-hover-color: #e9ecef;--btn-active-bg: #2a408e;--btn-active-border-color: #007bff;--btn-text-color: #f8f8f8;--btn-paginator-border-color: #f1f1f1;--btn-paginator-shadow: #4b92d2;--pin-bg: #f5f5f5;--pin-color: #999fa4;--btn-share-hover-color: var(--link-color);--card-border-color: #f1f1f1;--card-box-shadow: rgba(234, 234, 234, 0.7686274509803922);--label-color: #616161;--relate-post-date: rgba(30, 55, 70, 0.4);--tag-bg: rgba(0, 0, 0, 0.075);--tag-border: #dee2e6;--tag-shadow: var(--btn-border-color);--tag-hover: rgb(222, 226, 230);--categories-hover-bg: var(--btn-border-color);--dash-color: silver;--timeline-color: rgba(0, 0, 0, 0.075);--timeline-node-bg: #c2c6cc;--timeline-year-dot-color: #ffffff;--footer-bg-color: #ffffff;--footnote-target-bg: lightcyan;--footer-link: #424242}html[mode=light] .mode-toggle{transform:none}}:root{font-size:16px}body{line-height:1.75rem;background:var(--body-bg);color:var(--text-color);-webkit-font-smoothing:antialiased;font-family:"Source Sans Pro","Microsoft Yahei",sans-serif}h1{font-size:1.8rem}h2{font-size:1.4rem}h3{font-size:1.25rem}h4{font-size:1.15rem}h5{font-size:1.1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:1rem}img{max-width:100%}blockquote{border-left:5px solid var(--blockquote-border-color);padding-left:1rem;color:var(--blockquote-text-color)}kbd{margin:0 .3rem}footer{position:absolute;bottom:0;padding:0 1rem;height:5rem;font-size:.8rem;color:#7a7b7d;background-color:var(--footer-bg-color)}footer>div.d-flex{line-height:1.2rem;width:95%;max-width:1045px;border-top:1px solid var(--main-border-color);margin-bottom:1rem}footer>div.d-flex>div{width:350px}footer a{color:var(--footer-link)}footer a:link{text-decoration:none}footer a:hover{text-decoration:none}footer .footer-right{text-align:right}.access{top:2rem;transition:top .2s ease-in-out;margin-right:1.5rem;margin-top:3rem;margin-bottom:4rem}.access:only-child{position:-webkit-sticky;position:sticky}.access.topbar-down{top:6rem}.access>div{padding-left:1rem;border-left:1px solid var(--main-border-color)}.access>div:not(:last-child){margin-bottom:4rem}.access span{color:var(--label-color);font-size:inherit;font-weight:600;display:block;line-height:1.2;padding-top:.5rem;padding-bottom:.5rem;margin-top:0;margin-bottom:0;letter-spacing:-0.02em}.access .post-content{font-size:.9rem}#access-tags>div.post-content>div{max-width:80%}#access-tags .post-tag{display:inline-block;line-height:1rem;font-size:.85rem;background:none;border:1px solid var(--btn-border-color);border-radius:.8rem;padding:.3rem .5rem;margin:0 .35rem .5rem 0}#access-tags .post-tag:hover{background-color:#2a408e;border-color:#2a408e;color:#fff;transition:none}#access-lastmod li{height:1.8rem;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:1;-webkit-box-orient:vertical;list-style:none}#access-lastmod a{color:inherit}.footnotes>ol{padding-left:2rem;margin-top:.5rem}.footnotes>ol>li:not(:last-child){margin-bottom:.3rem}.footnotes>ol>li>p{margin-left:.25em;margin-top:0;margin-bottom:0}.footnotes>ol>li:target:not([scroll-focus]),.footnotes>ol>li[scroll-focus=true]>p{background-color:var(--footnote-target-bg);width:fit-content;-webkit-transition:background-color 1.5s ease-in-out;transition:background-color 1.5s ease-in-out}a.footnote{margin-left:1px;margin-right:1px;padding-left:2px;padding-right:2px;border-bottom-style:none !important;-webkit-transition:background-color 1.5s ease-in-out;transition:background-color 1.5s ease-in-out}sup:target:not([scroll-focus]),sup[scroll-focus=true]>a.footnote{background-color:var(--footnote-target-bg)}a.reversefootnote{font-size:.6rem;position:absolute;line-height:1;padding-top:.5em;margin-left:.5em;border-bottom-style:none !important}.post h1{margin-top:3rem;margin-bottom:1rem}.post em{padding-right:.2rem}.table-wrapper{overflow-x:auto;margin-bottom:1.5rem}.table-wrapper>table{min-width:100%;overflow-x:auto;border-spacing:0}.table-wrapper>table thead{border-bottom:solid 2px rgba(210,215,217,.75)}.table-wrapper>table tbody tr{border-bottom:1px solid var(--tb-border-color)}.table-wrapper>table tbody tr:nth-child(2n){background-color:var(--tb-even-bg)}.table-wrapper>table tbody tr:nth-child(2n+1){background-color:var(--tb-odd-bg)}.pageviews .fa-spinner{font-size:80%}.post-meta{font-size:.85rem;word-spacing:1px}.post-meta a:not(:last-child){margin-right:2px}.post-content{font-size:1.08rem;line-height:1.8;margin-top:2rem;overflow-wrap:break-word;word-wrap:break-word}.post-content img[data-src]{margin:.5rem 0}.post-content img[data-src]+em{display:block;text-align:center;font-style:normal;font-size:80%;padding:0;color:#6d6c6c}.post-content img[data-src].left{float:left;margin:.75rem 1rem 1rem 0}.post-content img[data-src].right{float:right;margin:.75rem 0 1rem 1rem}.post-content img[data-src].shadow{filter:drop-shadow(2px 4px 6px rgba(0, 0, 0, 0.08))}blockquote .post-content a{color:var(--link-color)}.post-content a.img-link img[data-src]{margin:.5rem 0}.post-content a.img-link img[data-src].left{float:left;margin:.75rem 1rem 1rem 0}.post-content a.img-link img[data-src].right{float:right;margin:.75rem 0 1rem 1rem}.post-content a.img-link img[data-src].shadow{filter:drop-shadow(2px 4px 6px rgba(0, 0, 0, 0.08))}.post-content a.img-link+em{display:block;text-align:center;font-style:normal;font-size:80%;padding:0;color:#6d6c6c}.post-content>p>img[data-src]:not(.normal):not(.left):not(.right){position:relative;left:50%;-webkit-transform:translateX(-50%);-ms-transform:translateX(-50%);transform:translateX(-50%)}.post-content ul .task-list-item[hide-bullet]{list-style-type:none}.post-content ul .task-list-item[hide-bullet]>i{margin:0 .4rem .2rem -1.4rem;vertical-align:middle;color:var(--checkbox-color)}.post-content ul .task-list-item[hide-bullet]>i.checked{color:var(--checkbox-checked-color)}.post-content ul input[type=checkbox]{margin:0 .5rem .2rem -1.3rem;vertical-align:middle}.post-content>ol,.post-content>ul{padding-left:2rem}.post-content>ol li+li,.post-content>ul li+li{margin-top:.3rem}.post-content>ol li ol,.post-content>ol li ul,.post-content>ul li ol,.post-content>ul li ul{padding-left:2rem;margin-top:.3rem}.post-content>ol li{padding-left:.25em}.post-content dl>dd{margin-left:1rem}.post-tag{display:inline-block;min-width:2rem;text-align:center;background:var(--tag-bg);border-radius:.3rem;padding:0 .4rem;color:inherit;line-height:1.3rem}.post-tag:not(:last-child){margin-right:.2rem}.post-tag:hover{border-bottom:none;text-decoration:none;color:#d2603a}.btn-lang{border:1px solid !important;padding:1px 3px;border-radius:3px;color:var(--link-color)}.btn-lang:focus{box-shadow:none}.semi-bold{font-weight:600 !important}.loaded{display:block !important}.d-flex.loaded{display:flex !important}.unloaded{display:none !important}.visible{visibility:visible !important}.hidden{visibility:hidden !important}.flex-grow-1{-ms-flex-positive:1 !important;flex-grow:1 !important}.btn-box-shadow{box-shadow:0 0 8px 0 var(--btn-box-shadow) !important}.topbar-up{top:-3rem !important}.no-text-decoration{text-decoration:none}.tooltip-inner{font-size:.7rem;max-width:220px;text-align:left}.disabled{color:#cec4c4;pointer-events:auto;cursor:not-allowed}.hide-border-bottom{border-bottom:none !important}.input-focus{box-shadow:none;border-color:var(--input-focus-border-color) !important;background:center !important;transition:background-color .15s ease-in-out,border-color .15s ease-in-out}#sidebar{padding-left:0;padding-right:0;position:fixed;top:0;left:0;height:100%;overflow-y:auto;width:260px;z-index:99;background:var(--sidebar-bg);-ms-overflow-style:none;scrollbar-width:none}#sidebar::-webkit-scrollbar{display:none}#sidebar a{color:rgba(255,255,255,.5);transition:color .35s ease-in-out;user-select:none}#sidebar a:hover{text-decoration:none;color:#fff}#sidebar #avatar:hover>a{border-color:#fff}#sidebar #avatar>a{display:block;width:6rem;height:6rem;border-radius:50%;border:2px solid #b6b6b6;overflow:hidden;transform:translateZ(0);-webkit-transition:border-color .35s ease-in-out;-moz-transition:border-color .35s ease-in-out;transition:border-color .35s ease-in-out}#sidebar #avatar img{width:100%;height:100%;-webkit-transition:transform .5s;-moz-transition:transform .5s;transition:transform .5s}#sidebar #avatar img:hover{-ms-transform:scale(1.2);-moz-transform:scale(1.2);-webkit-transform:scale(1.2);transform:scale(1.2)}#sidebar .site-title a{font-weight:900;font-size:1.5rem;letter-spacing:.5px}#sidebar .site-subtitle{font-size:95%;color:#828282;line-height:1.2rem;word-spacing:1px;margin:.5rem 1.5rem .5rem 1.5rem;min-height:3rem;user-select:none}#sidebar .nav-link{border-radius:0;font-size:.95rem;font-weight:600;letter-spacing:1px;display:table-cell;vertical-align:middle}#sidebar .nav-item{text-align:center;display:table;height:3.2rem}#sidebar .nav-item:hover .nav-link{color:rgba(248,249,250,.8117647059)}#sidebar .nav-item.active .nav-link{color:#fcfcfc}#sidebar ul{height:12.8rem;margin-bottom:2rem;padding-left:0}#sidebar ul li{width:100%}#sidebar ul li:last-child a{position:relative;left:1.5px;width:100%}#sidebar ul li:last-child::after{display:table;visibility:hidden;content:"";position:relative;right:1px;width:3px;height:1.6rem;border-radius:1px;background-color:var(--nav-cursor-color);pointer-events:none}#sidebar ul>li.active:nth-child(1)~li:last-child::after,#sidebar ul>li.nav-item:nth-child(1):hover~li:last-child::after{top:-8.8rem;visibility:visible}#sidebar ul>li.active:nth-child(2)~li:last-child::after,#sidebar ul>li.nav-item:nth-child(2):hover~li:last-child::after{top:-5.6rem;visibility:visible}#sidebar ul>li.active:nth-child(3)~li:last-child::after,#sidebar ul>li.nav-item:nth-child(3):hover~li:last-child::after{top:-2.4rem;visibility:visible}#sidebar ul>li.active:nth-child(4):last-child::after,#sidebar ul>li.nav-item:nth-child(4):last-child:hover::after{top:.8rem;visibility:visible}#sidebar .sidebar-bottom{font-size:1.2rem;margin-bottom:2.1rem;margin-left:auto;margin-right:auto;padding-left:1rem;padding-right:1rem}#sidebar .sidebar-bottom #mode-toggle-wrapper,#sidebar .sidebar-bottom a{width:2.4rem;text-align:center}#sidebar .sidebar-bottom #mode-toggle-wrapper i{color:rgba(255,255,255,.5);transition:color .35s ease-in-out;user-select:none;margin:0;font-size:1.05rem;text-align:center;position:relative;bottom:1px}#sidebar .sidebar-bottom .icon-border{background-color:#525354;content:"";width:3px;height:3px;border-radius:50%;position:relative;top:12px}#sidebar .sidebar-bottom a:hover,#sidebar .sidebar-bottom #mode-toggle-wrapper i:hover{color:#fff}@media(hover: hover){#sidebar ul>li:last-child::after{-webkit-transition:top .5s ease;-moz-transition:top .5s ease;-o-transition:top .5s ease;transition:top .5s ease}}.profile-wrapper{margin-top:2rem;width:100%}#search-result-wrapper{display:none;height:100%;overflow:auto}#search-result-wrapper .post-content{margin-top:2rem}#topbar-wrapper{height:3rem;position:fixed;top:0;left:260px;right:0;transition:top .2s ease-in-out;z-index:50;border-bottom:1px solid rgba(0,0,0,.07);box-shadow:0 3px 5px 0 rgba(0,0,0,.05);background-color:var(--topbar-wrapper-bg)}#topbar i{color:#999}#topbar #breadcrumb{font-size:1rem;color:gray;padding-left:.5rem}#topbar #breadcrumb span:not(:last-child)::after{content:"›";padding:0 .3rem}#sidebar-trigger,#search-trigger{display:none}#search-wrapper{display:flex;width:95%;border-radius:1rem;border:1px solid var(--search-wrapper-bg);background:var(--search-wrapper-bg);padding:0 .5rem}#search-wrapper i{z-index:2;font-size:.9rem;color:var(--search-icon-color)}#search-wrapper .fa-times-circle{visibility:hidden}#search-cancel{color:var(--link-color);margin-left:1rem;display:none}#search-input{background:center;border:0;border-radius:0;padding:.18rem .3rem;color:var(--text-color)}#search-input:focus{box-shadow:none;background:center}#search-input:focus.form-control::-webkit-input-placeholder{opacity:.6}#search-input:focus.form-control::-moz-placeholder{opacity:.6}#search-input:focus.form-control:-ms-input-placeholder{opacity:.6}#search-input:focus.form-control::placeholder{opacity:.6}#search-hints{display:none}#search-hints .post-tag{display:inline-block;line-height:1rem;font-size:1rem;background:var(--search-tag-bg);border:none;padding:.5rem;margin:0 1rem 1rem 0}#search-hints .post-tag::before{content:"#";color:var(--text-muted-color);padding-right:.2rem}#search-results{padding-bottom:6rem}#search-results a{font-size:1.4rem;line-height:2.5rem}#search-results>div{width:100%}#search-results>div:not(:last-child){margin-bottom:1rem}#search-results>div i{color:#818182;margin-right:.15rem;font-size:80%}#search-results>div>p{overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical}#topbar-title{display:none;font-size:1.1rem;font-weight:600;font-family:sans-serif;color:var(--topbar-text-color);text-align:center;width:70%;overflow:hidden;text-overflow:ellipsis;word-break:keep-all;white-space:nowrap}#mask{display:none;position:fixed;top:0;right:0;bottom:0;left:0;height:100%;width:100%;z-index:1}[sidebar-display] #mask{display:block !important}#main-wrapper{background-color:var(--main-wrapper-bg);position:relative;min-height:100vh;padding-bottom:5rem;padding-left:0;padding-right:0}#main>div.row:first-child>div:nth-child(1),#main>div.row:first-child>div:nth-child(2){margin-top:3rem}#main>div.row:first-child>div:first-child{min-height:calc(100vh - 3rem - 5rem - 35rem)}#post-wrapper{min-height:calc( + 100vh - 3rem - 5rem - 35rem) !important}#topbar-wrapper.row,#main>.row,#search-result-wrapper>.row{margin-left:0;margin-right:0}#back-to-top{display:none;z-index:1;cursor:pointer;position:fixed;background:var(--button-bg);color:var(--btn-backtotop-color);height:2.6em;width:2.7em;border-radius:50%;border:1px solid var(--btn-backtotop-border-color);transition:.2s ease-out;-webkit-transition:.2s ease-out}#back-to-top:hover{transform:translate3d(0, -5px, 0);-webkit-transform:translate3d(0, -5px, 0)}@media all and (max-width: 576px){#main>div.row:first-child>div:first-child{min-height:calc(100vh - 3rem - 6rem)}#post-wrapper{min-height:calc( + 100vh - 3rem - 6rem - 35rem) !important}#post-wrapper h1{margin-top:2.2rem;font-size:1.55rem}#avatar>a{width:5rem;height:5rem}.site-subtitle{margin-left:1.8rem;margin-right:1.8rem}#main-wrapper{padding-bottom:6rem}footer{height:6rem}footer>div.d-flex{width:100%;padding:1.5rem 0;margin-bottom:.3rem;flex-wrap:wrap;-ms-flex-pack:distribute !important;justify-content:space-around !important}footer .footer-left,footer .footer-right{text-align:center}}@media all and (max-width: 849px){#topbar-wrapper,#main-wrapper,#sidebar{-webkit-transition:transform .4s ease;transition:transform .4s ease}html,body{overflow-x:hidden}.footnotes ol>li{padding-top:3.5rem;margin-top:-3.2rem}.footnotes ol>li:first-child{margin-top:-3.5rem}[sidebar-display] #sidebar{transform:translateX(0)}[sidebar-display] #topbar-wrapper,[sidebar-display] #main-wrapper{transform:translateX(260px)}#sidebar{transform:translateX(-260px);-webkit-transform:translateX(-260px)}#sidebar .cursor{-webkit-transition:none;-moz-transition:none;transition:none}#main-wrapper{padding-top:3rem}#search-result-wrapper{width:100%}#breadcrumb,#search-wrapper{display:none}#topbar-wrapper{left:0}.topbar-up{top:0 !important}#main>div.row:first-child>div:nth-child(1),#main>div.row:first-child>div:nth-child(2){margin-top:0}#topbar-title,#sidebar-trigger,#search-trigger{display:block}#search-wrapper.loaded~a{margin-right:1rem}#search-wrapper .fa-times-circle{right:5.2rem}#search-input{margin-left:0;width:95%}#search-result-wrapper .post-content{letter-spacing:0}#search-hints{display:block;padding:0 1rem}#tags{-webkit-box-pack:center !important;-ms-flex-pack:center !important;justify-content:center !important}#page h1.dynamic-title{display:none}#page h1.dynamic-title~.post-content{margin-top:3rem}}@media all and (min-width: 577px)and (max-width: 1199px){footer>.d-flex>div{width:312px}}@media all and (min-width: 850px){html{overflow-y:scroll}#main-wrapper{margin-left:260px}.profile-wrapper{margin-top:3rem}#search-wrapper{width:22%;min-width:150px}#search-result-wrapper{margin-top:3rem}div.post-content .table-wrapper>table{min-width:70%}#back-to-top{bottom:5.5rem;right:1.2rem}.topbar-up{box-shadow:none !important}#topbar-title{text-align:left}footer>div.d-flex{width:92%}}@media all and (min-width: 992px)and (max-width: 1199px){#main .col-lg-11{-webkit-box-flex:0;-ms-flex:0 0 96%;flex:0 0 96%;max-width:96%}}@media all and (min-width: 850px)and (max-width: 1199px){#sidebar{width:210px}#sidebar .site-subtitle{margin-left:1rem;margin-right:1rem}#sidebar .sidebar-bottom a,#sidebar .sidebar-bottom span{width:2rem}#sidebar .sidebar-bottom .icon-border{left:-3px}#topbar-wrapper{left:210px}#search-results>div{max-width:700px}.site-title{font-size:1.3rem;margin-left:0 !important}.site-subtitle{margin-left:1rem;margin-right:1rem;font-size:90%}#main-wrapper{margin-left:210px}#breadcrumb{width:65%;overflow:hidden;text-overflow:ellipsis;word-break:keep-all;white-space:nowrap}}@media all and (max-width: 1199px){#panel-wrapper{display:none}#topbar{padding:0}#main>div.row{-webkit-box-pack:center !important;-ms-flex-pack:center !important;justify-content:center !important}}@media all and (min-width: 1200px){#main>div.row>div.col-xl-8{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%;padding-left:3%}#topbar{padding:0}#panel-wrapper{max-width:300px}#back-to-top{bottom:6.5rem;right:4.3rem}#search-input{-webkit-transition:all .3s ease-in-out;transition:all .3s ease-in-out}#search-results>div{width:46%}#search-results>div:nth-child(odd){margin-right:1.5rem}#search-results>div:nth-child(even){margin-left:1.5rem}#search-results>div:last-child:nth-child(odd){position:relative;right:24.3%}.post-content{font-size:1.03rem}footer>div.d-felx{width:85%}}@media all and (min-width: 1400px){#main>div.row{padding-left:calc((100% - 1150px)/2)}#main>div.row>div.col-xl-8{max-width:calc(100% - 340px)}#search-result-wrapper{padding-right:2rem}#search-result-wrapper>div{max-width:1110px}#search-wrapper .fa-times-circle{right:2.6rem}}@media all and (min-width: 1400px)and (max-width: 1650px){#topbar{padding-right:2rem}}@media all and (min-width: 1650px){#breadcrumb{padding-left:0}#main>div.row>div.col-xl-8{padding-left:0}#main>div.row>div.col-xl-8>div:first-child{padding-left:.55rem !important;padding-right:1.9rem !important}#main-wrapper{margin-left:350px}#topbar-wrapper{left:350px}#search-wrapper{margin-right:3%}#sidebar{width:350px}#sidebar .profile-wrapper{margin-top:4rem;margin-bottom:1rem}#sidebar .profile-wrapper.text-center{text-align:left !important}#sidebar .profile-wrapper .site-subtitle,#sidebar .profile-wrapper .site-title,#sidebar .profile-wrapper #avatar{margin-left:4.5rem}#sidebar .profile-wrapper #avatar>a{width:6.2rem;height:6.2rem}#sidebar .profile-wrapper #avatar>a.mx-auto{margin-left:0 !important}#sidebar .profile-wrapper .site-title a{font-size:1.7rem;letter-spacing:1px}#sidebar .profile-wrapper .site-subtitle{word-spacing:0;margin-top:.3rem}#sidebar ul{padding-left:2.5rem}#sidebar ul>li:last-child>a{position:static}#sidebar ul .nav-item{text-align:left}#sidebar ul .nav-item .nav-link>span{letter-spacing:3px}#sidebar ul .nav-item .nav-link>i{border:1px solid;border-radius:50%;width:1.65rem;height:1.65rem;line-height:1.5rem;font-size:.6rem;padding-top:1px;padding-left:1px;position:relative;bottom:1px}#sidebar ul .nav-item .nav-link>i.unloaded{display:inline-block !important}#sidebar .sidebar-bottom{padding-left:3.5rem;width:100%}#sidebar .sidebar-bottom.justify-content-center{-webkit-box-pack:start !important;-ms-flex-pack:start !important;justify-content:flex-start !important}#sidebar .sidebar-bottom a{font-size:1rem;width:3rem}#sidebar .sidebar-bottom i{border:1px solid;border-radius:50%;width:2rem;height:2rem;padding-top:.44rem;margin-top:.7rem;bottom:0}#sidebar .sidebar-bottom #mode-toggle-wrapper{width:3rem}#sidebar .sidebar-bottom #mode-toggle-wrapper i{top:11px}#sidebar .sidebar-bottom .icon-border{top:26px}footer>div.d-flex{width:92%;max-width:1140px}#search-result-wrapper>div{max-width:1150px}}@media all and (min-width: 1700px){#main>div.row{padding-left:calc((100% - 1150px - 2%)/2)}#panel-wrapper{margin-left:3%}footer{padding-left:0;padding-right:calc(100% - 350px - 1180px)}#back-to-top{right:calc(100% - 1920px + 15rem)}}@media(min-width: 1920px){#main>div.row{padding-left:190px}#search-result-wrapper{padding-right:calc(100% - 350px - 1180px)}#panel-wrapper{margin-left:41px}}.pagination{font-size:1rem}.pagination a:hover{text-decoration:none}.pagination .page-item .page-link{color:var(--btn-patinator-text-color);width:2.5rem;height:2.5rem;padding:0;text-align:center;display:-webkit-box;display:flex;-webkit-box-pack:center;justify-content:center;-webkit-box-align:center;align-items:center;border-radius:50%;border:1px solid var(--btn-paginator-border-color);font-family:"Lato",sans-serif;background-color:var(--button-bg)}.pagination .page-item .page-link:hover{background-color:var(--btn-paginator-hover-color)}.pagination .page-item.active .page-link{background-color:var(--btn-active-bg);border-color:var(--btn-active-border-color);box-shadow:0 0 8px 0 var(--btn-paginator-shadow) !important;color:var(--btn-text-color)}.pagination .page-item.disabled{cursor:not-allowed}.pagination .page-item.disabled .page-link{color:rgba(108,117,125,.57);border-color:var(--btn-paginator-border-color);background-color:var(--button-bg)}.pagination .page-item:first-child .page-link,.pagination .page-item:last-child .page-link{border-radius:50%}.pagination .page-item:not(:last-child){margin-right:.7rem}#post-list{margin-top:1rem;padding-right:.5rem}#post-list .post-preview{padding-top:1.5rem;padding-bottom:1rem;border-bottom:1px solid var(--main-border-color)}#post-list .post-preview h1{font-size:1.4rem;margin:0}#post-list .post-preview .post-meta i{font-size:.73rem}#post-list .post-preview .post-meta span:not(:last-child){margin-right:1.2rem}#post-list .post-preview .post-content{margin-top:.6rem;margin-bottom:.6rem;color:var(--post-list-text-color)}#post-list .post-preview .post-content>p{margin:0;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}#post-list .post-preview .pin>i{transform:rotate(45deg);padding-left:3px;color:var(--pin-color)}#post-list .post-preview .pin>span{display:none}@media all and (max-width: 830px){.pagination{justify-content:center}}@media all and (min-width: 831px){#post-list{margin-top:1.5rem}#post-list .post-preview .post-meta .pin{background:var(--pin-bg);border-radius:5px;line-height:1.4rem;height:1.3rem;margin-top:3px;padding-left:1px;padding-right:6px}#post-list .post-preview .post-meta .pin>span{display:inline}.pagination{font-size:.85rem}.pagination .page-item .page-link{width:2.2rem;height:2.2rem}}@media all and (max-width: 1200px){#post-list{padding-right:0}}.timeago::before{content:attr(prefix)}#post-wrapper .post-meta>div:nth-child(2)>span:not(:first-child)::before{content:"•";color:rgba(158,158,158,.8);padding-left:.2rem;padding-right:.4rem}#post-wrapper .post-meta #pv::after{content:" views"}#post-wrapper .post-meta .readtime::after{content:" read"}.post-content .preview-img{position:relative;left:50%;-webkit-transform:translateX(-50%);-ms-transform:translateX(-50%);transform:translateX(-50%);margin-top:0;margin-bottom:2.5rem !important}.post-content h1,.post-content h2,.post-content h3{font-weight:bold}.post-tail-wrapper{margin-top:6rem;border-bottom:1px double var(--main-border-color);font-size:.85rem}.post-tags{line-height:2rem}.post-navigation{padding-top:3rem;padding-bottom:4rem}.post-navigation .btn{width:50%;position:relative;border-color:var(--btn-border-color);color:var(--link-color)}.post-navigation .btn:hover{background:#2a408e;color:#fff;border-color:#2a408e}.post-navigation .btn.disabled{width:50%;position:relative;border-color:var(--btn-border-color);pointer-events:auto;cursor:not-allowed;background:none;color:gray}.post-navigation .btn.disabled:hover{border-color:none}.post-navigation .btn.btn-outline-primary.disabled:focus{box-shadow:none}.post-navigation .btn::before{color:var(--text-muted-color);font-size:.65rem;text-transform:uppercase;content:attr(prompt)}.post-navigation .btn:first-child{border-top-right-radius:0;border-bottom-right-radius:0;left:.5px}.post-navigation .btn:last-child{border-top-left-radius:0;border-bottom-left-radius:0;right:.5px}.post-navigation p{font-size:1.1rem;line-height:1.5rem;margin-top:.3rem;white-space:normal}@keyframes fade-up{from{opacity:0;position:relative;top:2rem}to{opacity:1;position:relative;top:0}}#toc-wrapper{border-left:1px solid rgba(158,158,158,.17);position:-webkit-sticky;position:sticky;top:4rem;transition:top .2s ease-in-out;animation:fade-up .8s}#toc-wrapper.topbar-down{top:6rem}#toc-wrapper>span{color:var(--label-color);font-size:inherit;font-weight:600;display:block;line-height:1.2;padding-top:.5rem;padding-bottom:.5rem;margin-top:0;margin-bottom:0;letter-spacing:-0.02em}#toc li>a{line-height:1rem;padding-top:.5rem;padding-bottom:.5rem}#toc li>a.nav-link:not(.active){color:inherit}#related-posts>h3{color:var(--label-color);font-size:1.1rem;font-weight:600}#related-posts .card{border-color:var(--card-border-color);background-color:var(--card-bg);box-shadow:0 0 5px 0 var(--card-box-shadow);-webkit-transition:all .3s ease-in-out;-moz-transition:all .3s ease-in-out;transition:all .3s ease-in-out}#related-posts .card h3{color:var(--text-color)}#related-posts .card:hover{-webkit-transform:translate3d(0, -3px, 0);transform:translate3d(0, -3px, 0);box-shadow:0 10px 15px -4px rgba(0,0,0,.15)}#related-posts .timeago{color:var(--relate-post-date)}#related-posts p{font-size:.9rem;margin-bottom:.5rem;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}#related-posts a:hover{text-decoration:none}#related-posts ul{list-style-type:none;padding-inline-start:1.5rem}#related-posts ul>li::before{background:#c2c9d4;width:5px;height:5px;border-radius:1px;display:block;content:"";position:relative;top:1rem;right:1rem}#post-extend-wrapper{min-height:2rem}#post-extend-wrapper #disqus_thread{margin-bottom:2rem}.post-tail-bottom a{color:inherit}.share-wrapper .share-icons>i:hover,.share-wrapper .share-icons a:hover>i{color:var(--btn-share-hover-color) !important}.share-wrapper{vertical-align:middle;user-select:none}.share-wrapper .share-icons{font-size:1.2rem}.share-wrapper .share-icons a:not(:last-child){margin-right:.25rem}.share-wrapper .share-icons a:hover{text-decoration:none}.share-wrapper .share-icons>i{padding-top:.35rem}.share-wrapper .share-icons .fab.fa-twitter{color:var(--btn-share-color, rgb(29, 161, 242))}.share-wrapper .share-icons .fab.fa-facebook-square{color:var(--btn-share-color, rgb(66, 95, 156))}.share-wrapper .share-icons .fab.fa-telegram{color:var(--btn-share-color, rgb(39, 159, 217))}.share-wrapper .share-icons .fab.fa-weibo{color:var(--btn-share-color, rgb(229, 20, 43))}.share-wrapper .fas.fa-link{color:var(--btn-share-color, rgb(171, 171, 171))}.share-label{color:inherit;font-size:inherit;font-weight:400}.share-label::after{content:":"}.license-wrapper{line-height:1.2rem}.license-wrapper>a{font-weight:600}.license-wrapper span:last-child{font-size:.85rem}@media all and (max-width: 576px){.post-tail-bottom{-ms-flex-wrap:wrap-reverse !important;flex-wrap:wrap-reverse !important}.post-tail-bottom>div:first-child{width:100%;margin-top:1rem}.post-content>div[class^=language-]{margin-left:-1.25rem;margin-right:-1.25rem;border-radius:0}.post-content>div[class^=language-]::before{right:1rem}}@media all and (max-width: 768px){.post-content>p>img{max-width:calc(100% + 1rem)}}@media all and (min-width: 768px){#post-wrapper .post-meta>div:not(:first-child)::before{content:"•";color:rgba(158,158,158,.8);padding-left:.5rem;padding-right:.2rem}#post-wrapper .post-meta.flex-column{-webkit-box-orient:horizontal !important;-webkit-box-direction:normal !important;-ms-flex-direction:row !important;flex-direction:row !important}}@media all and (max-width: 830px){.post-navigation{padding-left:0;padding-right:0;margin-left:-0.5rem;margin-right:-0.5rem}}.tag{border-radius:.7em;padding:6px 8px 7px;margin-right:.8rem;line-height:3rem;letter-spacing:0;border:1px solid var(--tag-border) !important;box-shadow:0 0 3px 0 var(--tag-shadow)}.tag span{margin-left:.6em;font-size:.7em;font-family:"Oswald",sans-serif}#archives ul li:first-child::before,#archives ul li::after{content:"";width:4px;left:75px;display:inline-block;float:left;position:relative;background-color:var(--timeline-color)}#archives{letter-spacing:.03rem}#archives span.lead{font-size:1.5rem;position:relative;left:8px}#archives span.lead::after{content:"";display:block;position:relative;-webkit-border-radius:50%;-moz-border-radius:50%;border-radius:50%;width:12px;height:12px;top:-26px;left:63px;border:3px solid;background-color:var(--timeline-year-dot-color);border-color:var(--timeline-node-bg);box-shadow:0 0 2px 0 #c2c6cc;z-index:1}#archives span.lead:not(:first-child){position:relative;left:4px}#archives span.lead:not(:first-child)::after{left:67px}#archives ul li{font-size:1.1rem;line-height:3rem}#archives ul li div{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}#archives ul li div a{margin-left:2.5rem;position:relative;top:.1rem}#archives ul li:nth-child(odd){background-color:var(--main-wrapper-bg, #fff);background-image:linear-gradient(to left, #fff, #fbfbfb, #fbfbfb, #fbfbfb, #fff)}#archives ul li::after{height:2.8rem;top:-1.3rem}#archives ul li:first-child::before{height:3.06rem;top:-1.61rem}#archives ul:not(:last-child)>li:last-child::after{height:3.4rem}#archives ul:last-child>li:last-child::after{display:none}#archives .date{white-space:nowrap;display:inline-block}#archives .date.month{width:1.4rem;text-align:center}#archives .date.month~a::before{content:"";display:inline-block;position:relative;-webkit-border-radius:50%;-moz-border-radius:50%;border-radius:50%;width:8px;height:8px;float:left;top:1.35rem;left:69px;background-color:var(--timeline-node-bg);box-shadow:0 0 3px 0 #c2c6cc;z-index:1}#archives .date.day{font-size:85%;font-family:"Lato",sans-serif;text-align:center;margin-right:-2px;width:1.2rem;position:relative;left:-0.15rem}@media all and (max-width: 576px){#archives{margin-top:-1rem}#archives ul{letter-spacing:0}}.categories .card-header>span>i:first-child,.categories .list-group-item>i{color:gray}.categories{margin-bottom:2rem}.categories .card-header{padding-right:12px}.categories i.far,.categories i.fas{font-size:86%}.categories .list-group-item{border-left:none;border-right:none;padding-left:2rem}.categories .list-group-item:first-child{border-top-left-radius:0;border-top-right-radius:0}.category-trigger{width:1.7rem;height:1.7rem;border-radius:50%;text-align:center;color:#6c757d !important}.category-trigger>i.fas{position:relative;height:.7rem;width:1rem;transition:300ms ease all}@media(hover: hover){.category-trigger:hover{background-color:var(--categories-hover-bg)}}.rotate{-ms-transform:rotate(-90deg);-webkit-transform:rotate(-90deg);transform:rotate(-90deg)}.dash{margin:0 .5rem .6rem .5rem;border-bottom:2px dotted var(--dash-color)}#page-category ul>li,#page-tag ul>li{line-height:1.5rem;padding:.6rem 0}#page-category ul>li::before,#page-tag ul>li::before{background:#999;width:5px;height:5px;border-radius:50%;display:block;content:"";position:relative;top:.6rem;margin-right:.5rem}#page-category ul>li>a,#page-tag ul>li>a{font-size:1.1rem}#page-category ul>li>span:last-child,#page-tag ul>li>span:last-child{white-space:nowrap}#page-tag h1>i{font-size:1.2rem}#page-category h1>i{font-size:1.25rem}#page-category a:hover,#page-tag a:hover,#access-lastmod a:hover{margin-bottom:-1px}@media all and (max-width: 576px){#page-category ul>li::before,#page-tag ul>li::before{margin:0 .5rem}#page-category ul>li>a,#page-tag ul>li>a{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}}/*# sourceMappingURL=style.css.map */ \ No newline at end of file diff --git a/assets/css/style.css.map b/assets/css/style.css.map new file mode 100644 index 000000000..a5db9a6aa --- /dev/null +++ b/assets/css/style.css.map @@ -0,0 +1 @@ +{"version":3,"sourceRoot":"","sources":["../../_sass/jekyll-theme-chirpy.scss","../../_sass/addon/commons.scss","../../_sass/addon/module.scss","../../_sass/addon/syntax.scss","../../_sass/colors/light-syntax.scss","../../_sass/colors/dark-syntax.scss","../../_sass/colors/light-typography.scss","../../_sass/colors/dark-typography.scss","../../_sass/addon/variables.scss","../../_sass/layout/home.scss","../../_sass/layout/post.scss","../../_sass/layout/tags.scss","../../_sass/layout/archives.scss","../../_sass/layout/categories.scss","../../_sass/layout/category-tag.scss"],"names":[],"mappings":"CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GCGQ,4GCGR,iCACE,2BACA,gBACA,gDAGF,2BACE,4BACA,uCAGF,+DACE,mBACA,cACA,mBAGF,yQACE,yBACA,gCACA,qBAGF,gEACE,wBAGF,4CACE,oDAGF,qKACE,mBAIA,oEACE,gBACA,mBAIJ,YACE,mBACA,mBC3CF,kCC8DE,8BACA,mCACA,kCACA,yCACA,0BAnEA,wFACA,2FACA,yGACA,0FACA,0FACA,6FACA,8GACA,6FACA,8GACA,oGACA,6FACA,2EACA,2EACA,iGACA,2EACA,2EACA,iFACA,2EACA,2EACA,4FACA,4FACA,4FACA,4FACA,4FACA,4FACA,yEACA,4EACA,2EACA,8EACA,4FACA,2EACA,+FACA,6EACA,4FACA,4FACA,4FACA,2EACA,2EACA,2EACA,4FACA,yEACA,2EACA,2EACA,2EACA,2EACA,8EACA,8EACA,8EACA,8EACA,8EACA,8EACA,8EACA,8EACA,8EACA,8EACA,8EACA,2EACA,2EACA,2EACA,2EACA,2EDtDF,gBENE,8BACA,mCACA,kCACA,yCACA,0BAGE,6CAGF,kCACA,0CAGA,0EACA,2EACA,4CACA,uEACA,4CACA,4CACA,4CACA,4CACA,4CACA,6CACA,6CACA,6CACA,6CACA,+DACA,gDACA,6CACA,6CACA,6CACA,6CACA,6CACA,6CACA,6CACA,4CACA,4CACA,6CACA,6CACA,6CACA,6CACA,6CACA,6CACA,6CACA,6CACA,6CACA,6CACA,6CACA,6CACA,6CACA,6CACA,6CACA,4CACA,6CACA,6CACA,6CACA,6CACA,6CACA,6CACA,6CACA,6CACA,6CACA,6CACA,6CACA,6CACA,6CACA,6CACA,6CACA,6CACA,6CACA,6CACA,6CACA,6CACA,6CACA,sEACA,sEFlEF,mCACE,iCEXA,8BACA,mCACA,kCACA,yCACA,0BAGE,6EAGF,uDACA,+DAGA,0GACA,4GACA,2EACA,wGACA,2EACA,2EACA,2EACA,2EACA,2EACA,6EACA,6EACA,6EACA,6EACA,+FACA,gFACA,6EACA,6EACA,6EACA,6EACA,6EACA,6EACA,6EACA,2EACA,2EACA,6EACA,6EACA,6EACA,6EACA,6EACA,6EACA,6EACA,6EACA,6EACA,6EACA,6EACA,6EACA,6EACA,6EACA,6EACA,2EACA,6EACA,6EACA,6EACA,6EACA,6EACA,6EACA,6EACA,6EACA,6EACA,6EACA,6EACA,6EACA,6EACA,6EACA,6EACA,6EACA,6EACA,6EACA,6EACA,6EACA,6EACA,sGACA,sGF5DA,iBC+CA,8BACA,mCACA,kCACA,yCACA,0BAnEA,uDACA,4DACA,wEACA,2DACA,2DACA,6DACA,8EACA,6DACA,8EACA,oEACA,6DACA,2CACA,2CACA,iEACA,2CACA,2CACA,iDACA,2CACA,2CACA,4DACA,4DACA,4DACA,4DACA,4DACA,4DACA,0CACA,6CACA,2CACA,8CACA,4DACA,2CACA,+DACA,6CACA,4DACA,4DACA,4DACA,2CACA,2CACA,2CACA,4DACA,0CACA,2CACA,2CACA,2CACA,2CACA,8CACA,8CACA,8CACA,8CACA,8CACA,8CACA,8CACA,8CACA,8CACA,8CACA,8CACA,2CACA,2CACA,2CACA,2CACA,4CDrCF,uDACE,qCAGF,sCACE,kBAGF,sNACE,eAWF,mBAIE,qCACA,iBACA,oBAGF,WAQE,cACA,mBACE,kBACA,oBACA,iBACA,iBACA,oCACA,4DACA,yBACA,wBACA,sBACA,qBACA,oBACA,iBAEF,eACE,gBACA,UA1Ca,OA2Cb,mBACA,iBAEF,iBACE,UACA,SACA,wBACE,iBACA,kBAGJ,cACE,UACA,SAIJ,KACE,qBACA,iBACA,kBACA,aAEA,uBACE,UAnEa,OAoEb,gBACA,kBACA,uCAGO,yBACP,iBACA,cAGO,+BACP,mBAGF,kCACE,cAIJ,cACE,kCAIA,gBACE,yBACA,8BACA,oBAWA,kMACE,aAQN,8BACE,mBACA,kBACA,WACA,eACA,gBACA,gBACA,oCACA,yBAGF,yBACE,8BACE,cAIJ,0BACE,8BACE,cFxJJ,kCKjBE,mBACA,mBACA,yBACA,6BACA,4BACA,sBACA,uBACA,gCACA,iCACA,sBACA,gCACA,yBACA,qBACA,2BACA,kBACA,+BACA,sCACA,0BACA,0BACA,kCAGA,0FAIA,4BAGA,2BACA,qCACA,6BACA,yBACA,6BACA,oDAGA,gCACA,oCACA,qCACA,yBACA,mCACA,0BACA,sCACA,gCACA,kBACA,qBAGA,2CACA,6BACA,2DACA,uBACA,0CACA,+BACA,sBACA,sCACA,gCACA,+CACA,qBAGA,uCACA,4BACA,mCAGA,2BACA,gCACA,uBL3DE,4DACE,eAWN,gBMtBE,mCACA,kCACA,qCACA,qCACA,wCACA,+CACA,2BACA,0CAGA,iCACA,yBACA,uCACA,iCACA,0CACA,qCACA,6BACA,2CACA,4CACA,oCACA,yCACA,sDACA,yCACA,kCACA,kCACA,mCACA,4CAGA,uCACA,gEAGA,uCAGA,2CACA,8CACA,6CACA,qCACA,4CACA,oCACA,sDACA,+CACA,wBACA,qBAGA,oCACA,0BACA,6BACA,oCACA,8BACA,oCACA,uCACA,2BACA,iCACA,4CACA,2BACA,qCACA,0CAGA,8BACA,8BACA,+BACA,8BAGA,qCACA,uCAGA,uCACA,kCACA,iDAGA,kCAEA,kCACE,uBAGF,mBACE,sCAIF,wRAKE,sCACA,kDAIF,kEAEE,gCAIA,yCACE,uCAEF,6CACE,iBACA,kBACA,kBACA,sCACA,wDACE,mCAKN,4CACE,+HNrHA,6BACE,0BAsBN,mCACE,iCM3BA,mCACA,kCACA,qCACA,qCACA,wCACA,+CACA,2BACA,0CAGA,iCACA,yBACA,uCACA,iCACA,0CACA,qCACA,6BACA,2CACA,4CACA,oCACA,yCACA,sDACA,yCACA,kCACA,kCACA,mCACA,4CAGA,uCACA,gEAGA,uCAGA,2CACA,8CACA,6CACA,qCACA,4CACA,oCACA,sDACA,+CACA,wBACA,qBAGA,oCACA,0BACA,6BACA,oCACA,8BACA,oCACA,uCACA,2BACA,iCACA,4CACA,2BACA,qCACA,0CAGA,8BACA,8BACA,+BACA,8BAGA,qCACA,uCAGA,uCACA,kCACA,iDAGA,kCAEA,qEACE,uBAGF,uCACE,sCAIF,qjBAKE,sCACA,kDAIF,sIAEE,gCAIA,mFACE,uCAEF,2FACE,iBACA,kBACA,kBACA,sCACA,iHACE,mCAKN,yFACE,+HNrHA,2DACE,0BA4BJ,iBKhCA,mBACA,mBACA,yBACA,6BACA,4BACA,sBACA,uBACA,gCACA,iCACA,sBACA,gCACA,yBACA,qBACA,2BACA,kBACA,+BACA,sCACA,0BACA,0BACA,kCAGA,0FAIA,4BAGA,2BACA,qCACA,6BACA,yBACA,6BACA,oDAGA,gCACA,oCACA,qCACA,yBACA,mCACA,0BACA,sCACA,gCACA,kBACA,qBAGA,2CACA,6BACA,2DACA,uBACA,0CACA,+BACA,sBACA,sCACA,gCACA,+CACA,qBAGA,uCACA,4BACA,mCAGA,2BACA,gCACA,uBL3DE,8BACE,gBA0BN,MACE,eAGF,KACE,oBACA,0BACA,wBACA,mCACA,2DAKF,GAGE,iBAGF,GAKE,iBAGF,GAKE,kBAGF,GAKE,kBAGF,GAKE,iBAKA,wBAEE,mBAQJ,IACE,eAGF,WACE,qDACA,kBACA,mCAGF,IACE,eAGF,OACE,kBACA,SACA,eACA,OO9Gc,KP+Gd,gBACA,cACA,wCAEA,kBACE,mBACA,UACA,iBACA,8CACA,mBAEA,sBACE,YAIJ,SACE,yBACA,cCxFF,qBD2FE,eC3FF,qBDiGA,qBACE,iBAMJ,QACE,SACA,+BACA,oBACA,gBACA,mBAEA,mBACE,wBACA,gBAEF,oBACE,SAEF,YACE,kBACA,+CACA,6BACE,mBAGJ,aC1FA,MADwD,mBAExD,UAKe,QAJf,YAH2C,IAS3C,cACA,gBACA,kBACA,qBACA,aACA,gBACA,uBD+EA,sBACE,gBAKF,kCACE,cAGF,uBACE,qBACA,iBACA,iBACA,gBACA,yCACA,oBACA,oBACA,wBACA,6BACE,yBACA,qBACA,WACA,gBAOJ,mBACE,cACA,gBACA,uBACA,oBACA,qBACA,4BACA,gBAGF,kBAOE,cAKJ,cACE,kBACA,iBAEE,kCACE,oBAEF,mBACE,kBACA,aACA,gBAGF,kFAEE,2CACA,kBACA,qDACA,6CAMK,WC3LT,YD4LiB,IC3LjB,aD2LiB,ICvLjB,aDwLiB,ICvLjB,cDuLiB,IAEf,oCACA,qDACA,6CAIO,iEAEP,2CAKO,kBACP,gBACA,kBACA,cACA,iBACA,iBACA,oCAKF,SACE,gBACA,mBAEF,SACE,oBAWJ,eACE,gBACA,qBAEA,qBACE,eACA,gBACA,iBAEA,2BACE,8CAOA,8BACE,+CACA,4CACE,mCAEF,8CACE,kCAYV,uBACE,cAGF,WACE,iBACA,iBAIE,8BACE,iBAQN,cACE,kBACA,gBACA,gBACA,yBACA,qBAeE,4BACE,eAbF,+BACE,cACA,kBACA,kBACA,cACA,UACA,cAaA,iCACE,WACA,0BAGF,kCACE,YACA,0BAGF,mCACE,oDASJ,2BACE,wBA5BF,uCACE,eAMA,4CACE,WACA,0BAGF,6CACE,YACA,0BAGF,8CACE,oDA9BJ,4BACE,cACA,kBACA,kBACA,cACA,UACA,cAoDF,kEC3TF,kBACA,SACA,mCACA,+BACA,2BD8TE,8CACE,qBAEA,gDACE,6BACA,sBACA,4BACA,wDACE,oCAMN,sCACE,6BACA,sBAKJ,kCAEE,kBAGE,8CACE,iBAGF,4FAEE,kBACA,iBAOJ,oBACE,mBAIJ,oBACE,iBASJ,UACE,qBACA,eACA,kBACA,yBACA,oBACA,gBACA,cACA,mBAEA,2BACE,mBAGF,gBAGE,mBACA,qBACA,cAKJ,UACE,4BACA,gBACA,kBACA,wBACA,gBACE,gBAMJ,WACE,2BAGF,QACE,yBAES,eACP,wBAIJ,UACE,wBAGF,SACE,8BAGF,QACE,6BAGF,aACE,+BACA,uBAGF,gBACE,sDAGF,WACE,qBAGF,oBCvfE,qBD2fF,eACE,gBACA,gBACA,gBAGF,UACE,cACA,oBACA,mBAGF,oBACE,8BAGF,aACE,gBACA,wDACA,6BACA,2EASF,SClgBE,aDmgBe,EClgBf,cDkgBe,EAEf,eACA,MACA,OACA,YACA,gBACA,MO9kBqB,MP+kBrB,WACA,6BAQA,wBACA,qBANA,4BACE,aAOF,WCziBA,MAD2B,qBAE3B,kCACA,iBD0iBE,iBChjBF,qBDmjBI,WAKF,yBACE,kBAGF,mBACE,cACA,WACA,YACA,kBACA,yBACA,gBACA,wBACA,iDACA,8CACA,yCAGF,qBACE,WACA,YACA,iCACA,8BACA,yBAEA,2BACE,yBACA,0BACA,6BACA,qBAMJ,uBACE,gBACA,iBACA,oBAIJ,wBACE,cACA,cACA,mBACA,iBACA,iCACA,gBACA,iBAGF,mBACE,gBACA,iBACA,gBACA,mBACA,mBACA,sBAGF,mBACE,kBACA,cACA,OOzqBS,OP2qBP,mCACE,oCAIF,oCACE,cAKN,YACE,eACA,mBACA,eAEA,eACE,WAKE,4BACE,kBACA,WACA,WAGF,iCACE,cACA,kBACA,WACA,kBACA,UACA,MAda,IAeb,OO7sBU,OP8sBV,kBACA,yCACA,oBAiBA,wHAXJ,IAMM,QALN,mBAUI,wHAXJ,IAMM,QALN,mBAUI,wHAXJ,IAMM,QALN,mBAeE,kHAhBF,IAMM,MALN,mBAyBJ,yBACE,iBACA,qBChrBF,YDkrBiB,KCjrBjB,aDirBiB,KC7qBjB,aD8qBiB,KC7qBjB,cD6qBiB,KAEf,yEACE,aACA,kBAUA,gDC9sBJ,MAD2B,qBAE3B,kCACA,iBD+sBM,SACA,kBACA,kBACA,kBACA,WAKJ,sCACE,yBACA,WACA,UACA,WACA,kBACA,kBACA,SAGF,uFAEE,WAON,qBACE,iCACE,gCACA,6BACA,2BACA,yBAIJ,iBACE,gBACA,WAGF,uBACE,aACA,YACA,cACA,qCACE,gBAMJ,gBACE,OOtzBc,KPuzBd,eACA,MACA,WACA,QACA,+BACA,WACA,wCACA,uCACA,0CAIA,UACE,WAGF,oBACE,eACA,WACA,mBAQI,iDACE,YACA,gBAOV,iCAEE,aAGF,gBACE,aACA,UACA,mBACA,0CACA,oCACA,gBACA,kBACE,UACA,gBACA,+BAEF,iCACE,kBAIJ,eACE,wBACA,iBACA,aAGF,cACE,kBACA,SACA,gBACA,qBACA,wBAEA,oBACE,gBACA,kBAEE,4DC5zBJ,WD6zBI,mDC7zBJ,WD8zBI,uDC9zBJ,WD+zBI,8CC/zBJ,WDo0BF,cACE,aAEA,wBACE,qBACA,iBACA,eACA,gCACA,YACA,cACA,qBACA,gCACE,YACA,8BACA,oBAKN,gBACE,oBACA,kBASE,iBACA,mBAGF,oBACE,WAEA,qCACE,mBAGF,sBACE,cACA,oBACA,cAGF,sBACE,gBACA,uBACA,oBACA,qBACA,4BAKN,cACE,aACA,iBACA,gBACA,uBACA,+BACA,kBACA,UACA,gBACA,uBACA,oBACA,mBAaF,MACE,aACA,eACA,MACA,QACA,SACA,OACA,YACA,WACA,UAES,wBACP,yBAMJ,cACE,wCACA,kBACA,iBACA,eO/+Bc,KN+Dd,aDk7Be,ECj7Bf,cDi7Be,EAIf,sFAEE,WOz/BY,KP2/Bd,0CAEE,6CAIJ,cACE;AAAA,4CAIF,2DC18BE,YD68Be,EC58Bf,aD48Be,EAKjB,aACE,aACA,UACA,eACA,eACA,4BACA,iCACA,aACA,YACA,kBACA,mDACA,wBACA,gCAGF,mBACE,kCACA,0CAYF,kCAIE,0CACE,qCAGF,cACE;AAAA,8CAEA,iBACE,kBACA,kBAIJ,UACE,WACA,YAGF,eCrgCA,YDsgCiB,OCrgCjB,aDqgCiB,OAGjB,cACE,eAzBc,KA4BhB,OACE,OA7Bc,KA8Bd,kBACE,WACA,iBACA,oBACA,eACA,oCACA,wCAEF,yCAEE,mBAON,kCACE,uCACE,sCACA,8BAGF,UAEE,kBAGF,iBACE,mBACA,mBACA,6BACE,mBAMF,2BACE,wBAGF,kEAEE,4BAKJ,SAGE,6BACA,qCAEA,iBACE,wBACA,qBACA,gBAIJ,cAGE,YO5oCY,KP+oCd,uBACE,WAGF,4BAEE,aAGF,gBAGE,OAGF,WACE,iBAGF,sFAEE,aAGF,+CAGE,cAIA,yBACE,kBAEF,iCACE,aAIJ,cACE,cACA,UAGF,qCACE,iBAGF,cACE,cACA,eAGF,MACE,mCACA,gCACA,kCAGF,uBACE,aACA,qCACE,iBAON,yDACE,mBACE,aAKJ,kCAEE,KACE,kBAGF,cACE,YOruCmB,MPwuCrB,iBACE,gBAGF,gBACE,UACA,gBAGF,uBACE,gBAGF,sCACE,cAIF,aACE,cACA,aAGF,WACE,2BAGF,cACE,gBAGF,kBACE,WAMJ,yDACE,iBACE,mBACA,iBACA,aACA,eAKJ,yDAEE,SACE,MO5xCkB,MP8xClB,wBACE,iBACA,kBAIA,yDAEE,WAEF,sCACE,UAKN,gBACE,WAGF,oBACE,gBAGF,YACE,iBACA,yBAGF,eC3vCA,YD4vCiB,KC3vCjB,aD2vCiB,KAEf,cAGF,cACE,kBAGF,YACE,UACA,gBACA,uBACA,oBACA,oBAMJ,mCACE,eACE,aAGF,QACE,UAGF,cACE,mCACA,gCACA,mCAMJ,mCAEE,2BACE,mBACA,iBACA,aACA,cACA,gBAGF,QACE,UAIF,eACE,UOx2Cc,MP22ChB,aACE,cACA,aAGF,cACE,uCACA,+BAGF,oBACE,UACA,mCACE,oBAEF,oCACE,mBAEF,8CACE,kBACA,YAIJ,cACE,kBAGF,kBACE,WAKJ,mCAEE,cACE,qCACA,2BACE,6BAIJ,uBACE,mBACA,2BACE,iBAIJ,iCACE,cAKJ,0DACE,QACE,oBAIJ,mCAEE,YACE,eAGF,2BACE,eACA,2CACE,+BACA,gCAIJ,cACE,YOh8CkB,MPu8CpB,gBACE,KOx8CkB,MP+8CpB,gBACE,gBAGF,SACE,MOp9CkB,MPs9ClB,0BACE,gBACA,mBAEA,sCACE,2BAGF,iHACE,mBAMA,oCACE,aACA,cACA,4CACE,yBAQJ,wCACE,iBACA,mBAIJ,yCAGE,eACA,iBAKJ,YACE,oBAGE,4BACE,gBAIJ,sBACE,gBAGE,qCACE,mBAGF,kCC19CR,iBACA,kBACA,MDy9C8B,QCx9C9B,ODw9C8B,QAEpB,mBACA,gBACA,gBACA,iBACA,kBACA,WAEA,2CACE,gCAQV,yBACE,oBACA,WAEA,gDACE,kCACA,+BACA,sCAGF,2BACE,eACA,WAGF,2BC5/CJ,iBACA,kBACA,MD2/C0B,KC1/C1B,OD0/C0B,KAEpB,mBACA,iBACA,SAGF,8CACE,WAEA,gDACE,SAIJ,sCACE,SAON,kBACE,UACA,iBAIA,2BACE,kBAMN,mCASE,cACE,0CAGF,eACE,eAGF,OACE,eACA,0CAGF,aACE,mCAKJ,0BACE,cACE,mBAGF,uBACE,0CAGF,eACE,kBQroDJ,YACE,eACA,oBACE,qBAIA,kCACE,sCACA,aACA,cACA,UACA,kBACA,oBACA,aACA,wBACA,uBACA,yBACA,mBACA,kBACA,mDACA,8BACA,kCACA,wCACE,kDAIF,yCACE,sCACA,4CACA,4DACA,4BAGJ,gCACE,mBACA,2CACE,4BACA,+CACA,kCAGJ,2FAEE,kBAEF,wCACE,mBAMN,WACE,gBACA,oBAEA,yBACE,mBACA,oBACA,iDAMA,4BACE,iBACA,SAIA,sCACE,iBAEF,0DACE,oBAIJ,uCACE,iBACA,oBACA,kCACA,yCAEE,SACA,gBACA,uBACA,oBACA,qBACA,4BAKF,gCACE,wBACA,iBACA,uBAEF,mCACE,aASR,kCACE,YACE,wBAKJ,kCAEE,WACE,kBAEE,yCACE,yBACA,kBACA,mBACA,cACA,eACA,iBACA,kBAEA,8CACE,eAMR,YACE,iBACA,kCACE,aACA,eAON,mCACE,WACE,iBCpIJ,iBACE,qBAKE,yEAZF,YACA,2BACA,aAHc,MAId,cAJ2B,MAkB3B,oCACE,iBAGF,0CACE,gBAKF,2BR+DA,kBACA,SACA,mCACA,+BACA,2BQhEE,aACA,gCAEF,mDACE,iBAIJ,mBACE,gBACA,kDACA,iBAGF,WACE,iBAGF,iBACE,iBACA,oBAEA,sBA1DA,UACA,kBACA,qCA2DE,wBAEA,4BACE,mBACA,WACA,qBAGF,+BArEF,UACA,kBACA,qCAsEI,oBACA,mBACA,gBACA,WAEA,qCACE,kBAIJ,yDACE,gBAGF,8BACE,8BACA,iBACA,yBACA,qBAGF,kCACE,0BACA,6BACA,UAGF,iCACE,yBACA,4BACA,WAIJ,mBACE,iBACA,mBACA,iBACA,mBAKJ,mBACE,KACE,UACA,kBACA,SAEF,GACE,UACA,kBACA,OAIJ,aACE,4CACA,wBACA,gBACA,SACA,+BACA,sBACA,yBACE,SAEF,kBR5DA,MADwD,mBAExD,UAKe,QAJf,YAH2C,IAS3C,cACA,gBACA,kBACA,qBACA,aACA,gBACA,uBQmDF,UACE,iBACA,kBACA,qBAEA,gCACE,cAOF,kBR9EA,MADwD,mBAExD,UQ8EiB,OR7EjB,YQ6EyB,IAEzB,qBACE,sCACA,gCACA,4CACA,uCACA,oCACA,+BACA,wBACE,wBAEF,2BACE,0CACA,kCACA,4CAIJ,wBACE,8BAGF,iBACE,gBACA,oBACA,gBACA,uBACA,oBACA,qBACA,4BAGF,uBACE,qBAGF,kBACE,qBACA,4BACA,6BACE,mBACA,UACA,WACA,kBACA,cACA,WACA,kBACA,SACA,WAKN,qBACE,gBACA,oCACE,mBAIJ,oBACE,cAGF,0EACE,8CAGF,eACE,sBACA,iBAEA,4BACE,iBAEE,+CACE,oBAEF,oCACE,qBAMJ,8BACE,mBAMA,4CA/PF,gDAkQE,oDAlQF,+CAqQE,6CArQF,gDAwQE,0CAxQF,+CA+QF,4BA/QE,iDAqRJ,aRlME,MQmM6B,QRlM7B,UQkMe,QRjMf,YQiMwB,IAExB,oBACE,YAIJ,iBACE,mBACA,mBACE,gBAMF,iCACE,iBAIJ,kCACE,kBACE,sCACA,kCACA,kCACE,WACA,gBAIJ,oCRpPA,YQqPiB,SRpPjB,aQoPiB,SAEf,gBACA,4CACE,YAMN,kCACE,oBACE,6BAIJ,kCAGM,uDA9TJ,YACA,2BACA,aA6TmB,MA5TnB,cA4T2B,MAEvB,qCACE,yCACA,wCACA,kCACA,+BAOR,kCACE,iBACE,eACA,gBACA,oBACA,sBChWJ,KACE,mBACA,oBACA,mBACA,iBACA,iBACA,8CACA,uCACA,UACE,iBACA,eACA,gCCXJ,2DACE,WACA,UACA,UACA,qBACA,WACA,kBACA,uCAGF,UACE,sBAEA,oBACE,iBACA,kBACA,SAEA,2BACE,WACA,cACA,kBACA,0BACA,uBACA,kBACA,WACA,YACA,UACA,UACA,iBACA,gDACA,qCACA,6BACA,UAGF,sCACE,kBACA,SACA,6CACE,UAOJ,gBACE,iBACA,iBAEA,oBACE,mBACA,gBACA,uBAEA,sBAEE,mBACA,kBACA,UAIJ,+BACE,8CACA,iFAGF,uBAGE,cACA,YAGF,oCAGE,eACA,aAIJ,mDACE,cAGF,6CACE,aAIJ,gBACE,mBACA,qBACA,sBACE,aACA,kBAEA,gCAEE,WACA,qBACA,kBACA,0BACA,uBACA,kBACA,UACA,WACA,WACA,YACA,UACA,yCACA,6BACA,UAGJ,oBACE,cACA,8BACA,kBACA,kBACA,aACA,kBACA,cAMN,kCACE,UACE,iBACA,aACE,kBCvIN,2EACE,WAGF,YACE,mBACA,yBACE,mBAIA,oCAEE,cAIJ,6BACE,iBACA,kBACA,kBAIA,yCACE,yBACA,0BAWN,kBACE,aACA,cACA,kBACA,kBACA,yBACA,wBACE,kBACA,aACA,WACA,0BAIJ,qBACE,wBACE,6CAIJ,QACE,6BACA,iCACA,yBC5DF,MACE,2BACA,2CAKA,qCACE,mBACA,gBAEA,qDACE,gBACA,UACA,WACA,kBACA,cACA,WACA,kBACA,UACA,mBAGF,yCAGE,iBAGF,qEACE,mBAKN,eACE,iBAGF,oBACE,kBAMA,iEAGE,mBAIJ,kCAIM,qDACE,eAEF,yCACE,mBACA,gBACA","sourcesContent":["/*!\n * The styles for Jekyll theme Chirpy\n *\n * Chirpy v3.3.2 (https://github.com/cotes2020/jekyll-theme-chirpy)\n * © 2019 Cotes Chung\n * MIT Licensed\n */\n\n@import\n \"colors/light-typography\",\n \"colors/dark-typography\",\n\n \"addon/module\",\n \"addon/variables\",\n \"addon/syntax\",\n \"addon/commons\",\n\n \"layout/home\",\n \"layout/post\",\n \"layout/tags\",\n \"layout/archives\",\n \"layout/categories\",\n \"layout/category-tag\";\n","/*\n The common styles\n*/\n@import url('https://fonts.googleapis.com/css2?family=Lato&family=Source+Sans+Pro:wght@400;600;900&display=swap');\n\n@mixin mode-toggle($dark-mode: false) {\n @if $dark-mode {\n @include dark-scheme;\n\n .mode-toggle {\n transform: rotateY(180deg);\n }\n\n } @else {\n @include light-scheme;\n\n .mode-toggle {\n transform: none;\n }\n }\n\n}\n\nhtml:not([mode]),\nhtml[mode=light] {\n @include mode-toggle();\n}\n\nhtml[mode=dark] {\n @include mode-toggle(true);\n}\n\n@media (prefers-color-scheme: dark) {\n html:not([mode]),\n html[mode=dark] {\n @include mode-toggle(true);\n }\n\n html[mode=light] {\n @include mode-toggle();\n }\n}\n\n:root {\n font-size: 16px;\n}\n\nbody {\n line-height: 1.75rem;\n background: var(--body-bg);\n color: var(--text-color);\n -webkit-font-smoothing: antialiased;\n font-family: 'Source Sans Pro', 'Microsoft Yahei', sans-serif;\n}\n\n/* --- Typography --- */\n\nh1 {\n @extend %heading;\n\n font-size: 1.8rem;\n}\n\nh2 {\n @extend %heading;\n @extend %section;\n @extend %anchor;\n\n font-size: 1.4rem;\n}\n\nh3 {\n @extend %heading;\n @extend %section;\n @extend %anchor;\n\n font-size: 1.25rem;\n}\n\nh4 {\n @extend %heading;\n @extend %section;\n @extend %anchor;\n\n font-size: 1.15rem;\n}\n\nh5 {\n @extend %heading;\n @extend %section;\n @extend %anchor;\n\n font-size: 1.1rem;\n}\n\nol,\nul {\n ol,\n ul {\n margin-bottom: 1rem;\n }\n}\n\na {\n @extend %link-color;\n}\n\nimg {\n max-width: 100%;\n}\n\nblockquote {\n border-left: 5px solid var(--blockquote-border-color);\n padding-left: 1rem;\n color: var(--blockquote-text-color);\n}\n\nkbd {\n margin: 0 0.3rem;\n}\n\nfooter {\n position: absolute;\n bottom: 0;\n padding: 0 1rem;\n height: $footer-height;\n font-size: 0.8rem;\n color: #7a7b7d;\n background-color: var(--footer-bg-color);\n\n > div.d-flex {\n line-height: 1.2rem;\n width: 95%;\n max-width: 1045px;\n border-top: 1px solid var(--main-border-color);\n margin-bottom: 1rem;\n\n > div {\n width: 350px;\n }\n }\n\n a {\n color: var(--footer-link);\n &:link {\n @include no-text-decoration;\n }\n &:hover {\n @extend %link-hover;\n\n @include no-text-decoration;\n }\n }\n .footer-right {\n text-align: right;\n }\n}\n\n/* --- Panels --- */\n\n.access {\n top: 2rem;\n transition: top 0.2s ease-in-out;\n margin-right: 1.5rem;\n margin-top: 3rem;\n margin-bottom: 4rem;\n\n &:only-child {\n position: -webkit-sticky; /* Safari */\n position: sticky;\n }\n &.topbar-down {\n top: 6rem;\n }\n > div {\n padding-left: 1rem;\n border-left: 1px solid var(--main-border-color);\n &:not(:last-child) {\n margin-bottom: 4rem;\n }\n }\n span {\n @include panel-label;\n }\n .post-content {\n font-size: 0.9rem;\n }\n}\n\n#access-tags {\n > div.post-content > div {\n max-width: 80%;\n }\n\n .post-tag {\n display: inline-block;\n line-height: 1rem;\n font-size: 0.85rem;\n background: none;\n border: 1px solid var(--btn-border-color);\n border-radius: 0.8rem;\n padding: 0.3rem 0.5rem;\n margin: 0 0.35rem 0.5rem 0;\n &:hover {\n background-color: #2a408e;\n border-color: #2a408e;\n color: #fff;\n transition: none;\n }\n }\n}\n\n#access-lastmod {\n\n li {\n height: 1.8rem;\n overflow: hidden;\n text-overflow: ellipsis;\n display: -webkit-box;\n -webkit-line-clamp: 1;\n -webkit-box-orient: vertical;\n list-style: none;\n }\n\n a {\n &:hover {\n @extend %link-hover;\n }\n\n @extend %no-bottom-border;\n\n color: inherit;\n }\n\n}\n\n.footnotes > ol {\n padding-left: 2rem;\n margin-top: 0.5rem;\n > li {\n &:not(:last-child) {\n margin-bottom: 0.3rem;\n }\n > p {\n margin-left: 0.25em;\n margin-top: 0;\n margin-bottom: 0;\n }\n // [scroll-focus] added by `smooth-scroll.js`\n &:target:not([scroll-focus]),\n &[scroll-focus=true] > p {\n background-color: var(--footnote-target-bg);\n width: fit-content;\n -webkit-transition: background-color 1.5s ease-in-out; // Safari prior 6.1\n transition: background-color 1.5s ease-in-out;\n }\n }\n}\n\n.footnote {\n @at-root a#{&} {\n @include ml-mr(1px);\n @include pl-pr(2px);\n\n border-bottom-style: none !important;\n -webkit-transition: background-color 1.5s ease-in-out; // Safari prior 6.1\n transition: background-color 1.5s ease-in-out;\n }\n\n // [scroll-focus] added by `smooth-scroll.js`\n @at-root sup:target:not([scroll-focus]),\n sup[scroll-focus=true] > a#{&} {\n background-color: var(--footnote-target-bg);\n }\n}\n\n.reversefootnote {\n @at-root a#{&} {\n font-size: 0.6rem;\n position: absolute;\n line-height: 1;\n padding-top: 0.5em;\n margin-left: 0.5em;\n border-bottom-style: none !important;\n }\n}\n\n.post {\n h1 {\n margin-top: 3rem;\n margin-bottom: 1rem;\n }\n em { /* MarkDown italic */\n padding-right: 0.2rem;\n }\n a:hover {\n code {\n @extend %link-hover;\n }\n }\n}\n\n/* --- Begin of Markdown table style --- */\n\n.table-wrapper { // it will be created by Liquid\n overflow-x: auto;\n margin-bottom: 1.5rem;\n\n > table {\n min-width: 100%;\n overflow-x: auto;\n border-spacing: 0;\n\n thead {\n border-bottom: solid 2px rgba(210, 215, 217, 0.75);\n th {\n @extend %table-cell;\n }\n }\n\n tbody {\n tr {\n border-bottom: 1px solid var(--tb-border-color);\n &:nth-child(2n) {\n background-color: var(--tb-even-bg);\n }\n &:nth-child(2n + 1) {\n background-color: var(--tb-odd-bg);\n }\n td {\n @extend %table-cell;\n }\n }\n }\n }\n}\n\n/* --- post --- */\n\n.pageviews .fa-spinner {\n font-size: 80%;\n}\n\n.post-meta {\n font-size: 0.85rem;\n word-spacing: 1px;\n a {\n @extend %link-color;\n @extend %link-underline;\n &:not(:last-child) {\n margin-right: 2px;\n }\n &:hover {\n @extend %link-hover;\n }\n }\n}\n\n.post-content {\n font-size: 1.08rem;\n line-height: 1.8;\n margin-top: 2rem;\n overflow-wrap: break-word;\n word-wrap: break-word;\n\n @mixin img-caption {\n + em {\n display: block;\n text-align: center;\n font-style: normal;\n font-size: 80%;\n padding: 0;\n color: #6d6c6c;\n }\n }\n\n @mixin img-style($caption: false) {\n\n img[data-src] {\n margin: 0.5rem 0;\n\n @if $caption {\n @include img-caption;\n }\n\n &.left {\n float: left;\n margin: 0.75rem 1rem 1rem 0;\n }\n\n &.right {\n float: right;\n margin: 0.75rem 0 1rem 1rem;\n }\n\n &.shadow {\n filter: drop-shadow(2px 4px 6px rgba(0, 0, 0, 0.08));\n }\n\n }\n }\n\n @include img-style(true);\n\n a {\n blockquote & {\n color: var(--link-color);\n }\n\n &:not(.img-link) {\n @extend %link-color;\n @extend %link-underline;\n &:hover {\n @extend %link-hover;\n }\n }\n\n &.img-link {\n @include img-style;\n @include img-caption;\n }\n }\n\n > p {\n > img[data-src]:not(.normal):not(.left):not(.right) {\n @include align-center;\n }\n }\n\n ul {\n // attribute 'hide-bullet' was added by liquid\n .task-list-item[hide-bullet] {\n list-style-type: none;\n\n > i { // checkbox icon\n margin: 0 0.4rem 0.2rem -1.4rem;\n vertical-align: middle;\n color: var(--checkbox-color);\n &.checked {\n color: var(--checkbox-checked-color);\n }\n }\n\n }\n\n input[type=checkbox] {\n margin: 0 0.5rem 0.2rem -1.3rem;\n vertical-align: middle;\n }\n\n } // ul\n\n > ol,\n > ul {\n padding-left: 2rem;\n\n li {\n + li {\n margin-top: 0.3rem;\n }\n\n ol,\n ul { // sub list\n padding-left: 2rem;\n margin-top: 0.3rem;\n }\n }\n\n }\n\n > ol {\n li {\n padding-left: 0.25em;\n }\n }\n\n dl > dd {\n margin-left: 1rem;\n }\n\n} // .post-content\n\n.tag:hover {\n @extend %tag-hover;\n}\n\n.post-tag {\n display: inline-block;\n min-width: 2rem;\n text-align: center;\n background: var(--tag-bg);\n border-radius: 0.3rem;\n padding: 0 0.4rem;\n color: inherit;\n line-height: 1.3rem;\n\n &:not(:last-child) {\n margin-right: 0.2rem;\n }\n\n &:hover {\n @extend %tag-hover;\n\n border-bottom: none;\n text-decoration: none;\n color: #d2603a;\n }\n}\n\n/* --- buttons --- */\n.btn-lang {\n border: 1px solid !important;\n padding: 1px 3px;\n border-radius: 3px;\n color: var(--link-color);\n &:focus {\n box-shadow: none;\n }\n}\n\n/* --- Effects classes --- */\n\n.semi-bold {\n font-weight: 600 !important;\n}\n\n.loaded {\n display: block !important;\n\n @at-root .d-flex#{&} {\n display: flex !important;\n }\n}\n\n.unloaded {\n display: none !important;\n}\n\n.visible {\n visibility: visible !important;\n}\n\n.hidden {\n visibility: hidden !important;\n}\n\n.flex-grow-1 {\n -ms-flex-positive: 1 !important;\n flex-grow: 1 !important;\n}\n\n.btn-box-shadow {\n box-shadow: 0 0 8px 0 var(--btn-box-shadow) !important;\n}\n\n.topbar-up {\n top: -3rem !important; /* same as topbar height. */\n}\n\n.no-text-decoration {\n @include no-text-decoration;\n}\n\n.tooltip-inner { /* Overrided BS4 Tooltip */\n font-size: 0.7rem;\n max-width: 220px;\n text-align: left;\n}\n\n.disabled {\n color: rgb(206, 196, 196);\n pointer-events: auto;\n cursor: not-allowed;\n}\n\n.hide-border-bottom {\n border-bottom: none !important;\n}\n\n.input-focus {\n box-shadow: none;\n border-color: var(--input-focus-border-color) !important;\n background: center !important;\n transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out;\n}\n\n/* --- sidebar layout --- */\n\n$tab-count: 5 !default;\n\n$sidebar-display: \"sidebar-display\";\n\n#sidebar {\n @include pl-pr(0);\n\n position: fixed;\n top: 0;\n left: 0;\n height: 100%;\n overflow-y: auto;\n width: $sidebar-width-medium;\n z-index: 99;\n background: var(--sidebar-bg);\n\n /* Hide scrollbar for Chrome, Safari and Opera */\n &::-webkit-scrollbar {\n display: none;\n }\n\n /* Hide scrollbar for IE, Edge and Firefox */\n -ms-overflow-style: none; /* IE and Edge */\n scrollbar-width: none; /* Firefox */\n\n a {\n @include sidebar-links;\n\n &:hover {\n @include no-text-decoration;\n\n color: #fff;\n }\n }\n\n #avatar {\n &:hover > a {\n border-color: #fff;\n }\n\n > a {\n display: block;\n width: 6rem;\n height: 6rem;\n border-radius: 50%;\n border: 2px solid #b6b6b6;\n overflow: hidden;\n transform: translateZ(0); // fixed the zoom in Safari\n -webkit-transition: border-color 0.35s ease-in-out;\n -moz-transition: border-color 0.35s ease-in-out;\n transition: border-color 0.35s ease-in-out;\n }\n\n img {\n width: 100%;\n height: 100%;\n -webkit-transition: transform 0.5s;\n -moz-transition: transform 0.5s;\n transition: transform 0.5s;\n\n &:hover {\n -ms-transform: scale(1.2);\n -moz-transform: scale(1.2);\n -webkit-transform: scale(1.2);\n transform: scale(1.2);\n }\n }\n } // #avatar\n\n .site-title {\n a {\n font-weight: 900;\n font-size: 1.5rem;\n letter-spacing: 0.5px;\n }\n }\n\n .site-subtitle {\n font-size: 95%;\n color: #828282;\n line-height: 1.2rem;\n word-spacing: 1px;\n margin: 0.5rem 1.5rem 0.5rem 1.5rem;\n min-height: 3rem; // avoid vertical shifting in multi-line words\n user-select: none;\n }\n\n .nav-link {\n border-radius: 0;\n font-size: 0.95rem;\n font-weight: 600;\n letter-spacing: 1px;\n display: table-cell;\n vertical-align: middle;\n }\n\n .nav-item {\n text-align: center;\n display: table;\n height: $tab-height;\n &:hover {\n .nav-link {\n color: #f8f9facf;\n }\n }\n &.active {\n .nav-link {\n color: #fcfcfc;\n }\n }\n }\n\n ul {\n height: $tab-height * $tab-count;\n margin-bottom: 2rem;\n padding-left: 0;\n\n li {\n width: 100%;\n\n &:last-child {\n $cursor-width: 3px;\n\n a {\n position: relative;\n left: $cursor-width / 2;\n width: 100%;\n }\n\n &::after { // the cursor\n display: table;\n visibility: hidden;\n content: \"\";\n position: relative;\n right: 1px;\n width: $cursor-width;\n height: $tab-cursor-height;\n border-radius: 1px;\n background-color: var(--nav-cursor-color);\n pointer-events: none;\n }\n }\n } // li\n\n @mixin fix-cursor($top) {\n top: $top;\n visibility: visible;\n }\n\n @for $i from 1 through $tab-count {\n $offset: $tab-count - $i;\n $top: -$offset * $tab-height + $tab-cursor-height / 2;\n\n @if $i < $tab-count {\n > li.active:nth-child(#{$i}),\n > li.nav-item:nth-child(#{$i}):hover {\n ~li:last-child::after {\n @include fix-cursor($top);\n }\n }\n } @else {\n > li.active:nth-child(#{$i}):last-child::after,\n > li.nav-item:nth-child(#{$i}):last-child:hover::after {\n @include fix-cursor($top);\n }\n }\n\n } // @for\n\n } // ul\n\n .sidebar-bottom {\n font-size: 1.2rem;\n margin-bottom: 2.1rem;\n\n @include ml-mr(auto);\n @include pl-pr(1rem);\n\n %icon {\n width: 2.4rem;\n text-align: center;\n }\n\n a {\n @extend %icon;\n }\n\n #mode-toggle-wrapper {\n @extend %icon;\n\n i {\n @include sidebar-links;\n\n margin: 0;\n font-size: 1.05rem;\n text-align: center;\n position: relative;\n bottom: 1px;\n }\n\n }\n\n .icon-border {\n background-color: #525354;\n content: \"\";\n width: 3px;\n height: 3px;\n border-radius: 50%;\n position: relative;\n top: 12px;\n }\n\n a:hover,\n #mode-toggle-wrapper i:hover {\n color: #fff;\n }\n\n } // .sidebar-bottom\n\n} // #sidebar\n\n@media (hover: hover) {\n #sidebar ul > li:last-child::after {\n -webkit-transition: top 0.5s ease;\n -moz-transition: top 0.5s ease;\n -o-transition: top 0.5s ease;\n transition: top 0.5s ease;\n }\n}\n\n.profile-wrapper {\n margin-top: 2rem;\n width: 100%;\n}\n\n#search-result-wrapper {\n display: none;\n height: 100%;\n overflow: auto;\n .post-content {\n margin-top: 2rem;\n }\n}\n\n/* --- top-bar --- */\n\n#topbar-wrapper {\n height: $topbar-height;\n position: fixed;\n top: 0;\n left: 260px; /* same as sidebar width */\n right: 0;\n transition: top 0.2s ease-in-out;\n z-index: 50;\n border-bottom: 1px solid rgba(0, 0, 0, 0.07);\n box-shadow: 0 3px 5px 0 rgba(0, 0, 0, 0.05);\n background-color: var(--topbar-wrapper-bg);\n}\n\n#topbar {\n i { // icons\n color: #999;\n }\n\n #breadcrumb {\n font-size: 1rem;\n color: gray;\n padding-left: 0.5rem;\n\n a:hover {\n @extend %link-hover;\n }\n\n span {\n &:not(:last-child) {\n &::after {\n content: \"›\";\n padding: 0 0.3rem;\n }\n }\n }\n }\n} // #topbar\n\n#sidebar-trigger,\n#search-trigger {\n display: none;\n}\n\n#search-wrapper {\n display: flex;\n width: 95%;\n border-radius: 1rem;\n border: 1px solid var(--search-wrapper-bg);\n background: var(--search-wrapper-bg);\n padding: 0 0.5rem;\n i {\n z-index: 2;\n font-size: 0.9rem;\n color: var(--search-icon-color);\n }\n .fa-times-circle { /* button 'clean up' */\n visibility: hidden;\n }\n}\n\n#search-cancel { /* 'Cancel' link */\n color: var(--link-color);\n margin-left: 1rem;\n display: none;\n}\n\n#search-input {\n background: center;\n border: 0;\n border-radius: 0;\n padding: 0.18rem 0.3rem;\n color: var(--text-color);\n\n &:focus {\n box-shadow: none;\n background: center;\n &.form-control {\n &::-webkit-input-placeholder { @include input-placeholder; }\n &::-moz-placeholder { @include input-placeholder; }\n &:-ms-input-placeholder { @include input-placeholder; }\n &::placeholder { @include input-placeholder; }\n }\n }\n}\n\n#search-hints {\n display: none;\n\n .post-tag {\n display: inline-block;\n line-height: 1rem;\n font-size: 1rem;\n background: var(--search-tag-bg);\n border: none;\n padding: 0.5rem;\n margin: 0 1rem 1rem 0;\n &::before {\n content: \"#\";\n color: var(--text-muted-color);\n padding-right: 0.2rem;\n }\n }\n}\n\n#search-results {\n padding-bottom: 6rem;\n a {\n &:hover {\n @extend %link-hover;\n }\n\n @extend %link-color;\n @extend %no-bottom-border;\n @extend %heading;\n\n font-size: 1.4rem;\n line-height: 2.5rem;\n }\n\n > div {\n width: 100%;\n\n &:not(:last-child) {\n margin-bottom: 1rem;\n }\n\n i { // icons\n color: #818182;\n margin-right: 0.15rem;\n font-size: 80%;\n }\n\n > p {\n overflow: hidden;\n text-overflow: ellipsis;\n display: -webkit-box;\n -webkit-line-clamp: 3;\n -webkit-box-orient: vertical;\n }\n }\n} // #search-results\n\n#topbar-title {\n display: none;\n font-size: 1.1rem;\n font-weight: 600;\n font-family: sans-serif;\n color: var(--topbar-text-color);\n text-align: center;\n width: 70%;\n overflow: hidden;\n text-overflow: ellipsis;\n word-break: keep-all;\n white-space: nowrap;\n}\n\n#page {\n .categories,\n #tags,\n #archives {\n a:not(:hover) {\n @extend %no-bottom-border;\n }\n }\n}\n\n#mask {\n display: none;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n height: 100%;\n width: 100%;\n z-index: 1;\n\n @at-root [#{$sidebar-display}] & {\n display: block !important;\n }\n}\n\n/* --- main wrapper --- */\n\n#main-wrapper {\n background-color: var(--main-wrapper-bg);\n position: relative;\n min-height: 100vh;\n padding-bottom: $footer-height;\n\n @include pl-pr(0);\n}\n\n#main > div.row:first-child > div {\n &:nth-child(1),\n &:nth-child(2) {\n margin-top: $topbar-height; /* same as the height of topbar */\n }\n &:first-child {\n /* 3rem for topbar, 6rem for footer */\n min-height: calc(100vh - #{$topbar-height} - #{$footer-height} - #{$post-extend-min-height});\n }\n}\n\n#post-wrapper {\n min-height: calc(\n 100vh - #{$topbar-height} - #{$footer-height} - #{$post-extend-min-height}) !important;\n}\n\n#topbar-wrapper.row,\n#main > .row,\n#search-result-wrapper > .row {\n @include ml-mr(0);\n}\n\n/* --- button back-to-top --- */\n\n#back-to-top {\n display: none;\n z-index: 1;\n cursor: pointer;\n position: fixed;\n background: var(--button-bg);\n color: var(--btn-backtotop-color);\n height: 2.6em;\n width: 2.7em;\n border-radius: 50%;\n border: 1px solid var(--btn-backtotop-border-color);\n transition: 0.2s ease-out;\n -webkit-transition: 0.2s ease-out;\n}\n\n#back-to-top:hover {\n transform: translate3d(0, -5px, 0);\n -webkit-transform: translate3d(0, -5px, 0);\n}\n\n/*\n Responsive Design:\n\n {sidebar, content, panel} >= 1120px screen width\n {sidebar, content} >= 850px screen width\n {content} <= 849px screen width\n\n*/\n\n@media all and (max-width: 576px) {\n\n $footer-height: 6rem; // overwrite\n\n #main > div.row:first-child > div:first-child {\n min-height: calc(100vh - #{$topbar-height} - #{$footer-height});\n }\n\n #post-wrapper {\n min-height: calc(\n 100vh - #{$topbar-height} - #{$footer-height} - #{$post-extend-min-height}) !important;\n h1 {\n margin-top: 2.2rem;\n font-size: 1.55rem;\n }\n }\n\n #avatar > a {\n width: 5rem;\n height: 5rem;\n }\n\n .site-subtitle {\n @include ml-mr(1.8rem);\n }\n\n #main-wrapper {\n padding-bottom: $footer-height;\n }\n\n footer {\n height: $footer-height;\n > div.d-flex {\n width: 100%;\n padding: 1.5rem 0;\n margin-bottom: 0.3rem;\n flex-wrap: wrap;\n -ms-flex-pack: distribute !important;\n justify-content: space-around !important;\n }\n .footer-left,\n .footer-right {\n text-align: center;\n }\n }\n\n}\n\n/* hide sidebar and panel */\n@media all and (max-width: 849px) {\n %slide {\n -webkit-transition: transform 0.4s ease;\n transition: transform 0.4s ease;\n }\n\n html,\n body {\n overflow-x: hidden;\n }\n\n .footnotes ol > li {\n padding-top: 3.5rem;\n margin-top: -3.2rem;\n &:first-child {\n margin-top: -3.5rem;\n }\n }\n\n [#{$sidebar-display}] {\n\n #sidebar {\n transform: translateX(0);\n }\n\n #topbar-wrapper,\n #main-wrapper {\n transform: translateX(#{$sidebar-width-medium});\n }\n\n }\n\n #sidebar {\n @extend %slide;\n\n transform: translateX(-#{$sidebar-width-medium}); // hide\n -webkit-transform: translateX(-#{$sidebar-width-medium});\n\n .cursor {\n -webkit-transition: none;\n -moz-transition: none;\n transition: none;\n }\n }\n\n #main-wrapper {\n @extend %slide;\n\n padding-top: $topbar-height;\n }\n\n #search-result-wrapper {\n width: 100%;\n }\n\n #breadcrumb,\n #search-wrapper {\n display: none;\n }\n\n #topbar-wrapper {\n @extend %slide;\n\n left: 0;\n }\n\n .topbar-up {\n top: 0 !important;\n }\n\n #main > div.row:first-child > div:nth-child(1),\n #main > div.row:first-child > div:nth-child(2) {\n margin-top: 0;\n }\n\n #topbar-title,\n #sidebar-trigger,\n #search-trigger {\n display: block;\n }\n\n #search-wrapper {\n &.loaded ~ a {\n margin-right: 1rem;\n }\n .fa-times-circle {\n right: 5.2rem;\n }\n }\n\n #search-input {\n margin-left: 0;\n width: 95%;\n }\n\n #search-result-wrapper .post-content {\n letter-spacing: 0;\n }\n\n #search-hints {\n display: block;\n padding: 0 1rem;\n }\n\n #tags {\n -webkit-box-pack: center !important;\n -ms-flex-pack: center !important;\n justify-content: center !important;\n }\n\n #page h1.dynamic-title {\n display: none;\n ~ .post-content {\n margin-top: 3rem;\n }\n }\n\n} // max-width: 849px\n\n/* Phone & Pad */\n@media all and (min-width: 577px) and (max-width: 1199px) {\n footer > .d-flex > div {\n width: 312px;\n }\n}\n\n/* Sidebar is visible */\n@media all and (min-width: 850px) {\n /* Solved jumping scrollbar */\n html {\n overflow-y: scroll;\n }\n\n #main-wrapper {\n margin-left: $sidebar-width-medium;\n }\n\n .profile-wrapper {\n margin-top: 3rem;\n }\n\n #search-wrapper {\n width: 22%;\n min-width: 150px;\n }\n\n #search-result-wrapper {\n margin-top: 3rem;\n }\n\n div.post-content .table-wrapper > table {\n min-width: 70%;\n }\n\n /* button 'back-to-Top' position */\n #back-to-top {\n bottom: 5.5rem;\n right: 1.2rem;\n }\n\n .topbar-up {\n box-shadow: none !important;\n }\n\n #topbar-title {\n text-align: left;\n }\n\n footer > div.d-flex {\n width: 92%;\n }\n\n}\n\n/* Pad horizontal */\n@media all and (min-width: 992px) and (max-width: 1199px) {\n #main .col-lg-11 {\n -webkit-box-flex: 0;\n -ms-flex: 0 0 96%;\n flex: 0 0 96%;\n max-width: 96%;\n }\n}\n\n/* Compact icons in sidebar & panel hidden */\n@media all and (min-width: 850px) and (max-width: 1199px) {\n\n #sidebar {\n width: $sidebar-width-small;\n\n .site-subtitle {\n margin-left: 1rem;\n margin-right: 1rem;\n }\n\n .sidebar-bottom {\n a,\n span {\n width: 2rem;\n }\n .icon-border {\n left: -3px;\n }\n }\n }\n\n #topbar-wrapper {\n left: 210px;\n }\n\n #search-results > div {\n max-width: 700px;\n }\n\n .site-title {\n font-size: 1.3rem;\n margin-left: 0 !important;\n }\n\n .site-subtitle {\n @include ml-mr(1rem);\n\n font-size: 90%;\n }\n\n #main-wrapper {\n margin-left: 210px;\n }\n\n #breadcrumb {\n width: 65%;\n overflow: hidden;\n text-overflow: ellipsis;\n word-break: keep-all;\n white-space: nowrap;\n }\n\n}\n\n/* panel hidden */\n@media all and (max-width: 1199px) {\n #panel-wrapper {\n display: none;\n }\n\n #topbar {\n padding: 0;\n }\n\n #main > div.row {\n -webkit-box-pack: center !important;\n -ms-flex-pack: center !important;\n justify-content: center !important;\n }\n}\n\n/* --- desktop mode, both sidebar and panel are visible --- */\n\n@media all and (min-width: 1200px) {\n\n #main > div.row > div.col-xl-8 {\n -webkit-box-flex: 0;\n -ms-flex: 0 0 75%;\n flex: 0 0 75%;\n max-width: 75%;\n padding-left: 3%;\n }\n\n #topbar {\n padding: 0;\n /* max-width: 1070px; */\n }\n\n #panel-wrapper {\n max-width: $panel-max-width;\n }\n\n #back-to-top {\n bottom: 6.5rem;\n right: 4.3rem;\n }\n\n #search-input {\n -webkit-transition: all 0.3s ease-in-out;\n transition: all 0.3s ease-in-out;\n }\n\n #search-results > div {\n width: 46%;\n &:nth-child(odd) {\n margin-right: 1.5rem;\n }\n &:nth-child(even) {\n margin-left: 1.5rem;\n }\n &:last-child:nth-child(odd) {\n position: relative;\n right: 24.3%;\n }\n }\n\n .post-content {\n font-size: 1.03rem;\n }\n\n footer > div.d-felx {\n width: 85%;\n }\n\n}\n\n@media all and (min-width: 1400px) {\n\n #main > div.row {\n padding-left: calc((100% - #{$main-content-max-width}) / 2);\n > div.col-xl-8 {\n max-width: calc(100% - 340px);\n }\n }\n\n #search-result-wrapper {\n padding-right: 2rem;\n > div {\n max-width: 1110px;\n }\n }\n\n #search-wrapper .fa-times-circle {\n right: 2.6rem;\n }\n\n}\n\n@media all and (min-width: 1400px) and (max-width: 1650px) {\n #topbar {\n padding-right: 2rem;\n }\n}\n\n@media all and (min-width: 1650px) {\n\n #breadcrumb {\n padding-left: 0;\n }\n\n #main > div.row > div.col-xl-8 {\n padding-left: 0;\n > div:first-child {\n padding-left: 0.55rem !important;\n padding-right: 1.9rem !important;\n }\n }\n\n #main-wrapper {\n margin-left: $sidebar-width-large;\n }\n\n #panel-wrapper {\n /* margin-left: calc((100% - #{$main-content-max-width}) / 10); */\n }\n\n #topbar-wrapper {\n left: $sidebar-width-large;\n }\n\n #topbar {\n /* max-width: #{$main-content-max-width}; */\n }\n\n #search-wrapper {\n margin-right: 3%;\n }\n\n #sidebar {\n width: $sidebar-width-large;\n\n .profile-wrapper {\n margin-top: 4rem;\n margin-bottom: 1rem;\n\n &.text-center {\n text-align: left !important;\n }\n\n %profile-ml {\n margin-left: 4.5rem;\n }\n\n #avatar {\n @extend %profile-ml;\n\n > a {\n width: 6.2rem;\n height: 6.2rem;\n &.mx-auto {\n margin-left: 0 !important;\n }\n }\n }\n\n .site-title {\n @extend %profile-ml;\n\n a {\n font-size: 1.7rem;\n letter-spacing: 1px;\n }\n }\n\n .site-subtitle {\n @extend %profile-ml;\n\n word-spacing: 0;\n margin-top: 0.3rem;\n }\n\n } // .profile-wrapper (min-width: 1650px)\n\n ul {\n padding-left: 2.5rem;\n\n > li:last-child {\n > a {\n position: static;\n }\n }\n\n .nav-item {\n text-align: left;\n\n .nav-link {\n > span {\n letter-spacing: 3px;\n }\n\n > i {\n @include icon-round(1.65rem);\n\n line-height: 1.5rem;\n font-size: 0.6rem;\n padding-top: 1px;\n padding-left: 1px;\n position: relative;\n bottom: 1px;\n\n &.unloaded {\n display: inline-block !important;\n }\n }\n }\n\n }\n }\n\n .sidebar-bottom {\n padding-left: 3.5rem;\n width: 100%;\n\n &.justify-content-center {\n -webkit-box-pack: start !important;\n -ms-flex-pack: start !important;\n justify-content: flex-start !important;\n }\n\n a {\n font-size: 1rem;\n width: 3rem;\n }\n\n i {\n @include icon-round(2rem);\n\n padding-top: 0.44rem;\n margin-top: .7rem; // multi line space\n bottom: 0;\n }\n\n #mode-toggle-wrapper {\n width: 3rem;\n\n i {\n top: 11px;\n }\n }\n\n .icon-border {\n top: 26px;\n }\n\n } // .sidebar-bottom\n\n } // #sidebar\n\n footer > div.d-flex {\n width: 92%;\n max-width: 1140px;\n }\n\n #search-result-wrapper {\n > div {\n max-width: #{$main-content-max-width};\n }\n }\n\n} // min-width: 1650px\n\n@media all and (min-width: 1700px) {\n #topbar-wrapper {\n /* 100% - 350px - (1920px - 350px); */\n }\n\n #topbar {\n /* max-width: calc(#{$main-content-max-width} + 20px); */\n }\n\n #main > div.row {\n padding-left: calc((100% - #{$main-content-max-width} - 2%) / 2);\n }\n\n #panel-wrapper {\n margin-left: 3%;\n }\n\n footer {\n padding-left: 0;\n padding-right: calc(100% - #{$sidebar-width-large} - 1180px);\n }\n\n #back-to-top {\n right: calc(100% - 1920px + 15rem);\n }\n\n}\n\n@media (min-width: 1920px) {\n #main > div.row {\n padding-left: 190px;\n }\n\n #search-result-wrapper {\n padding-right: calc(100% - #{$sidebar-width-large} - 1180px);\n }\n\n #panel-wrapper {\n margin-left: 41px;\n }\n}\n","/*\n* Mainly scss modules, only imported to `assets/css/main.scss`\n*/\n\n/* ---------- scss placeholder --------- */\n\n%heading {\n color: var(--heading-color);\n font-weight: 400;\n font-family: 'Lato', 'Microsoft Yahei', sans-serif;\n}\n\n%tag-hover {\n background: var(--tag-hover);\n transition: background 0.35s ease-in-out;\n}\n\n%table-cell {\n padding: 0.4rem 1rem;\n font-size: 95%;\n white-space: nowrap;\n}\n\n%link-hover {\n color: #d2603a !important;\n border-bottom: 1px solid #d2603a;\n text-decoration: none;\n}\n\n%link-color {\n color: var(--link-color);\n}\n\n%link-underline {\n border-bottom: 1px solid var(--link-underline-color);\n}\n\n%no-bottom-border {\n border-bottom: none;\n}\n\n%section {\n #post-wrapper & {\n line-height: 1.2;\n margin-bottom: 1rem;\n }\n}\n\n%anchor {\n padding-top: 3.5rem;\n margin-top: -2.5rem;\n}\n\n/* ---------- scss mixin --------- */\n\n@mixin no-text-decoration {\n text-decoration: none;\n}\n\n@mixin sidebar-links($color: rgba(255, 255, 255, 0.5)) {\n color: $color;\n transition: color 0.35s ease-in-out;\n user-select: none;\n}\n\n@mixin icon-round($diameter) {\n border: 1px solid;\n border-radius: 50%;\n width: $diameter;\n height: $diameter;\n}\n\n@mixin ml-mr($value) {\n margin-left: $value;\n margin-right: $value;\n}\n\n@mixin pl-pr($val) {\n padding-left: $val;\n padding-right: $val;\n}\n\n@mixin input-placeholder {\n opacity: 0.6;\n}\n\n@mixin semi-bold {\n font-weight: 600;\n}\n\n@mixin label($font-size: 1rem, $font-weight: 600, $color: var(--label-color)) {\n color: $color;\n font-size: $font-size;\n font-weight: $font-weight;\n}\n\n@mixin panel-label {\n @include label(inherit);\n\n display: block;\n line-height: 1.2;\n padding-top: 0.5rem;\n padding-bottom: 0.5rem;\n margin-top: 0;\n margin-bottom: 0;\n letter-spacing: -0.02em;\n}\n\n@mixin align-center {\n position: relative;\n left: 50%;\n -webkit-transform: translateX(-50%);\n -ms-transform: translateX(-50%);\n transform: translateX(-50%);\n}\n","/*\n* The syntax highlight.\n*/\n\n@import \"colors/light-syntax\";\n@import \"colors/dark-syntax\";\n\nhtml:not([mode]),\nhtml[mode=light] {\n @include light-syntax;\n}\n\nhtml[mode=dark] {\n @include dark-syntax;\n}\n\n@media (prefers-color-scheme: dark) {\n html:not([mode]),\n html[mode=dark] {\n @include dark-syntax;\n }\n\n html[mode=light] {\n @include light-syntax;\n }\n}\n\n/* -- Codes Snippet -- */\n\n%code-snippet-bg {\n background: var(--highlight-bg-color);\n}\n\n%code-snippet-radius {\n border-radius: 6px;\n}\n\n%code-snippet-padding {\n padding: 1.5rem;\n}\n\n$code-font-size: 0.85rem;\n\ndiv > pre {\n @extend %code-snippet-bg;\n @extend %code-snippet-radius;\n @extend %code-snippet-padding;\n}\n\n.highlighter-rouge {\n @extend %code-snippet-bg;\n @extend %code-snippet-radius;\n\n color: var(--highlighter-rouge-color);\n margin-top: 0.5rem;\n margin-bottom: 1.2em; /* Override BS Inline-code style */\n}\n\n.highlight {\n @extend %code-snippet-radius;\n @extend %code-snippet-bg;\n\n @at-root figure#{&} {\n @extend %code-snippet-bg;\n }\n\n overflow: auto;\n .lineno {\n margin-left: 0.2rem;\n padding-right: 0.5rem;\n min-width: 2.2rem;\n text-align: right;\n color: var(--highlight-lineno-color);\n border-right: 1px solid var(--highlight-lineno-border-color);\n -webkit-user-select: none;\n -khtml-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n -o-user-select: none;\n user-select: none;\n }\n pre {\n margin-bottom: 0;\n font-size: $code-font-size;\n line-height: 1.4rem;\n word-wrap: normal; /* Fixed Safari overflow-x */\n }\n table {\n padding: 0;\n border: 0;\n td pre {\n overflow: visible; /* Fixed iOS safari overflow-x */\n word-break: normal; /* Fixed iOS safari linenos code break */\n }\n }\n td {\n padding: 0;\n border: 0;\n }\n} //.highlight\n\ncode {\n -webkit-hyphens: none;\n -ms-hyphens: none;\n -moz-hyphens: none;\n hyphens: none;\n\n &.highlighter-rouge {\n font-size: $code-font-size;\n padding: 3px 5px;\n border-radius: 4px;\n background-color: var(--inline-code-bg);\n }\n\n @at-root a > &.highlighter-rouge {\n padding-bottom: 0; // show link's underlinke\n color: inherit;\n }\n\n @at-root a:hover > &.highlighter-rouge {\n border-bottom: none;\n }\n\n blockquote &.highlighter-rouge {\n color: inherit;\n }\n}\n\ntd.rouge-code {\n padding: 1.5rem 1.5rem 1.5rem 1rem;\n\n // Prevent some browser extends from\n // changing the URL string of code block.\n a {\n color: inherit !important;\n border-bottom: none !important;\n pointer-events: none;\n }\n\n}\n\n/* Hide line numbers for default, console, and terminal code snippets */\ndiv {\n &[class^='highlighter-rouge'],\n &.language-plaintext.highlighter-rouge,\n &.language-console.highlighter-rouge,\n &.language-terminal.highlighter-rouge {\n pre.lineno {\n display: none;\n }\n td.rouge-code {\n @extend %code-snippet-padding;\n }\n }\n}\n\ndiv[class^='language-']::before {\n content: attr(lang);\n position: absolute;\n right: 2rem;\n margin-top: 3px;\n font-size: 0.7rem;\n font-weight: 600;\n color: var(--highlight-lineno-color);\n text-transform: uppercase;\n}\n\n@media (min-width: 768px) {\n div[class^='language-']::before {\n right: 3.1rem;\n }\n}\n\n@media (min-width: 1650px) {\n div[class^='language-']::before {\n right: 3.5rem;\n }\n}\n","/*\n * The syntax light mode code snippet colors.\n */\n\n@mixin light-syntax {\n /* see: */\n .highlight .hll { background-color: #ffffcc; }\n .highlight .c { color: #999988; font-style: italic; } /* Comment */\n .highlight .err { color: #a61717; background-color: #e3d2d2; } /* Error */\n .highlight .k { color: #000000; font-weight: bold; } /* Keyword */\n .highlight .o { color: #000000; font-weight: bold; } /* Operator */\n .highlight .cm { color: #999988; font-style: italic; } /* Comment.Multiline */\n .highlight .cp { color: #999999; font-weight: bold; font-style: italic; } /* Comment.Preproc */\n .highlight .c1 { color: #999988; font-style: italic; } /* Comment.Single */\n .highlight .cs { color: #999999; font-weight: bold; font-style: italic; } /* Comment.Special */\n .highlight .gd { color: #d01040; background-color: #ffdddd; } /* Generic.Deleted */\n .highlight .ge { color: #000000; font-style: italic; } /* Generic.Emph */\n .highlight .gr { color: #aa0000; } /* Generic.Error */\n .highlight .gh { color: #999999; } /* Generic.Heading */\n .highlight .gi { color: #008080; background-color: #ddffdd; } /* Generic.Inserted */\n .highlight .go { color: #888888; } /* Generic.Output */\n .highlight .gp { color: #555555; } /* Generic.Prompt */\n .highlight .gs { font-weight: bold; } /* Generic.Strong */\n .highlight .gu { color: #aaaaaa; } /* Generic.Subheading */\n .highlight .gt { color: #aa0000; } /* Generic.Traceback */\n .highlight .kc { color: #000000; font-weight: bold; } /* Keyword.Constant */\n .highlight .kd { color: #000000; font-weight: bold; } /* Keyword.Declaration */\n .highlight .kn { color: #000000; font-weight: bold; } /* Keyword.Namespace */\n .highlight .kp { color: #000000; font-weight: bold; } /* Keyword.Pseudo */\n .highlight .kr { color: #000000; font-weight: bold; } /* Keyword.Reserved */\n .highlight .kt { color: #445588; font-weight: bold; } /* Keyword.Type */\n .highlight .m { color: #009999; } /* Literal.Number */\n .highlight .s { color: #d01040; } /* Literal.String */\n .highlight .na { color: #008080; } /* Name.Attribute */\n .highlight .nb { color: #0086b3; } /* Name.Builtin */\n .highlight .nc { color: #445588; font-weight: bold; } /* Name.Class */\n .highlight .no { color: #008080; } /* Name.Constant */\n .highlight .nd { color: #3c5d5d; font-weight: bold; } /* Name.Decorator */\n .highlight .ni { color: #800080; } /* Name.Entity */\n .highlight .ne { color: #990000; font-weight: bold; } /* Name.Exception */\n .highlight .nf { color: #990000; font-weight: bold; } /* Name.Function */\n .highlight .nl { color: #990000; font-weight: bold; } /* Name.Label */\n .highlight .nn { color: #555555; } /* Name.Namespace */\n .highlight .nt { color: #000080; } /* Name.Tag */\n .highlight .nv { color: #008080; } /* Name.Variable */\n .highlight .ow { color: #000000; font-weight: bold; } /* Operator.Word */\n .highlight .w { color: #bbbbbb; } /* Text.Whitespace */\n .highlight .mf { color: #009999; } /* Literal.Number.Float */\n .highlight .mh { color: #009999; } /* Literal.Number.Hex */\n .highlight .mi { color: #009999; } /* Literal.Number.Integer */\n .highlight .mo { color: #009999; } /* Literal.Number.Oct */\n .highlight .sb { color: #d01040; } /* Literal.String.Backtick */\n .highlight .sc { color: #d01040; } /* Literal.String.Char */\n .highlight .sd { color: #d01040; } /* Literal.String.Doc */\n .highlight .s2 { color: #d01040; } /* Literal.String.Double */\n .highlight .se { color: #d01040; } /* Literal.String.Escape */\n .highlight .sh { color: #d01040; } /* Literal.String.Heredoc */\n .highlight .si { color: #d01040; } /* Literal.String.Interpol */\n .highlight .sx { color: #d01040; } /* Literal.String.Other */\n .highlight .sr { color: #009926; } /* Literal.String.Regex */\n .highlight .s1 { color: #d01040; } /* Literal.String.Single */\n .highlight .ss { color: #990073; } /* Literal.String.Symbol */\n .highlight .bp { color: #999999; } /* Name.Builtin.Pseudo */\n .highlight .vc { color: #008080; } /* Name.Variable.Class */\n .highlight .vg { color: #008080; } /* Name.Variable.Global */\n .highlight .vi { color: #008080; } /* Name.Variable.Instance */\n .highlight .il { color: #009999; } /* Literal.Number.Integer.Long */\n\n /* --- custom light colors --- */\n --highlight-bg-color: #f7f7f7;\n --highlighter-rouge-color: #2f2f2f;\n --highlight-lineno-color: #c2c6cc;\n --highlight-lineno-border-color: #e9ecef;\n --inline-code-bg: #f3f3f3;\n} // light-syntax\n","/*\n * The syntax dark mode styles.\n */\n\n@mixin dark-syntax {\n /* ----- My styles ------ */\n --highlight-bg-color: #252525;\n --highlighter-rouge-color: #de6b18;\n --highlight-lineno-color: #6c6c6d;\n --highlight-lineno-border-color: #303435;\n --inline-code-bg: #272822;\n\n .highlight {\n .gp { color: #818c96; }\n }\n\n pre { color: #bfbfbf; } /* override Bootstrap */\n kbd { background-color: black; }\n\n /* syntax highlight colors from https://raw.githubusercontent.com/jwarby/pygments-css/master/monokai.css */\n .highlight pre { background-color: var(--highlight-bg-color); }\n .highlight .hll { background-color: var(--highlight-bg-color); }\n .highlight .c { color: #75715e; } /* Comment */\n .highlight .err { color: #960050; background-color: #1e0010; } /* Error */\n .highlight .k { color: #66d9ef; } /* Keyword */\n .highlight .l { color: #ae81ff; } /* Literal */\n .highlight .n { color: #f8f8f2; } /* Name */\n .highlight .o { color: #f92672; } /* Operator */\n .highlight .p { color: #f8f8f2; } /* Punctuation */\n .highlight .cm { color: #75715e; } /* Comment.Multiline */\n .highlight .cp { color: #75715e; } /* Comment.Preproc */\n .highlight .c1 { color: #75715e; } /* Comment.Single */\n .highlight .cs { color: #75715e; } /* Comment.Special */\n .highlight .ge { color: inherit; font-style: italic; } /* Generic.Emph */\n .highlight .gs { font-weight: bold; } /* Generic.Strong */\n .highlight .kc { color: #66d9ef; } /* Keyword.Constant */\n .highlight .kd { color: #66d9ef; } /* Keyword.Declaration */\n .highlight .kn { color: #f92672; } /* Keyword.Namespace */\n .highlight .kp { color: #66d9ef; } /* Keyword.Pseudo */\n .highlight .kr { color: #66d9ef; } /* Keyword.Reserved */\n .highlight .kt { color: #66d9ef; } /* Keyword.Type */\n .highlight .ld { color: #e6db74; } /* Literal.Date */\n .highlight .m { color: #ae81ff; } /* Literal.Number */\n .highlight .s { color: #e6db74; } /* Literal.String */\n .highlight .na { color: #a6e22e; } /* Name.Attribute */\n .highlight .nb { color: #f8f8f2; } /* Name.Builtin */\n .highlight .nc { color: #a6e22e; } /* Name.Class */\n .highlight .no { color: #66d9ef; } /* Name.Constant */\n .highlight .nd { color: #a6e22e; } /* Name.Decorator */\n .highlight .ni { color: #f8f8f2; } /* Name.Entity */\n .highlight .ne { color: #a6e22e; } /* Name.Exception */\n .highlight .nf { color: #a6e22e; } /* Name.Function */\n .highlight .nl { color: #f8f8f2; } /* Name.Label */\n .highlight .nn { color: #f8f8f2; } /* Name.Namespace */\n .highlight .nx { color: #a6e22e; } /* Name.Other */\n .highlight .py { color: #f8f8f2; } /* Name.Property */\n .highlight .nt { color: #f92672; } /* Name.Tag */\n .highlight .nv { color: #f8f8f2; } /* Name.Variable */\n .highlight .ow { color: #f92672; } /* Operator.Word */\n .highlight .w { color: #f8f8f2; } /* Text.Whitespace */\n .highlight .mf { color: #ae81ff; } /* Literal.Number.Float */\n .highlight .mh { color: #ae81ff; } /* Literal.Number.Hex */\n .highlight .mi { color: #ae81ff; } /* Literal.Number.Integer */\n .highlight .mo { color: #ae81ff; } /* Literal.Number.Oct */\n .highlight .sb { color: #e6db74; } /* Literal.String.Backtick */\n .highlight .sc { color: #e6db74; } /* Literal.String.Char */\n .highlight .sd { color: #e6db74; } /* Literal.String.Doc */\n .highlight .s2 { color: #e6db74; } /* Literal.String.Double */\n .highlight .se { color: #ae81ff; } /* Literal.String.Escape */\n .highlight .sh { color: #e6db74; } /* Literal.String.Heredoc */\n .highlight .si { color: #e6db74; } /* Literal.String.Interpol */\n .highlight .sx { color: #e6db74; } /* Literal.String.Other */\n .highlight .sr { color: #e6db74; } /* Literal.String.Regex */\n .highlight .s1 { color: #e6db74; } /* Literal.String.Single */\n .highlight .ss { color: #e6db74; } /* Literal.String.Symbol */\n .highlight .bp { color: #f8f8f2; } /* Name.Builtin.Pseudo */\n .highlight .vc { color: #f8f8f2; } /* Name.Variable.Class */\n .highlight .vg { color: #f8f8f2; } /* Name.Variable.Global */\n .highlight .vi { color: #f8f8f2; } /* Name.Variable.Instance */\n .highlight .il { color: #ae81ff; } /* Literal.Number.Integer.Long */\n .highlight .gu { color: #75715e; } /* Generic.Subheading & Diff Unified/Comment? */\n .highlight .gd { color: #f92672; background-color: #561c08; } /* Generic.Deleted & Diff Deleted */\n .highlight .gi { color: #a6e22e; background-color: #0b5858; } /* Generic.Inserted & Diff Inserted */\n}\n","/*\n * The syntax light mode typography colors\n */\n\n@mixin light-scheme {\n /* Common */\n --body-bg: #fafafa;\n --mask-bg: #c1c3c5;\n --main-wrapper-bg: white;\n --main-border-color: #f3f3f3;\n --btn-border-color: #e9ecef;\n --text-color: #34343c;\n --heading-color: black;\n --blockquote-border-color: #eee;\n --blockquote-text-color: #9a9a9a;\n --link-color: #2a408e;\n --link-underline-color: #dee2e6;\n --text-muted-color: gray;\n --tb-odd-bg: #fbfcfd;\n --tb-border-color: #eaeaea;\n --button-bg: #fff;\n --btn-backtotop-color: #686868;\n --btn-backtotop-border-color: #f1f1f1;\n --btn-box-shadow: #eaeaea;\n --checkbox-color: #c5c5c5;\n --checkbox-checked-color: #07a8f7;\n\n /* Sidebar */\n --sidebar-bg: radial-gradient(\n circle,\n rgba(42, 30, 107, 1) 0%,\n rgba(35, 37, 46, 1) 100%);\n --nav-cursor-color: #fcfcfc;\n\n /* Topbar */\n --topbar-wrapper-bg: white;\n --topbar-text-color: rgb(78, 78, 78);\n --search-wrapper-bg: #f5f5f5;\n --search-tag-bg: #f8f9fa;\n --search-icon-color: #c2c6cc;\n --input-focus-border-color: var(--btn-border-color);\n\n /* Home page */\n --post-list-text-color: dimgray;\n --btn-patinator-text-color: #555555;\n --btn-paginator-hover-color: #e9ecef;\n --btn-active-bg: #2a408e;\n --btn-active-border-color: #007bff;\n --btn-text-color: #f8f8f8;\n --btn-paginator-border-color: #f1f1f1;\n --btn-paginator-shadow: #4b92d2;\n --pin-bg: #f5f5f5;\n --pin-color: #999fa4;\n\n /* Posts */\n --btn-share-hover-color: var(--link-color);\n --card-border-color: #f1f1f1;\n --card-box-shadow: rgba(234, 234, 234, 0.7686274509803922);\n --label-color: #616161;\n --relate-post-date: rgba(30, 55, 70, 0.4);\n --tag-bg: rgba(0, 0, 0, 0.075);\n --tag-border: #dee2e6;\n --tag-shadow: var(--btn-border-color);\n --tag-hover: rgb(222, 226, 230);\n --categories-hover-bg: var(--btn-border-color);\n --dash-color: silver;\n\n /* Archive */\n --timeline-color: rgba(0, 0, 0, 0.075);\n --timeline-node-bg: #c2c6cc;\n --timeline-year-dot-color: #ffffff;\n\n /* Footer */\n --footer-bg-color: #ffffff;\n --footnote-target-bg: lightcyan;\n --footer-link: #424242;\n} // light-scheme\n","/*\n * The main dark mode styles\n */\n\n@mixin dark-scheme {\n /* framework */\n --main-wrapper-bg: rgb(27, 27, 30);\n --body-bg: var(--main-wrapper-bg);\n --topbar-wrapper-bg: rgb(39, 40, 43);\n --search-wrapper-bg: rgb(34, 34, 39);\n --search-icon-color: rgb(100, 102, 105);\n --input-focus-border-color: rgb(112, 114, 115);\n --mask-bg: rgb(68, 69, 70);\n --footer-bg-color: var(--main-wrapper-bg);\n\n /* common color */\n --text-color: rgb(175, 176, 177);\n --heading-color: #cccccc;\n --text-muted-color: rgb(107, 116, 124);\n --link-color: rgb(138, 180, 248);\n --link-underline-color: rgb(82, 108, 150);\n --main-border-color: rgb(44, 45, 45);\n --button-bg: rgb(39, 40, 43);\n --blockquote-border-color: rgb(66, 66, 66);\n --blockquote-text-color: rgb(117, 117, 117);\n --btn-border-color: rgb(63, 65, 68);\n --btn-backtotop-color: var(--text-color);\n --btn-backtotop-border-color: var(--btn-border-color);\n --btn-box-shadow: var(--main-wrapper-bg);\n --card-header-bg: rgb(51, 50, 50);\n --label-color: rgb(108, 117, 125);\n --checkbox-color: rgb(118 120 121);\n --checkbox-checked-color: var(--link-color);\n\n /* Sidebar */\n --nav-cursor-color: rgb(183, 182, 182);\n --sidebar-bg: radial-gradient(circle, #242424 0%, #1d1f27 100%);\n\n /* Top Bar */\n --topbar-text-color: var(--text-color);\n\n /* Home page */\n --post-list-text-color: rgb(175, 176, 177);\n --btn-patinator-text-color: var(--text-color);\n --btn-paginator-hover-color: rgb(64, 65, 66);\n --btn-active-bg: rgba(28, 52, 94, 1);\n --btn-active-border-color: rgb(66, 94, 138);\n --btn-text-color: var(--text-color);\n --btn-paginator-border-color: var(--btn-border-color);\n --btn-paginator-shadow: var(--main-wrapper-bg);\n --pin-bg: rgb(34 35 37);\n --pin-color: inherit;\n\n /* Posts */\n --toc-highlight: rgb(116, 178, 243);\n --tag-bg: rgb(41, 40, 40);\n --tag-hover: rgb(43, 56, 62);\n --tb-odd-bg: rgba(42, 47, 53, 0.52); /* odd rows of the posts' table */\n --tb-even-bg: rgb(31, 31, 34); /* even rows of the posts' table */\n --tb-border-color: var(--tb-odd-bg);\n --footnote-target-bg: rgb(63, 81, 181);\n --btn-share-color: #6c757d;\n --btn-share-hover-color: #bfc1ca;\n --relate-post-date: var(--text-muted-color);\n --card-bg: rgb(39, 40, 43);\n --card-border-color: rgb(53, 53, 60);\n --card-box-shadow: var(--main-wrapper-bg);\n\n /* tags */\n --tag-border: rgb(59, 79, 88);\n --tag-shadow: rgb(32, 33, 33);\n --search-tag-bg: var(--tag-bg);\n --dash-color: rgb(63, 65, 68);\n\n /* categories */\n --categories-border: rgb(64, 66, 69);\n --categories-hover-bg: rgb(73, 75, 76);\n\n /* archives */\n --timeline-node-bg: rgb(150, 152, 156);\n --timeline-color: rgb(63, 65, 68);\n --timeline-year-dot-color: var(--timeline-color);\n\n /* Footer */\n --footer-link: rgb(171, 171, 171);\n\n .post-content img {\n filter: brightness(90%);\n }\n\n hr {\n border-color: var(--main-border-color);\n }\n\n /* posts' toc, override BS */\n nav[data-toggle=toc] .nav-link.active,\n nav[data-toggle=toc] .nav-link.active:focus,\n nav[data-toggle=toc] .nav-link.active:hover,\n nav[data-toggle=toc] .nav > li > a:focus,\n nav[data-toggle=toc] .nav > li > a:hover {\n color: var(--toc-highlight) !important;\n border-left-color: var(--toc-highlight) !important;\n }\n\n /* categories */\n .categories.card,\n .list-group-item {\n background-color: var(--card-bg);\n }\n\n .categories {\n .card-header {\n background-color: var(--card-header-bg);\n }\n .list-group-item {\n border-left: none;\n border-right: none;\n padding-left: 2rem;\n border-color: var(--categories-border);\n &:last-child {\n border-bottom-color: var(--card-bg);\n }\n }\n }\n\n #archives li:nth-child(odd) {\n background-image: linear-gradient(\n to left,\n rgb(26, 26, 30),\n rgb(39, 39, 45),\n rgb(39, 39, 45),\n rgb(39, 39, 45),\n rgb(26, 26, 30));\n }\n\n} // dark-scheme\n","/*\n* Mainly scss variables\n*/\n\n/* --- ↓ width and height ---- */\n\n$tab-height: 3.2rem;\n$tab-cursor-height: 1.6rem;\n\n$sidebar-width-small: 210px;\n$sidebar-width-medium: 260px;\n$sidebar-width-large: 350px;\n\n$topbar-height: 3rem;\n\n$footer-height: 5rem;\n\n$main-content-max-width: 1150px;\n\n$panel-max-width: 300px;\n\n$post-extend-min-height: 35rem;\n","/*\n Style for Homepage\n*/\n\n.pagination {\n font-size: 1rem;\n a:hover {\n text-decoration: none;\n }\n\n .page-item {\n .page-link {\n color: var(--btn-patinator-text-color);\n width: 2.5rem;\n height: 2.5rem;\n padding: 0;\n text-align: center;\n display: -webkit-box;\n display: flex;\n -webkit-box-pack: center;\n justify-content: center;\n -webkit-box-align: center;\n align-items: center;\n border-radius: 50%;\n border: 1px solid var(--btn-paginator-border-color);\n font-family: 'Lato', sans-serif;\n background-color: var(--button-bg);\n &:hover {\n background-color: var(--btn-paginator-hover-color);\n }\n }\n &.active {\n .page-link {\n background-color: var(--btn-active-bg);\n border-color: var(--btn-active-border-color);\n box-shadow: 0 0 8px 0 var(--btn-paginator-shadow) !important;\n color: var(--btn-text-color);\n }\n }\n &.disabled {\n cursor: not-allowed;\n .page-link {\n color: rgba(108, 117, 125, 0.57);\n border-color: var(--btn-paginator-border-color);\n background-color: var(--button-bg);\n }\n }\n &:first-child .page-link,\n &:last-child .page-link {\n border-radius: 50%;\n }\n &:not(:last-child) {\n margin-right: 0.7rem;\n }\n } // .page-item\n\n} // .pagination\n\n#post-list {\n margin-top: 1rem;\n padding-right: 0.5rem;\n\n .post-preview {\n padding-top: 1.5rem;\n padding-bottom: 1rem;\n border-bottom: 1px solid var(--main-border-color);\n\n a:hover {\n @extend %link-hover;\n }\n\n h1 {\n font-size: 1.4rem;\n margin: 0;\n }\n\n .post-meta {\n i {\n font-size: 0.73rem;\n }\n span:not(:last-child) {\n margin-right: 1.2rem;\n }\n }\n\n .post-content {\n margin-top: 0.6rem;\n margin-bottom: 0.6rem;\n color: var(--post-list-text-color);\n > p {\n /* Make preview shorter on the homepage */\n margin: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n }\n }\n\n .pin {\n > i {\n transform: rotate(45deg);\n padding-left: 3px;\n color: var(--pin-color);\n }\n > span {\n display: none;\n }\n }\n\n } // .post-preview\n\n} // #post-list\n\n/* Hide SideBar and TOC */\n@media all and (max-width: 830px) {\n .pagination {\n justify-content: center;\n }\n}\n\n/* Sidebar is visible */\n@media all and (min-width: 831px) {\n\n #post-list {\n margin-top: 1.5rem;\n .post-preview .post-meta {\n .pin {\n background: var(--pin-bg);\n border-radius: 5px;\n line-height: 1.4rem;\n height: 1.3rem;\n margin-top: 3px;\n padding-left: 1px;\n padding-right: 6px;\n\n > span {\n display: inline;\n }\n }\n }\n }\n\n .pagination {\n font-size: 0.85rem;\n .page-item .page-link {\n width: 2.2rem;\n height: 2.2rem;\n }\n }\n\n}\n\n/* Pannel hidden */\n@media all and (max-width: 1200px) {\n #post-list {\n padding-right: 0;\n }\n}\n","/*\n Post-specific style\n*/\n\n@mixin btn-sharing-color($light-color, $important: false) {\n @if $important {\n color: var(--btn-share-color, $light-color) !important;\n } @else {\n color: var(--btn-share-color, $light-color);\n }\n}\n\n@mixin btn-post-nav {\n width: 50%;\n position: relative;\n border-color: var(--btn-border-color);\n}\n\n@mixin dot($pl: 0.2rem, $pr: 0.4rem) {\n content: \"\\2022\";\n color: rgba(158, 158, 158, 0.8);\n padding-left: $pl;\n padding-right: $pr;\n}\n\n.timeago::before {\n content: attr(prefix);\n}\n\n#post-wrapper .post-meta {\n > div:nth-child(2) {\n > span:not(:first-child)::before {\n @include dot;\n }\n }\n\n #pv::after {\n content: \" views\";\n }\n\n .readtime::after {\n content: \" read\";\n }\n}\n\n.post-content {\n .preview-img {\n @include align-center;\n\n margin-top: 0;\n margin-bottom: 2.5rem !important;\n }\n h1, h2, h3 {\n font-weight: bold;\n }\n}\n\n.post-tail-wrapper {\n margin-top: 6rem;\n border-bottom: 1px double var(--main-border-color);\n font-size: 0.85rem;\n}\n\n.post-tags {\n line-height: 2rem;\n}\n\n.post-navigation {\n padding-top: 3rem;\n padding-bottom: 4rem;\n\n .btn {\n @include btn-post-nav;\n\n color: var(--link-color);\n\n &:hover {\n background: #2a408e;\n color: #fff;\n border-color: #2a408e;\n }\n\n &.disabled {\n @include btn-post-nav;\n\n pointer-events: auto;\n cursor: not-allowed;\n background: none;\n color: gray;\n\n &:hover {\n border-color: none;\n }\n }\n\n &.btn-outline-primary.disabled:focus {\n box-shadow: none;\n }\n\n &::before {\n color: var(--text-muted-color);\n font-size: 0.65rem;\n text-transform: uppercase;\n content: attr(prompt);\n }\n\n &:first-child {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n left: 0.5px;\n }\n\n &:last-child {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n right: 0.5px;\n }\n }\n\n p {\n font-size: 1.1rem;\n line-height: 1.5rem;\n margin-top: 0.3rem;\n white-space: normal;\n }\n\n} // .post-navigation\n\n@keyframes fade-up {\n from {\n opacity: 0;\n position: relative;\n top: 2rem;\n }\n to {\n opacity: 1;\n position: relative;\n top: 0;\n }\n}\n\n#toc-wrapper {\n border-left: 1px solid rgba(158, 158, 158, 0.17);\n position: -webkit-sticky;\n position: sticky;\n top: 4rem;\n transition: top 0.2s ease-in-out;\n animation: fade-up 0.8s;\n &.topbar-down {\n top: 6rem;\n }\n > span {\n @include panel-label;\n }\n}\n\n#toc li > a {\n line-height: 1rem;\n padding-top: 0.5rem;\n padding-bottom: 0.5rem;\n\n &.nav-link:not(.active) {\n color: inherit;\n }\n}\n\n/* --- Related Posts --- */\n\n#related-posts {\n > h3 {\n @include label(1.1rem, 600);\n }\n .card {\n border-color: var(--card-border-color);\n background-color: var(--card-bg);\n box-shadow: 0 0 5px 0 var(--card-box-shadow);\n -webkit-transition: all 0.3s ease-in-out;\n -moz-transition: all 0.3s ease-in-out;\n transition: all 0.3s ease-in-out;\n h3 {\n color: var(--text-color);\n }\n &:hover {\n -webkit-transform: translate3d(0, -3px, 0);\n transform: translate3d(0, -3px, 0);\n box-shadow: 0 10px 15px -4px rgba(0, 0, 0, 0.15);\n }\n }\n\n .timeago {\n color: var(--relate-post-date);\n }\n\n p {\n font-size: 0.9rem;\n margin-bottom: 0.5rem;\n overflow: hidden;\n text-overflow: ellipsis;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n }\n\n a:hover {\n text-decoration: none;\n }\n\n ul {\n list-style-type: none;\n padding-inline-start: 1.5rem;\n > li::before {\n background: #c2c9d4;\n width: 5px;\n height: 5px;\n border-radius: 1px;\n display: block;\n content: \"\";\n position: relative;\n top: 1rem;\n right: 1rem;\n }\n }\n}\n\n#post-extend-wrapper {\n min-height: 2rem;\n #disqus_thread {\n margin-bottom: 2rem;\n }\n}\n\n.post-tail-bottom a {\n color: inherit;\n}\n\n%btn-share-hovor {\n color: var(--btn-share-hover-color) !important;\n}\n\n.share-wrapper {\n vertical-align: middle;\n user-select: none;\n\n .share-icons {\n font-size: 1.2rem;\n a {\n &:not(:last-child) {\n margin-right: 0.25rem;\n }\n &:hover {\n text-decoration: none;\n > i {\n @extend %btn-share-hovor;\n }\n }\n }\n > i {\n padding-top: 0.35rem;\n &:hover {\n @extend %btn-share-hovor;\n }\n }\n .fab {\n &.fa-twitter {\n @include btn-sharing-color(rgba(29, 161, 242, 1));\n }\n &.fa-facebook-square {\n @include btn-sharing-color(rgb(66, 95, 156));\n }\n &.fa-telegram {\n @include btn-sharing-color(rgb(39, 159, 217));\n }\n &.fa-weibo {\n @include btn-sharing-color(rgb(229, 20, 43));\n }\n }\n\n } // .share-icons\n\n .fas.fa-link {\n @include btn-sharing-color(rgb(171, 171, 171));\n }\n\n} // .share-wrapper\n\n.share-label {\n @include label(inherit, 400, inherit);\n\n &::after {\n content: \":\";\n }\n}\n\n.license-wrapper {\n line-height: 1.2rem;\n > a {\n font-weight: 600;\n &:hover {\n @extend %link-hover;\n }\n }\n\n span:last-child {\n font-size: 0.85rem;\n }\n} // .license-wrapper\n\n@media all and (max-width: 576px) {\n .post-tail-bottom {\n -ms-flex-wrap: wrap-reverse !important;\n flex-wrap: wrap-reverse !important;\n >div:first-child {\n width: 100%;\n margin-top: 1rem;\n }\n }\n\n .post-content > div[class^='language-'] {\n @include ml-mr(-1.25rem);\n\n border-radius: 0;\n &::before { // the lang badge\n right: 1rem;\n }\n }\n\n}\n\n@media all and (max-width: 768px) {\n .post-content > p > img {\n max-width: calc(100% + 1rem);\n }\n}\n\n@media all and (min-width: 768px) {\n #post-wrapper {\n .post-meta {\n >div:not(:first-child)::before {\n @include dot(0.5rem, 0.2rem);\n }\n &.flex-column {\n -webkit-box-orient: horizontal !important;\n -webkit-box-direction: normal !important;\n -ms-flex-direction: row !important;\n flex-direction: row !important;\n }\n }\n } // .post\n}\n\n/* Hide SideBar and TOC */\n@media all and (max-width: 830px) {\n .post-navigation {\n padding-left: 0;\n padding-right: 0;\n margin-left: -.5rem;\n margin-right: -.5rem;\n }\n}\n","/*\n Styles for Tab Tags\n*/\n\n.tag {\n border-radius: 0.7em;\n padding: 6px 8px 7px;\n margin-right: 0.8rem;\n line-height: 3rem;\n letter-spacing: 0;\n border: 1px solid var(--tag-border) !important;\n box-shadow: 0 0 3px 0 var(--tag-shadow);\n span {\n margin-left: 0.6em;\n font-size: 0.7em;\n font-family: 'Oswald', sans-serif;\n }\n}\n","/*\n Style for Archives\n*/\n\n%date-timeline {\n content: \"\";\n width: 4px;\n left: 75px;\n display: inline-block;\n float: left;\n position: relative;\n background-color: var(--timeline-color);\n}\n\n#archives {\n letter-spacing: 0.03rem;\n\n span.lead {\n font-size: 1.5rem;\n position: relative;\n left: 8px;\n\n &::after { /* Year dot */\n content: \"\";\n display: block;\n position: relative;\n -webkit-border-radius: 50%;\n -moz-border-radius: 50%;\n border-radius: 50%;\n width: 12px;\n height: 12px;\n top: -26px;\n left: 63px;\n border: 3px solid;\n background-color: var(--timeline-year-dot-color);\n border-color: var(--timeline-node-bg);\n box-shadow: 0 0 2px 0 #c2c6cc;\n z-index: 1;\n }\n\n &:not(:first-child) {\n position: relative;\n left: 4px;\n &::after {\n left: 67px;\n }\n }\n\n } // #archives span.lead\n\n ul {\n li {\n font-size: 1.1rem;\n line-height: 3rem;\n\n div {\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n\n a {\n /* post title in Archvies */\n margin-left: 2.5rem;\n position: relative;\n top: 0.1rem;\n }\n }\n\n &:nth-child(odd) {\n background-color: var(--main-wrapper-bg, #fff);\n background-image: linear-gradient(to left, #fff, #fbfbfb, #fbfbfb, #fbfbfb, #fff);\n }\n\n &::after {\n @extend %date-timeline;\n\n height: 2.8rem;\n top: -1.3rem;\n }\n\n &:first-child::before {\n @extend %date-timeline;\n\n height: 3.06rem;\n top: -1.61rem;\n }\n }\n\n &:not(:last-child) > li:last-child::after {\n height: 3.4rem;\n }\n\n &:last-child > li:last-child::after {\n display: none;\n }\n } // #archives ul\n\n .date {\n white-space: nowrap;\n display: inline-block;\n &.month {\n width: 1.4rem;\n text-align: center;\n\n ~ a::before {\n /* A dot for Month and Day */\n content: \"\";\n display: inline-block;\n position: relative;\n -webkit-border-radius: 50%;\n -moz-border-radius: 50%;\n border-radius: 50%;\n width: 8px;\n height: 8px;\n float: left;\n top: 1.35rem;\n left: 69px;\n background-color: var(--timeline-node-bg);\n box-shadow: 0 0 3px 0 #c2c6cc;\n z-index: 1;\n }\n }\n &.day {\n font-size: 85%;\n font-family: 'Lato', sans-serif;\n text-align: center;\n margin-right: -2px;\n width: 1.2rem;\n position: relative;\n left: -.15rem;\n }\n } // #archives .date\n\n} // #archives\n\n@media all and (max-width: 576px) {\n #archives {\n margin-top: -1rem;\n ul {\n letter-spacing: 0;\n }\n }\n}\n","/*\n Style for Tab Categories\n*/\n\n%category-icon-color {\n color: gray;\n}\n\n.categories {\n margin-bottom: 2rem;\n .card-header {\n padding-right: 12px;\n }\n\n i {\n &.far,\n &.fas {\n font-size: 86%; // fontawesome icons\n }\n }\n\n .list-group-item {\n border-left: none;\n border-right: none;\n padding-left: 2rem;\n > i {\n @extend %category-icon-color;\n }\n &:first-child {\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n }\n\n }\n\n .card-header > span > i:first-child {\n @extend %category-icon-color;\n }\n\n} // .categories\n\n.category-trigger {\n width: 1.7rem;\n height: 1.7rem;\n border-radius: 50%;\n text-align: center;\n color: #6c757d !important;\n > i.fas {\n position: relative;\n height: 0.7rem;\n width: 1rem;\n transition: 300ms ease all;\n }\n}\n\n@media (hover: hover) { // only works on desktop\n .category-trigger:hover {\n background-color: var(--categories-hover-bg);\n }\n}\n\n.rotate {\n -ms-transform: rotate(-90deg); /* IE 9 */\n -webkit-transform: rotate(-90deg); /* Safari 3-8 */\n transform: rotate(-90deg);\n}\n","/*\n Style for page Category and Tag\n*/\n\n.dash {\n margin: 0 .5rem .6rem .5rem;\n border-bottom: 2px dotted var(--dash-color);\n}\n\n#page-category,\n#page-tag {\n ul > li {\n line-height: 1.5rem;\n padding: 0.6rem 0;\n\n &::before { // dot\n background: #999;\n width: 5px;\n height: 5px;\n border-radius: 50%;\n display: block;\n content: \"\";\n position: relative;\n top: 0.6rem;\n margin-right: 0.5rem;\n }\n\n > a { /* post's title */\n @extend %no-bottom-border;\n\n font-size: 1.1rem;\n }\n\n > span:last-child {\n white-space: nowrap;\n } /* post's date */\n }\n}\n\n#page-tag h1 > i { // tag icon\n font-size: 1.2rem;\n}\n\n#page-category h1 > i {\n font-size: 1.25rem;\n}\n\n#page-category,\n#page-tag,\n#access-lastmod {\n a:hover {\n @extend %link-hover;\n\n margin-bottom: -1px; // Avoid jumping\n }\n}\n\n@media all and (max-width: 576px) {\n #page-category,\n #page-tag {\n ul > li {\n &::before {\n margin: 0 .5rem;\n }\n > a {\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n }\n }\n}\n"],"file":"style.css"} \ No newline at end of file diff --git a/assets/image-20230603185315584.png b/assets/image-20230603185315584.png new file mode 100644 index 000000000..38d5340bd Binary files /dev/null and b/assets/image-20230603185315584.png differ diff --git a/assets/image-20230603190020412.png b/assets/image-20230603190020412.png new file mode 100644 index 000000000..b21c7466f Binary files /dev/null and b/assets/image-20230603190020412.png differ diff --git a/assets/image/Pasted image 20230819181036.png b/assets/image/Pasted image 20230819181036.png new file mode 100644 index 000000000..9fc63f554 Binary files /dev/null and b/assets/image/Pasted image 20230819181036.png differ diff --git a/assets/image/Pasted image 20230819181043.png b/assets/image/Pasted image 20230819181043.png new file mode 100644 index 000000000..9fc63f554 Binary files /dev/null and b/assets/image/Pasted image 20230819181043.png differ diff --git a/assets/images/Pasted image 20240702214523.png b/assets/images/Pasted image 20240702214523.png new file mode 100644 index 000000000..52869e07a Binary files /dev/null and b/assets/images/Pasted image 20240702214523.png differ diff --git a/assets/images/Pasted image 20240702214527.png b/assets/images/Pasted image 20240702214527.png new file mode 100644 index 000000000..52869e07a Binary files /dev/null and b/assets/images/Pasted image 20240702214527.png differ diff --git a/assets/images/Pasted image 20240702220301.png b/assets/images/Pasted image 20240702220301.png new file mode 100644 index 000000000..c0a2aaf0c Binary files /dev/null and b/assets/images/Pasted image 20240702220301.png differ diff --git a/assets/images/Pasted image 20240704102705.png b/assets/images/Pasted image 20240704102705.png new file mode 100644 index 000000000..889539a85 Binary files /dev/null and b/assets/images/Pasted image 20240704102705.png differ diff --git a/assets/images/Pasted image 20240704102707.png b/assets/images/Pasted image 20240704102707.png new file mode 100644 index 000000000..889539a85 Binary files /dev/null and b/assets/images/Pasted image 20240704102707.png differ diff --git a/assets/images/Pasted image 20240704110123.png b/assets/images/Pasted image 20240704110123.png new file mode 100644 index 000000000..171070bf2 Binary files /dev/null and b/assets/images/Pasted image 20240704110123.png differ diff --git a/assets/images/Pasted image 20240704110124.png b/assets/images/Pasted image 20240704110124.png new file mode 100644 index 000000000..171070bf2 Binary files /dev/null and b/assets/images/Pasted image 20240704110124.png differ diff --git a/assets/images/Pasted image 20240704110502.png b/assets/images/Pasted image 20240704110502.png new file mode 100644 index 000000000..b919f5727 Binary files /dev/null and b/assets/images/Pasted image 20240704110502.png differ diff --git a/assets/images/Pasted image 20240704110521.png b/assets/images/Pasted image 20240704110521.png new file mode 100644 index 000000000..b919f5727 Binary files /dev/null and b/assets/images/Pasted image 20240704110521.png differ diff --git a/assets/images/dpblog.png b/assets/images/dpblog.png new file mode 100644 index 000000000..c0a2aaf0c Binary files /dev/null and b/assets/images/dpblog.png differ diff --git a/assets/img/2021-11-25-st-algorithm/Screenshot_1637750381.png b/assets/img/2021-11-25-st-algorithm/Screenshot_1637750381.png new file mode 100644 index 000000000..48bc6868e Binary files /dev/null and b/assets/img/2021-11-25-st-algorithm/Screenshot_1637750381.png differ diff --git a/assets/img/2021-11-25-st-algorithm/Screenshot_1637750872.png b/assets/img/2021-11-25-st-algorithm/Screenshot_1637750872.png new file mode 100644 index 000000000..7eecc595b Binary files /dev/null and b/assets/img/2021-11-25-st-algorithm/Screenshot_1637750872.png differ diff --git a/assets/img/2021-11-25-st-algorithm/Screenshot_1637751957.png b/assets/img/2021-11-25-st-algorithm/Screenshot_1637751957.png new file mode 100644 index 000000000..57d396db2 Binary files /dev/null and b/assets/img/2021-11-25-st-algorithm/Screenshot_1637751957.png differ diff --git a/assets/img/2021-11-25-st-algorithm/Screenshot_1637752084.png b/assets/img/2021-11-25-st-algorithm/Screenshot_1637752084.png new file mode 100644 index 000000000..324686ced Binary files /dev/null and b/assets/img/2021-11-25-st-algorithm/Screenshot_1637752084.png differ diff --git "a/assets/img/2021-12-22-flutter-(rn-\352\260\234\353\260\234\354\236\220\353\245\274-\354\234\204\355\225\234-\354\240\225\353\246\2543)/canvas-2cc207759f6ab912bf73e1c3298dc2183618ef207ed989f4d83f6c08fd3a3279.png" "b/assets/img/2021-12-22-flutter-(rn-\352\260\234\353\260\234\354\236\220\353\245\274-\354\234\204\355\225\234-\354\240\225\353\246\2543)/canvas-2cc207759f6ab912bf73e1c3298dc2183618ef207ed989f4d83f6c08fd3a3279.png" new file mode 100644 index 000000000..567bc0b74 Binary files /dev/null and "b/assets/img/2021-12-22-flutter-(rn-\352\260\234\353\260\234\354\236\220\353\245\274-\354\234\204\355\225\234-\354\240\225\353\246\2543)/canvas-2cc207759f6ab912bf73e1c3298dc2183618ef207ed989f4d83f6c08fd3a3279.png" differ diff --git "a/assets/img/2021-12-22-flutter-(rn-\352\260\234\353\260\234\354\236\220\353\245\274-\354\234\204\355\225\234-\354\240\225\353\246\2543)/stack-04b7bf2727e1eb71f5dfea8430ee833f24be1ced1893ae86270795b2ab76c5b9.png" "b/assets/img/2021-12-22-flutter-(rn-\352\260\234\353\260\234\354\236\220\353\245\274-\354\234\204\355\225\234-\354\240\225\353\246\2543)/stack-04b7bf2727e1eb71f5dfea8430ee833f24be1ced1893ae86270795b2ab76c5b9.png" new file mode 100644 index 000000000..f70b75940 Binary files /dev/null and "b/assets/img/2021-12-22-flutter-(rn-\352\260\234\353\260\234\354\236\220\353\245\274-\354\234\204\355\225\234-\354\240\225\353\246\2543)/stack-04b7bf2727e1eb71f5dfea8430ee833f24be1ced1893ae86270795b2ab76c5b9.png" differ diff --git "a/assets/img/2022-01-02-\353\257\270\354\204\270\353\250\274\354\247\200\354\230\210\354\270\241\353\252\250\353\215\270/image-20220102181633815.png" "b/assets/img/2022-01-02-\353\257\270\354\204\270\353\250\274\354\247\200\354\230\210\354\270\241\353\252\250\353\215\270/image-20220102181633815.png" new file mode 100644 index 000000000..55fb06e45 Binary files /dev/null and "b/assets/img/2022-01-02-\353\257\270\354\204\270\353\250\274\354\247\200\354\230\210\354\270\241\353\252\250\353\215\270/image-20220102181633815.png" differ diff --git "a/assets/img/2022-01-02-\353\257\270\354\204\270\353\250\274\354\247\200\354\230\210\354\270\241\353\252\250\353\215\270/image-20220102181654794.png" "b/assets/img/2022-01-02-\353\257\270\354\204\270\353\250\274\354\247\200\354\230\210\354\270\241\353\252\250\353\215\270/image-20220102181654794.png" new file mode 100644 index 000000000..279869549 Binary files /dev/null and "b/assets/img/2022-01-02-\353\257\270\354\204\270\353\250\274\354\247\200\354\230\210\354\270\241\353\252\250\353\215\270/image-20220102181654794.png" differ diff --git "a/assets/img/2022-01-05-css\354\204\240\355\203\235\354\236\220/3.png" "b/assets/img/2022-01-05-css\354\204\240\355\203\235\354\236\220/3.png" new file mode 100644 index 000000000..52fbf4c90 Binary files /dev/null and "b/assets/img/2022-01-05-css\354\204\240\355\203\235\354\236\220/3.png" differ diff --git a/assets/img/2022-01-10-reactv18/1.png b/assets/img/2022-01-10-reactv18/1.png new file mode 100644 index 000000000..162515eca Binary files /dev/null and b/assets/img/2022-01-10-reactv18/1.png differ diff --git a/assets/img/2022-01-10-reactv18/2.png b/assets/img/2022-01-10-reactv18/2.png new file mode 100644 index 000000000..52173cef1 Binary files /dev/null and b/assets/img/2022-01-10-reactv18/2.png differ diff --git a/assets/img/2022-01-10-reactv18/3.png b/assets/img/2022-01-10-reactv18/3.png new file mode 100644 index 000000000..481ad2bbf Binary files /dev/null and b/assets/img/2022-01-10-reactv18/3.png differ diff --git a/assets/img/2022-01-10-reactv18/4.png b/assets/img/2022-01-10-reactv18/4.png new file mode 100644 index 000000000..9b60f17af Binary files /dev/null and b/assets/img/2022-01-10-reactv18/4.png differ diff --git a/assets/img/2022-01-10-reactv18/5.png b/assets/img/2022-01-10-reactv18/5.png new file mode 100644 index 000000000..053bcaeb5 Binary files /dev/null and b/assets/img/2022-01-10-reactv18/5.png differ diff --git a/assets/img/2022-01-10-reactv18/6.png b/assets/img/2022-01-10-reactv18/6.png new file mode 100644 index 000000000..b66ead957 Binary files /dev/null and b/assets/img/2022-01-10-reactv18/6.png differ diff --git a/assets/img/2022-01-10-reactv18/7.png b/assets/img/2022-01-10-reactv18/7.png new file mode 100644 index 000000000..7d7ff1de0 Binary files /dev/null and b/assets/img/2022-01-10-reactv18/7.png differ diff --git "a/assets/img/2022-01-17-MVC \355\214\250\355\204\264/image-20220117005500338.png" "b/assets/img/2022-01-17-MVC \355\214\250\355\204\264/image-20220117005500338.png" new file mode 100644 index 000000000..15d434aa7 Binary files /dev/null and "b/assets/img/2022-01-17-MVC \355\214\250\355\204\264/image-20220117005500338.png" differ diff --git "a/assets/img/2022-01-17-MVC \355\214\250\355\204\264/image-20220117005529402.png" "b/assets/img/2022-01-17-MVC \355\214\250\355\204\264/image-20220117005529402.png" new file mode 100644 index 000000000..15d434aa7 Binary files /dev/null and "b/assets/img/2022-01-17-MVC \355\214\250\355\204\264/image-20220117005529402.png" differ diff --git a/assets/img/2022-01-18-MVVM-MVP/img1.daumcdn-16424351335101.png b/assets/img/2022-01-18-MVVM-MVP/img1.daumcdn-16424351335101.png new file mode 100644 index 000000000..481452a31 Binary files /dev/null and b/assets/img/2022-01-18-MVVM-MVP/img1.daumcdn-16424351335101.png differ diff --git a/assets/img/2022-01-18-MVVM-MVP/img1.daumcdn.png b/assets/img/2022-01-18-MVVM-MVP/img1.daumcdn.png new file mode 100644 index 000000000..56ab6017e Binary files /dev/null and b/assets/img/2022-01-18-MVVM-MVP/img1.daumcdn.png differ diff --git "a/assets/img/2022-01-21-flex,grid\353\241\234 \353\202\250\353\212\224\352\263\265\352\260\204 \354\261\204\354\232\260\352\270\260/image-20220121013951112.png" "b/assets/img/2022-01-21-flex,grid\353\241\234 \353\202\250\353\212\224\352\263\265\352\260\204 \354\261\204\354\232\260\352\270\260/image-20220121013951112.png" new file mode 100644 index 000000000..6f20fa7b5 Binary files /dev/null and "b/assets/img/2022-01-21-flex,grid\353\241\234 \353\202\250\353\212\224\352\263\265\352\260\204 \354\261\204\354\232\260\352\270\260/image-20220121013951112.png" differ diff --git "a/assets/img/2022-01-21-flex,grid\353\241\234 \353\202\250\353\212\224\352\263\265\352\260\204 \354\261\204\354\232\260\352\270\260/image-20220121014113322.png" "b/assets/img/2022-01-21-flex,grid\353\241\234 \353\202\250\353\212\224\352\263\265\352\260\204 \354\261\204\354\232\260\352\270\260/image-20220121014113322.png" new file mode 100644 index 000000000..27a0fbfdc Binary files /dev/null and "b/assets/img/2022-01-21-flex,grid\353\241\234 \353\202\250\353\212\224\352\263\265\352\260\204 \354\261\204\354\232\260\352\270\260/image-20220121014113322.png" differ diff --git "a/assets/img/2022-01-21-flex,grid\353\241\234 \353\202\250\353\212\224\352\263\265\352\260\204 \354\261\204\354\232\260\352\270\260/image-20220121015024107.png" "b/assets/img/2022-01-21-flex,grid\353\241\234 \353\202\250\353\212\224\352\263\265\352\260\204 \354\261\204\354\232\260\352\270\260/image-20220121015024107.png" new file mode 100644 index 000000000..11685c886 Binary files /dev/null and "b/assets/img/2022-01-21-flex,grid\353\241\234 \353\202\250\353\212\224\352\263\265\352\260\204 \354\261\204\354\232\260\352\270\260/image-20220121015024107.png" differ diff --git "a/assets/img/2022-01-21-flex,grid\353\241\234 \353\202\250\353\212\224\352\263\265\352\260\204 \354\261\204\354\232\260\352\270\260/image-20220121015303592.png" "b/assets/img/2022-01-21-flex,grid\353\241\234 \353\202\250\353\212\224\352\263\265\352\260\204 \354\261\204\354\232\260\352\270\260/image-20220121015303592.png" new file mode 100644 index 000000000..bf88d5a5d Binary files /dev/null and "b/assets/img/2022-01-21-flex,grid\353\241\234 \353\202\250\353\212\224\352\263\265\352\260\204 \354\261\204\354\232\260\352\270\260/image-20220121015303592.png" differ diff --git "a/assets/img/2022-01-21-flex,grid\353\241\234 \353\202\250\353\212\224\352\263\265\352\260\204 \354\261\204\354\232\260\352\270\260/image-20220121015322451.png" "b/assets/img/2022-01-21-flex,grid\353\241\234 \353\202\250\353\212\224\352\263\265\352\260\204 \354\261\204\354\232\260\352\270\260/image-20220121015322451.png" new file mode 100644 index 000000000..fcada8424 Binary files /dev/null and "b/assets/img/2022-01-21-flex,grid\353\241\234 \353\202\250\353\212\224\352\263\265\352\260\204 \354\261\204\354\232\260\352\270\260/image-20220121015322451.png" differ diff --git "a/assets/img/2022-01-21-flex,grid\353\241\234 \353\202\250\353\212\224\352\263\265\352\260\204 \354\261\204\354\232\260\352\270\260/image-20220121015610969.png" "b/assets/img/2022-01-21-flex,grid\353\241\234 \353\202\250\353\212\224\352\263\265\352\260\204 \354\261\204\354\232\260\352\270\260/image-20220121015610969.png" new file mode 100644 index 000000000..12fb43645 Binary files /dev/null and "b/assets/img/2022-01-21-flex,grid\353\241\234 \353\202\250\353\212\224\352\263\265\352\260\204 \354\261\204\354\232\260\352\270\260/image-20220121015610969.png" differ diff --git "a/assets/img/2022-01-21-flex,grid\353\241\234 \353\202\250\353\212\224\352\263\265\352\260\204 \354\261\204\354\232\260\352\270\260/image-20220121020027651.png" "b/assets/img/2022-01-21-flex,grid\353\241\234 \353\202\250\353\212\224\352\263\265\352\260\204 \354\261\204\354\232\260\352\270\260/image-20220121020027651.png" new file mode 100644 index 000000000..c137f445d Binary files /dev/null and "b/assets/img/2022-01-21-flex,grid\353\241\234 \353\202\250\353\212\224\352\263\265\352\260\204 \354\261\204\354\232\260\352\270\260/image-20220121020027651.png" differ diff --git "a/assets/img/2022-01-21-flex,grid\353\241\234 \353\202\250\353\212\224\352\263\265\352\260\204 \354\261\204\354\232\260\352\270\260/image-20220121020823208.png" "b/assets/img/2022-01-21-flex,grid\353\241\234 \353\202\250\353\212\224\352\263\265\352\260\204 \354\261\204\354\232\260\352\270\260/image-20220121020823208.png" new file mode 100644 index 000000000..53c81af5c Binary files /dev/null and "b/assets/img/2022-01-21-flex,grid\353\241\234 \353\202\250\353\212\224\352\263\265\352\260\204 \354\261\204\354\232\260\352\270\260/image-20220121020823208.png" differ diff --git a/assets/img/2022-01-22-datastructure/2.png b/assets/img/2022-01-22-datastructure/2.png new file mode 100644 index 000000000..57817fc6d Binary files /dev/null and b/assets/img/2022-01-22-datastructure/2.png differ diff --git a/assets/img/2022-01-24-network/image-20220124010027316.png b/assets/img/2022-01-24-network/image-20220124010027316.png new file mode 100644 index 000000000..30c08bffe Binary files /dev/null and b/assets/img/2022-01-24-network/image-20220124010027316.png differ diff --git a/assets/img/2022-01-24-network/image-20220124010203287.png b/assets/img/2022-01-24-network/image-20220124010203287.png new file mode 100644 index 000000000..beb79a26b Binary files /dev/null and b/assets/img/2022-01-24-network/image-20220124010203287.png differ diff --git "a/assets/img/2022-02-03-react-\353\241\234\353\224\251-\354\262\230\353\246\254/adasd.gif" "b/assets/img/2022-02-03-react-\353\241\234\353\224\251-\354\262\230\353\246\254/adasd.gif" new file mode 100644 index 000000000..751d7989c Binary files /dev/null and "b/assets/img/2022-02-03-react-\353\241\234\353\224\251-\354\262\230\353\246\254/adasd.gif" differ diff --git "a/assets/img/2022-02-03-react-\353\241\234\353\224\251-\354\262\230\353\246\254/image-20220203032433794.png" "b/assets/img/2022-02-03-react-\353\241\234\353\224\251-\354\262\230\353\246\254/image-20220203032433794.png" new file mode 100644 index 000000000..f757297c5 Binary files /dev/null and "b/assets/img/2022-02-03-react-\353\241\234\353\224\251-\354\262\230\353\246\254/image-20220203032433794.png" differ diff --git "a/assets/img/2022-02-04-database \352\260\234\354\232\224/deadlock.png" "b/assets/img/2022-02-04-database \352\260\234\354\232\224/deadlock.png" new file mode 100644 index 000000000..8dd8edc82 Binary files /dev/null and "b/assets/img/2022-02-04-database \352\260\234\354\232\224/deadlock.png" differ diff --git "a/assets/img/2022-02-04-database \352\260\234\354\232\224/image-20220206005652242.png" "b/assets/img/2022-02-04-database \352\260\234\354\232\224/image-20220206005652242.png" new file mode 100644 index 000000000..269bb3e95 Binary files /dev/null and "b/assets/img/2022-02-04-database \352\260\234\354\232\224/image-20220206005652242.png" differ diff --git "a/assets/img/2022-02-04-database \352\260\234\354\232\224/transaction-status.png" "b/assets/img/2022-02-04-database \352\260\234\354\232\224/transaction-status.png" new file mode 100644 index 000000000..668a43f76 Binary files /dev/null and "b/assets/img/2022-02-04-database \352\260\234\354\232\224/transaction-status.png" differ diff --git "a/assets/img/2022-02-08-javascript \354\247\210\353\254\270/browser-structure.png" "b/assets/img/2022-02-08-javascript \354\247\210\353\254\270/browser-structure.png" new file mode 100644 index 000000000..ef54a49a8 Binary files /dev/null and "b/assets/img/2022-02-08-javascript \354\247\210\353\254\270/browser-structure.png" differ diff --git "a/assets/img/2022-02-08-javascript \354\247\210\353\254\270/image-20220209004707394.png" "b/assets/img/2022-02-08-javascript \354\247\210\353\254\270/image-20220209004707394.png" new file mode 100644 index 000000000..ece1cffb5 Binary files /dev/null and "b/assets/img/2022-02-08-javascript \354\247\210\353\254\270/image-20220209004707394.png" differ diff --git a/assets/img/2022-02-09-hoisting/image-20220209020517822.png b/assets/img/2022-02-09-hoisting/image-20220209020517822.png new file mode 100644 index 000000000..610927598 Binary files /dev/null and b/assets/img/2022-02-09-hoisting/image-20220209020517822.png differ diff --git a/assets/img/2022-02-18-promise-async-await/257CF64C5444D93006.png b/assets/img/2022-02-18-promise-async-await/257CF64C5444D93006.png new file mode 100644 index 000000000..c9d82f8e6 Binary files /dev/null and b/assets/img/2022-02-18-promise-async-await/257CF64C5444D93006.png differ diff --git a/assets/img/2022-02-22-Gof/gof_types.png b/assets/img/2022-02-22-Gof/gof_types.png new file mode 100644 index 000000000..f2333e739 Binary files /dev/null and b/assets/img/2022-02-22-Gof/gof_types.png differ diff --git a/assets/img/2022-02-22-UML/image-20220222012714142.png b/assets/img/2022-02-22-UML/image-20220222012714142.png new file mode 100644 index 000000000..9efbbc527 Binary files /dev/null and b/assets/img/2022-02-22-UML/image-20220222012714142.png differ diff --git "a/assets/img/2022-05-17-\355\225\250\354\210\230\355\230\225\355\224\204\353\241\234\352\267\270\353\236\230\353\260\215/image-16527766713942.png" "b/assets/img/2022-05-17-\355\225\250\354\210\230\355\230\225\355\224\204\353\241\234\352\267\270\353\236\230\353\260\215/image-16527766713942.png" new file mode 100644 index 000000000..6deecd091 Binary files /dev/null and "b/assets/img/2022-05-17-\355\225\250\354\210\230\355\230\225\355\224\204\353\241\234\352\267\270\353\236\230\353\260\215/image-16527766713942.png" differ diff --git "a/assets/img/2022-05-17-\355\225\250\354\210\230\355\230\225\355\224\204\353\241\234\352\267\270\353\236\230\353\260\215/image.png" "b/assets/img/2022-05-17-\355\225\250\354\210\230\355\230\225\355\224\204\353\241\234\352\267\270\353\236\230\353\260\215/image.png" new file mode 100644 index 000000000..06fce66c2 Binary files /dev/null and "b/assets/img/2022-05-17-\355\225\250\354\210\230\355\230\225\355\224\204\353\241\234\352\267\270\353\236\230\353\260\215/image.png" differ diff --git a/assets/img/2022-07-17-virtual Dom/img.png b/assets/img/2022-07-17-virtual Dom/img.png new file mode 100644 index 000000000..4cf369566 Binary files /dev/null and b/assets/img/2022-07-17-virtual Dom/img.png differ diff --git a/assets/img/2022-07-28-react-key/key_error.png b/assets/img/2022-07-28-react-key/key_error.png new file mode 100644 index 000000000..8d2cd0b6f Binary files /dev/null and b/assets/img/2022-07-28-react-key/key_error.png differ diff --git "a/assets/img/2022-07-31-css \354\265\234\354\240\201\355\231\224/imageurl=/images/blog/css-performance/parser-blocking-css.png" "b/assets/img/2022-07-31-css \354\265\234\354\240\201\355\231\224/imageurl=/images/blog/css-performance/parser-blocking-css.png" new file mode 100644 index 000000000..99877a6b6 Binary files /dev/null and "b/assets/img/2022-07-31-css \354\265\234\354\240\201\355\231\224/imageurl=/images/blog/css-performance/parser-blocking-css.png" differ diff --git "a/assets/img/2022-07-31-css \354\265\234\354\240\201\355\231\224/imageurl=/images/blog/css-performance/parser-blocking-script.png" "b/assets/img/2022-07-31-css \354\265\234\354\240\201\355\231\224/imageurl=/images/blog/css-performance/parser-blocking-script.png" new file mode 100644 index 000000000..b26da23c9 Binary files /dev/null and "b/assets/img/2022-07-31-css \354\265\234\354\240\201\355\231\224/imageurl=/images/blog/css-performance/parser-blocking-script.png" differ diff --git "a/assets/img/2022-08-01-\354\275\224\355\205\214 \353\214\200\353\271\204 sql \353\254\270\353\262\225/OUTER-JOIN_\353\215\224\354\225\214\354\225\204\353\263\264\352\270\260.png" "b/assets/img/2022-08-01-\354\275\224\355\205\214 \353\214\200\353\271\204 sql \353\254\270\353\262\225/OUTER-JOIN_\353\215\224\354\225\214\354\225\204\353\263\264\352\270\260.png" new file mode 100644 index 000000000..f480b42b5 Binary files /dev/null and "b/assets/img/2022-08-01-\354\275\224\355\205\214 \353\214\200\353\271\204 sql \353\254\270\353\262\225/OUTER-JOIN_\353\215\224\354\225\214\354\225\204\353\263\264\352\270\260.png" differ diff --git a/assets/img/2022-09-12-was-webserver/web-service-architecture.png b/assets/img/2022-09-12-was-webserver/web-service-architecture.png new file mode 100644 index 000000000..d9ffe3d51 Binary files /dev/null and b/assets/img/2022-09-12-was-webserver/web-service-architecture.png differ diff --git "a/assets/img/2022-09-17-\355\224\204\353\241\240\355\212\270 \354\225\204\355\202\244\355\205\215\354\263\220 \353\263\200\355\231\224/images/teo/post/a4e07c98-fe30-4860-88cb-1a48f701209a/image.png" "b/assets/img/2022-09-17-\355\224\204\353\241\240\355\212\270 \354\225\204\355\202\244\355\205\215\354\263\220 \353\263\200\355\231\224/images/teo/post/a4e07c98-fe30-4860-88cb-1a48f701209a/image.png" new file mode 100644 index 000000000..6d2c3d455 Binary files /dev/null and "b/assets/img/2022-09-17-\355\224\204\353\241\240\355\212\270 \354\225\204\355\202\244\355\205\215\354\263\220 \353\263\200\355\231\224/images/teo/post/a4e07c98-fe30-4860-88cb-1a48f701209a/image.png" differ diff --git "a/assets/img/2023-07-29-react-\353\222\244\353\241\234\352\260\200\352\270\260-\353\247\211\352\270\260/Chrome_-_\354\235\264_\354\202\254\354\235\264\355\212\270\354\227\220\354\204\234_\353\202\230\352\260\200\354\213\234\352\262\240\354\212\265\353\213\210\352\271\214.png" "b/assets/img/2023-07-29-react-\353\222\244\353\241\234\352\260\200\352\270\260-\353\247\211\352\270\260/Chrome_-_\354\235\264_\354\202\254\354\235\264\355\212\270\354\227\220\354\204\234_\353\202\230\352\260\200\354\213\234\352\262\240\354\212\265\353\213\210\352\271\214.png" new file mode 100644 index 000000000..da84ecf74 Binary files /dev/null and "b/assets/img/2023-07-29-react-\353\222\244\353\241\234\352\260\200\352\270\260-\353\247\211\352\270\260/Chrome_-_\354\235\264_\354\202\254\354\235\264\355\212\270\354\227\220\354\204\234_\353\202\230\352\260\200\354\213\234\352\262\240\354\212\265\353\213\210\352\271\214.png" differ diff --git a/assets/img/2023-08-07-next-caching/image?url=/docs/dark/caching-overview.png b/assets/img/2023-08-07-next-caching/image?url=/docs/dark/caching-overview.png new file mode 100644 index 000000000..db907c914 Binary files /dev/null and b/assets/img/2023-08-07-next-caching/image?url=/docs/dark/caching-overview.png differ diff --git a/assets/img/KakaoTalk_20210523_193727337.png b/assets/img/KakaoTalk_20210523_193727337.png new file mode 100644 index 000000000..f508b514a Binary files /dev/null and b/assets/img/KakaoTalk_20210523_193727337.png differ diff --git "a/assets/img/Monosnap 9. API \354\230\210\354\231\270 \354\262\230\353\246\254.pdf 2022-11-03 16-08-16.png" "b/assets/img/Monosnap 9. API \354\230\210\354\231\270 \354\262\230\353\246\254.pdf 2022-11-03 16-08-16.png" new file mode 100644 index 000000000..17efba43c Binary files /dev/null and "b/assets/img/Monosnap 9. API \354\230\210\354\231\270 \354\262\230\353\246\254.pdf 2022-11-03 16-08-16.png" differ diff --git a/assets/img/[spring] AOP/imagesrc=https:/s3-us-west-2.amazonaws.com/secure.notion-static.com/baff68c9-468e-41f0-99a8-da038b8d3d40/_2020-08-06__8.32.12.png b/assets/img/[spring] AOP/imagesrc=https:/s3-us-west-2.amazonaws.com/secure.notion-static.com/baff68c9-468e-41f0-99a8-da038b8d3d40/_2020-08-06__8.32.12.png new file mode 100755 index 000000000..c9fca64bf Binary files /dev/null and b/assets/img/[spring] AOP/imagesrc=https:/s3-us-west-2.amazonaws.com/secure.notion-static.com/baff68c9-468e-41f0-99a8-da038b8d3d40/_2020-08-06__8.32.12.png differ diff --git a/assets/img/[spring] AOP/scode=mtistory2&fname=https:/t1.daumcdn.png b/assets/img/[spring] AOP/scode=mtistory2&fname=https:/t1.daumcdn.png new file mode 100755 index 000000000..780ebe911 Binary files /dev/null and b/assets/img/[spring] AOP/scode=mtistory2&fname=https:/t1.daumcdn.png differ diff --git "a/assets/img/[spring] DI\353\212\224 IOC\353\245\274 \355\225\204\354\232\224\353\241\234\355\225\230\354\247\200 \354\225\212\353\212\224\353\213\244(\354\240\225\353\246\254)/DIP-resolved.png" "b/assets/img/[spring] DI\353\212\224 IOC\353\245\274 \355\225\204\354\232\224\353\241\234\355\225\230\354\247\200 \354\225\212\353\212\224\353\213\244(\354\240\225\353\246\254)/DIP-resolved.png" new file mode 100755 index 000000000..382876639 Binary files /dev/null and "b/assets/img/[spring] DI\353\212\224 IOC\353\245\274 \355\225\204\354\232\224\353\241\234\355\225\230\354\247\200 \354\225\212\353\212\224\353\213\244(\354\240\225\353\246\254)/DIP-resolved.png" differ diff --git "a/assets/img/[spring] DI\353\212\224 IOC\353\245\274 \355\225\204\354\232\224\353\241\234\355\225\230\354\247\200 \354\225\212\353\212\224\353\213\244(\354\240\225\353\246\254)/DIP-violation.png" "b/assets/img/[spring] DI\353\212\224 IOC\353\245\274 \355\225\204\354\232\224\353\241\234\355\225\230\354\247\200 \354\225\212\353\212\224\353\213\244(\354\240\225\353\246\254)/DIP-violation.png" new file mode 100755 index 000000000..18a2a02b7 Binary files /dev/null and "b/assets/img/[spring] DI\353\212\224 IOC\353\245\274 \355\225\204\354\232\224\353\241\234\355\225\230\354\247\200 \354\225\212\353\212\224\353\213\244(\354\240\225\353\246\254)/DIP-violation.png" differ diff --git "a/assets/img/[spring] \354\212\244\355\224\204\353\247\201 MVC \352\265\254\354\241\260/imagesrc=https:/s3-us-west-2.amazonaws.com/secure.notion-static.com/27b5c274-b3f1-4ae8-90c4-accc4439f1bd/Untitled.png" "b/assets/img/[spring] \354\212\244\355\224\204\353\247\201 MVC \352\265\254\354\241\260/imagesrc=https:/s3-us-west-2.amazonaws.com/secure.notion-static.com/27b5c274-b3f1-4ae8-90c4-accc4439f1bd/Untitled.png" new file mode 100755 index 000000000..5609550e1 Binary files /dev/null and "b/assets/img/[spring] \354\212\244\355\224\204\353\247\201 MVC \352\265\254\354\241\260/imagesrc=https:/s3-us-west-2.amazonaws.com/secure.notion-static.com/27b5c274-b3f1-4ae8-90c4-accc4439f1bd/Untitled.png" differ diff --git a/assets/img/favicons/android-icon-144x144.png b/assets/img/favicons/android-icon-144x144.png new file mode 100644 index 000000000..782cf3a94 Binary files /dev/null and b/assets/img/favicons/android-icon-144x144.png differ diff --git a/assets/img/favicons/android-icon-192x192.png b/assets/img/favicons/android-icon-192x192.png new file mode 100644 index 000000000..db2d8e67a Binary files /dev/null and b/assets/img/favicons/android-icon-192x192.png differ diff --git a/assets/img/favicons/android-icon-36x36.png b/assets/img/favicons/android-icon-36x36.png new file mode 100644 index 000000000..0b560f448 Binary files /dev/null and b/assets/img/favicons/android-icon-36x36.png differ diff --git a/assets/img/favicons/android-icon-48x48.png b/assets/img/favicons/android-icon-48x48.png new file mode 100644 index 000000000..7b01aa734 Binary files /dev/null and b/assets/img/favicons/android-icon-48x48.png differ diff --git a/assets/img/favicons/android-icon-72x72.png b/assets/img/favicons/android-icon-72x72.png new file mode 100644 index 000000000..a841bb806 Binary files /dev/null and b/assets/img/favicons/android-icon-72x72.png differ diff --git a/assets/img/favicons/android-icon-96x96.png b/assets/img/favicons/android-icon-96x96.png new file mode 100644 index 000000000..a9004f855 Binary files /dev/null and b/assets/img/favicons/android-icon-96x96.png differ diff --git a/assets/img/favicons/apple-icon-114x114.png b/assets/img/favicons/apple-icon-114x114.png new file mode 100644 index 000000000..59ea4a178 Binary files /dev/null and b/assets/img/favicons/apple-icon-114x114.png differ diff --git a/assets/img/favicons/apple-icon-120x120.png b/assets/img/favicons/apple-icon-120x120.png new file mode 100644 index 000000000..b44e035d9 Binary files /dev/null and b/assets/img/favicons/apple-icon-120x120.png differ diff --git a/assets/img/favicons/apple-icon-144x144.png b/assets/img/favicons/apple-icon-144x144.png new file mode 100644 index 000000000..782cf3a94 Binary files /dev/null and b/assets/img/favicons/apple-icon-144x144.png differ diff --git a/assets/img/favicons/apple-icon-152x152.png b/assets/img/favicons/apple-icon-152x152.png new file mode 100644 index 000000000..4bdb9384a Binary files /dev/null and b/assets/img/favicons/apple-icon-152x152.png differ diff --git a/assets/img/favicons/apple-icon-180x180.png b/assets/img/favicons/apple-icon-180x180.png new file mode 100644 index 000000000..f1f5d3579 Binary files /dev/null and b/assets/img/favicons/apple-icon-180x180.png differ diff --git a/assets/img/favicons/apple-icon-57x57.png b/assets/img/favicons/apple-icon-57x57.png new file mode 100644 index 000000000..0874717ac Binary files /dev/null and b/assets/img/favicons/apple-icon-57x57.png differ diff --git a/assets/img/favicons/apple-icon-60x60.png b/assets/img/favicons/apple-icon-60x60.png new file mode 100644 index 000000000..c841d6ed1 Binary files /dev/null and b/assets/img/favicons/apple-icon-60x60.png differ diff --git a/assets/img/favicons/apple-icon-72x72.png b/assets/img/favicons/apple-icon-72x72.png new file mode 100644 index 000000000..a841bb806 Binary files /dev/null and b/assets/img/favicons/apple-icon-72x72.png differ diff --git a/assets/img/favicons/apple-icon-76x76.png b/assets/img/favicons/apple-icon-76x76.png new file mode 100644 index 000000000..3dc2e8bbc Binary files /dev/null and b/assets/img/favicons/apple-icon-76x76.png differ diff --git a/assets/img/favicons/apple-icon-precomposed.png b/assets/img/favicons/apple-icon-precomposed.png new file mode 100644 index 000000000..8eb50f63a Binary files /dev/null and b/assets/img/favicons/apple-icon-precomposed.png differ diff --git a/assets/img/favicons/apple-icon.png b/assets/img/favicons/apple-icon.png new file mode 100644 index 000000000..8eb50f63a Binary files /dev/null and b/assets/img/favicons/apple-icon.png differ diff --git a/assets/img/favicons/browserconfig.xml b/assets/img/favicons/browserconfig.xml new file mode 100644 index 000000000..02c84b541 --- /dev/null +++ b/assets/img/favicons/browserconfig.xml @@ -0,0 +1 @@ + #ffffff diff --git a/assets/img/favicons/favicon-16x16.png b/assets/img/favicons/favicon-16x16.png new file mode 100644 index 000000000..5033c246c Binary files /dev/null and b/assets/img/favicons/favicon-16x16.png differ diff --git a/assets/img/favicons/favicon-32x32.png b/assets/img/favicons/favicon-32x32.png new file mode 100644 index 000000000..7f903ae12 Binary files /dev/null and b/assets/img/favicons/favicon-32x32.png differ diff --git a/assets/img/favicons/favicon-96x96.png b/assets/img/favicons/favicon-96x96.png new file mode 100644 index 000000000..a9004f855 Binary files /dev/null and b/assets/img/favicons/favicon-96x96.png differ diff --git a/assets/img/favicons/favicon.ico b/assets/img/favicons/favicon.ico new file mode 100644 index 000000000..b02d8bfd4 Binary files /dev/null and b/assets/img/favicons/favicon.ico differ diff --git a/assets/img/favicons/manifest.json b/assets/img/favicons/manifest.json new file mode 100644 index 000000000..d7941dd23 --- /dev/null +++ b/assets/img/favicons/manifest.json @@ -0,0 +1 @@ +{ "name": "디피의 개발일지", "short_name": "디피의 개발일지", "description": "학습 및 프로젝트 기록 블로그 React, Spring", "icons": [ { "src": "/assets/img/favicons/android-icon-36x36.png", "sizes": "36x36", "type": "image/png", "density": "0.75" }, { "src": "/assets/img/favicons/android-icon-48x48.png", "sizes": "48x48", "type": "image/png", "density": "1.0" }, { "src": "/assets/img/favicons/android-icon-72x72.png", "sizes": "72x72", "type": "image/png", "density": "1.5" }, { "src": "/assets/img/favicons/android-icon-96x96.png", "sizes": "96x96", "type": "image/png", "density": "2.0" }, { "src": "/assets/img/favicons/android-icon-144x144.png", "sizes": "144x144", "type": "image/png", "density": "3.0" }, { "src": "/assets/img/favicons/android-icon-192x192.png", "sizes": "192x192", "type": "image/png", "density": "4.0" }], "start_url": "/index.html", "display": "fullscreen", "theme_color": "#2a1e6b", "background_color": "white" } diff --git a/assets/img/favicons/ms-icon-144x144.png b/assets/img/favicons/ms-icon-144x144.png new file mode 100644 index 000000000..782cf3a94 Binary files /dev/null and b/assets/img/favicons/ms-icon-144x144.png differ diff --git a/assets/img/favicons/ms-icon-150x150.png b/assets/img/favicons/ms-icon-150x150.png new file mode 100644 index 000000000..2b56368f4 Binary files /dev/null and b/assets/img/favicons/ms-icon-150x150.png differ diff --git a/assets/img/favicons/ms-icon-310x310.png b/assets/img/favicons/ms-icon-310x310.png new file mode 100644 index 000000000..f95a77a25 Binary files /dev/null and b/assets/img/favicons/ms-icon-310x310.png differ diff --git a/assets/img/favicons/ms-icon-70x70.png b/assets/img/favicons/ms-icon-70x70.png new file mode 100644 index 000000000..685290b73 Binary files /dev/null and b/assets/img/favicons/ms-icon-70x70.png differ diff --git a/assets/img/lottie costumise.PNG b/assets/img/lottie costumise.PNG new file mode 100644 index 000000000..65f9744e3 Binary files /dev/null and b/assets/img/lottie costumise.PNG differ diff --git a/assets/img/next.js app router props drilling/image-20230729182254895.png b/assets/img/next.js app router props drilling/image-20230729182254895.png new file mode 100644 index 000000000..3591b8c7b Binary files /dev/null and b/assets/img/next.js app router props drilling/image-20230729182254895.png differ diff --git a/assets/img/next.js app router props drilling/image-20230729182303529.png b/assets/img/next.js app router props drilling/image-20230729182303529.png new file mode 100644 index 000000000..376e66ffd Binary files /dev/null and b/assets/img/next.js app router props drilling/image-20230729182303529.png differ diff --git a/assets/img/profile.jpg b/assets/img/profile.jpg new file mode 100644 index 000000000..d83d74172 Binary files /dev/null and b/assets/img/profile.jpg differ diff --git a/assets/img/sequential-parallel-data-fetching.png b/assets/img/sequential-parallel-data-fetching.png new file mode 100644 index 000000000..c6149873f Binary files /dev/null and b/assets/img/sequential-parallel-data-fetching.png differ diff --git a/assets/img/yourlist/Screenshot_2021.08.28_10.27.54.310.png b/assets/img/yourlist/Screenshot_2021.08.28_10.27.54.310.png new file mode 100644 index 000000000..97409f4a1 Binary files /dev/null and b/assets/img/yourlist/Screenshot_2021.08.28_10.27.54.310.png differ diff --git a/assets/img/yourlist/Screenshot_2021.08.28_10.28.13.193.png b/assets/img/yourlist/Screenshot_2021.08.28_10.28.13.193.png new file mode 100644 index 000000000..ae2fcd5ba Binary files /dev/null and b/assets/img/yourlist/Screenshot_2021.08.28_10.28.13.193.png differ diff --git a/assets/img/yourlist/Screenshot_2021.08.28_10.28.24.311.png b/assets/img/yourlist/Screenshot_2021.08.28_10.28.24.311.png new file mode 100644 index 000000000..d6a00700f Binary files /dev/null and b/assets/img/yourlist/Screenshot_2021.08.28_10.28.24.311.png differ diff --git a/assets/img/yourlist/Screenshot_2021.08.28_10.28.30.793.png b/assets/img/yourlist/Screenshot_2021.08.28_10.28.30.793.png new file mode 100644 index 000000000..92bac04af Binary files /dev/null and b/assets/img/yourlist/Screenshot_2021.08.28_10.28.30.793.png differ diff --git a/assets/img/yourlist/Screenshot_2021.08.28_10.28.41.459.png b/assets/img/yourlist/Screenshot_2021.08.28_10.28.41.459.png new file mode 100644 index 000000000..f3bfb690f Binary files /dev/null and b/assets/img/yourlist/Screenshot_2021.08.28_10.28.41.459.png differ diff --git a/assets/img/yourlist/Screenshot_2021.08.28_10.28.48.129.png b/assets/img/yourlist/Screenshot_2021.08.28_10.28.48.129.png new file mode 100644 index 000000000..72b67f4f3 Binary files /dev/null and b/assets/img/yourlist/Screenshot_2021.08.28_10.28.48.129.png differ diff --git a/assets/img/yourlist/Screenshot_2021.08.28_10.28.57.543.png b/assets/img/yourlist/Screenshot_2021.08.28_10.28.57.543.png new file mode 100644 index 000000000..6d2a8bcc0 Binary files /dev/null and b/assets/img/yourlist/Screenshot_2021.08.28_10.28.57.543.png differ diff --git a/assets/index.html b/assets/index.html new file mode 100644 index 000000000..5038baf35 --- /dev/null +++ b/assets/index.html @@ -0,0 +1,11 @@ + + + + Redirecting… + + + + +

Redirecting…

+ Click here if you are not redirected. + diff --git a/assets/js/data/search.json b/assets/js/data/search.json new file mode 100644 index 000000000..d7fd1b854 --- /dev/null +++ b/assets/js/data/search.json @@ -0,0 +1 @@ +[ { "title": "JS bundler, package manager, compiler", "url": "/posts/JS-bundler,-package-manager,-compiler/", "categories": "study,, js", "tags": "#study", "date": "2024-08-31 19:45:24 +0900", "snippet": "프론트엔드 관련 아티클을 읽다보면 다양한 bundler, package manger, compiler 도구에 대한 얘기가 나온다. 그때마다 익숙지않은 도구의 이름(pnpm, swc, vite 등)이 나오는데 뭐가 뭔지 잘 이해가 안간다. 따라서 각각의 역할이 무엇인지, 어떤 종류가 있고 어떤 장단이 있는지 간단히 정리해보려고 한다.Bundler여러개의 파일을 하나의 파일로 묶어주는 도구이다.초기에 브라우저는 모듈 시스템을 지원하지 않았다. &lt;script&gt; 간 모듈을 내보낼 수 없었고, 전역 객체를 통해..." }, { "title": "How I Use "AI"", "url": "/posts/How-I-Use-AI/", "categories": "study,, article", "tags": "#study", "date": "2024-08-31 19:45:24 +0900", "snippet": "원본작성자가 언제 어떻게 LLM 을 활용하는지 공유하는 포스트 애플리케이션을 만드는 초기 단계에서 전체적인 베이스를 만드는데 도움을 받았음 새로운 기술의 튜터로써 코드 단순화를 위해 단조로운 작업을 빠르게 처리하기 위해 불규칙적인 작업이더라도 LLM이 패턴을 파악하여 결과물을 내게할 수 있음 자동화작업에 사용가능 " }, { "title": "Esbuild to reduce build times by 90%", "url": "/posts/Esbuild-to-reduce-build-times-by-90/", "categories": "study,, article", "tags": "#study", "date": "2024-08-31 19:45:24 +0900", "snippet": "원본기존에 빌드가 오래 걸렸던 원인들 웹팩, 롤업 작업 시간이 대부분이었음 작은 의존성들이 하나씩 빌드됨. 이것들은 동시에 진행될 여지가 있음 몇몇 의존성은 필요한 것보다 오래 빌드됨. 변경되지 않은 파일에 대해서 typeshare 를 재실행하지 않기위해 find를 실행했으나, 그냥 typeshare를 실행하는 것보다 find 실행시간이 더 길었음해커톤을 통해 몇몇 시도한 해결책 esbuild를 webpack, rollup의 로더로 사용하여 성능 개선을 이루어냄. rollup 에서는 80%의 성능개선을..." }, { "title": "실용주의프로그래머 - 전체 후기", "url": "/posts/%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8-%EC%A0%84%EC%B2%B4-%ED%9B%84%EA%B8%B0/", "categories": "study,, 실용주의프로그래머", "tags": "#study, #review", "date": "2024-08-11 21:07:38 +0900", "snippet": "이 책은 프로젝트 과정 전반에 거쳐 소프트웨어 전문가답게 진행하는 방법에 대해 서술되어있다. 전반적으로 이해가 잘되도록 쓰여져 있고, 현인의 어록과도 같은 책이라 참고할만한 부분도 많이 있다.하지만 조직문화나 권한의 한계, 물리적인 시간의 한계로 모든 팁을 다 따르기는 어렵다. 또한 이러한 내용은 완전히 습관이 되어야하는데, 긴 책 내용을 전부 기억하기는 현실적으로 어렵다. 따라서 현재 내 상황에서 실천 가능한 팁들을 간단히 정리하였다.이 문서는 1차, 2차, … 로 비정기적으로 업데이트 해나갈 것이다. 다음 업데이트는 이전 ..." }, { "title": "실용주의프로그래머 9장 실용주의 프로젝트", "url": "/posts/%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8-9%EC%9E%A5-%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8/", "categories": "study,, 실용주의프로그래머", "tags": "#study", "date": "2024-08-11 19:33:56 +0900", "snippet": " Topic 49 : 프로젝트 참여인원이 복수가 되었을때 실용적으로 각 부분을 위임하는 법 Topic 50 : 팀에게 잘 맞는 방법론을 사용하고 있는가 Topic 51 : 안정적인 소프트웨어를 지속적으로 생산해내는 방법 Topic 52 : 사용자를 기쁘게하는 방법 Topic 53 : 자신의 작업에 자부심을 가져라Topic 49 : 실용주의 팀실용주의 팀은 작아야한다. 구성원은 대략 10~12명이고, 구성의 변경이 적어야한다. 서로를 신뢰하고 의존할 수 있어야한다. 작고 안정적인 팀을 유지하라원하는 바를 실현할 수 있..." }, { "title": "실용주의프로그래머 8장 프로젝트 전에", "url": "/posts/%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8-8%EC%9E%A5-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%A0%84%EC%97%90/", "categories": "study,, 실용주의프로그래머", "tags": "#study", "date": "2024-08-07 17:32:06 +0900", "snippet": "Topic 45 : 요구사항의 구렁텅이 자신이 뭘 원하는지 정확히 아는 사람은 아무도 없다.요구사항 미신무엇을 다루든 정확한 명세란 것은 거의 불가능하다. 이때 프로그래머는 사람들이 자신이 무엇을 원하는지 깨닫도록 도와줄수 있다. 프로그래머는 사람들이 자신이 원하는 바를 꺠닫도록 돕는다.상담 치료로서의 프로그래밍신입 개발자의 실수는 요구사항을 받고 바로 해결책을 구현하는 것이다.어떤 요구사항을 받았을때 여러 경우를 생각하고 되물어 확인하자 (사소하여 짜증날거 같은 질문이라해도) 요구사항: 5만 원 이상인 모든 주문은 배송..." }, { "title": "잘못된 DRY", "url": "/posts/%EC%9E%98%EB%AA%BB%EB%90%9C-DRY/", "categories": "study", "tags": "#study", "date": "2024-08-06 11:17:27 +0900", "snippet": "https://velog.io/@eunbinn/dry-the-common-source-of-bad-abstractions DRY 원칙을 너무 지키다보니 잘못된 추상화를 하는 경우가 있다 바로 서로 성격이 다른 코드이지만, 비슷한 패턴을 가진다는 이유로 같은 코드로 묶는 경우이다. ex) 네비게이션에 Article, Menu, Buy 버튼이 있을때, Article, Menu와 Buy 버튼은 서로 성격이 다르지만 같은 팩터리 메서드로 묶는다면, 추후 Buy 버튼만 변경될 우려가 있다. 따라서 서로 다른 성격의 코드는 다르게..." }, { "title": "실용주의프로그래머 7장 코딩하는 동안", "url": "/posts/%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8-7%EC%9E%A5-%EC%BD%94%EB%94%A9%ED%95%98%EB%8A%94-%EB%8F%99%EC%95%88/", "categories": "study,, 실용주의프로그래머", "tags": "#study", "date": "2024-08-05 18:06:46 +0900", "snippet": "코딩은 설계내용을 단순히 코드로 바꾸는 과정이 아니다. 이번 장에서는 프로그램을 정확하게 동작하게 하려면 어떻게 해야하는지 다룬다. 운전할 때와 마찬가지로 코딩도 반복적인 일이지만 정신을 늘 기민하게 유지하여야한다. topic 37 : 본능과 무의식적인 생각을 더 잘 활용할 수 있다 topic 38 : 우연에 맡기는 프로그래밍을 피하는 방법 topic 39 : 알고리즘의 속도 topic 40 : 리팩터링 topic 41 : 테스트로 코딩하기 topic 42 : 속성 기반 테스트를 통해 고정관념에서 벗어난 테스트하기 ..." }, { "title": "실용주의프로그래머 6장 동시성", "url": "/posts/%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8-6%EC%9E%A5-%EB%8F%99%EC%8B%9C%EC%84%B1/", "categories": "study,, 실용주의프로그래머", "tags": "#study", "date": "2024-08-04 18:04:36 +0900", "snippet": " 동시성 : 둘 이상의 코드 조작이 실행될 때 동시에 실행 중인 것처럼 행동하는 것. 소프트웨어 동작 코드의 다른 부분으로 실행을 전환할 수 있는 환경에서 코드를 구동해야함. ex) 스레드, 프로세스 병렬성 : 실제로 동시에 실행되는 것. 하드웨어 동작 두가지 일을 동시에 할 수 있는 하드웨어가 필요함. ex) CPU내 코어, 네트워크로 연결된 컴퓨터 등 모든 일에 동시성이 있다.시스템이 커지면 동시성은 필수다. 어떤 일을 할 때, 한번에 하나씩 순차적으로 수행 하면 시..." }, { "title": "실용주의프로그래머 5장 구부러지거나 부러지거나", "url": "/posts/%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8-5%EC%9E%A5-%EA%B5%AC%EB%B6%80%EB%9F%AC%EC%A7%80%EA%B1%B0%EB%82%98-%EB%B6%80%EB%9F%AC%EC%A7%80%EA%B1%B0%EB%82%98/", "categories": "study,, 실용주의프로그래머", "tags": "#study", "date": "2024-07-28 20:07:50 +0900", "snippet": "이번 장에서는 되돌릴 수 있는 의사결정을 내리는 구체적인 방법을 설명한다. topic 28 : 결합도 줄이기 topic 29 : 실세계를 갖고 저글링하기. 이벤트에 반응하는 네 가지 서로 다른 전략 topic 30 : 변환 프로그래밍. 함수 파이프라인 topic 31 : 상속세. 유연하고 바꾸기 쉬운 코드를 만들 수 있는 대안 topic 32 : 설정. 세부사항을 코드 밖으로 옮기는 방법Topic 28 : 결합도 줄이기코드에서 나타나는 결합의 증상 관계없는 모듈이나 라이브러리 간의 희한한 의존 관계 한 모듈의 간단..." }, { "title": "실용주의프로그래머 4장 실용주의 편집증", "url": "/posts/%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8-4%EC%9E%A5-%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98-%ED%8E%B8%EC%A7%91%EC%A6%9D/", "categories": "study,, 실용주의프로그래머", "tags": "#study", "date": "2024-07-27 21:04:15 +0900", "snippet": " 여러분은 완벽한 소프트웨어를 만들 수 없다실용주의 프로그래머는 자신의 실수에 대비한 방어책을 마련한다. Topic 23 : 코드의 공급자와 사용자는 권리와 책임에 대해 동의해야한다 Topic 24 : 버그상황에서 헤어나오는 도중에 어떤 손상도 입히지 않도록 보장해야한다. Topic 25 : 확인을 쉽게하는 방법. 가정(assumption)을 적극적으로 검증하는 코드 작성 Topic 26 : 여러 시스템 리소스를 다룰때 실수 하지 않는 법 Topic 27 : 언제나 작은 단계를 고수해야한다.Topic 23 : 계약에..." }, { "title": "실용주의프로그래머 3장 기본도구", "url": "/posts/%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8-3%EC%9E%A5-%EA%B8%B0%EB%B3%B8%EB%8F%84%EA%B5%AC/", "categories": "study,, 실용주의프로그래머", "tags": "#study", "date": "2024-07-21 20:49:29 +0900", "snippet": "Topic 16 - 일반 텍스트의 힘일반 텍스트란?인쇄 가능한 문자로 이루어지고, 정보를 전달하기에 적합한 형식을 갖추어야한다. 또한 사람이 이해할 수 있어야한다. 일반 텍스트 우유커피 일반 텍스트가 아님 hlj;uijn bfjxrrctvh jkni’pio6p7gu;vh bjxrdi5rgvhjField19=467abe 형식은 HTML, JSON, YAML 등 다양하다.일반 텍스트는 데이터를 그 자체로만 이해할 수 있다. 이진포맷은 데이터를 읽는데 필요한 문맥과 데이터가 분리되어있어, 데이터를 ..." }, { "title": "실용주의프로그래머 2장 실용주의 접근법", "url": "/posts/%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8-2%EC%9E%A5-%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98-%EC%A0%91%EA%B7%BC%EB%B2%95/", "categories": "study,, 실용주의프로그래머", "tags": "#study", "date": "2024-07-15 21:15:49 +0900", "snippet": "Topic 8 - 좋은 설계의 핵심 좋은 설계는 나쁜 설계보다 바꾸기 쉽다.ETC(Easy To Change)는 규칙이 아니라 가치가치란 결정을 내릴때 도움을 주는 것이다. ETC는 가치로서 내제화되어야하며, 의식적으로 방금 한 일이 ETC 한지 항상 물어봐야한다.일반적으로 상식선에서 추측이 가능하다. 하지만 결정하기 어려울때가 있는데, 이럴땐 다음 두가지 방법이 있다. 코드가 교체되기 쉬워야한다. (결합도를 낮추고 응집도를 높여라) 직관적으로 선택하고, 그 이유에 대해 기록해둬라. 그리고 추후 변경이 실제로 발생..." }, { "title": "실용주의프로그래머 1장 실용주의 철학", "url": "/posts/%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8-1%EC%9E%A5-%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98-%EC%B2%A0%ED%95%99/", "categories": "study,, 실용주의프로그래머", "tags": "#study", "date": "2024-07-14 16:13:54 +0900", "snippet": "Topic 1 - 당신의 인생이다변화를 피하지 말고 불만이 있는 것이 있으면 고치기 위해 노력하라 업무환경, 적성이 안맞으면 바꾸되, 너무 오래 노력하지는 말기 뒤쳐지는 기분이 들면 여가 시간에 공부하기기회는 많고 적극적으로 행동해 그 기회를 잡아라Topic 2 - 고양이가 내 소스 코드를 삼켰어요실용주의 철학의 초석 중 하나는 자신과 자신의 행동에 대해 책임을 지는 것이다. 아무리 잘해도 발생하는 문제는 있고, 이를 정직하고 솔직하게 인정하고, 처리하려고 노력해야한다.팀 내 신뢰팀이 나를 믿고 의지할 수 있어야하고, 나도..." }, { "title": "next.js 최적화과정", "url": "/posts/next.js-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%B5%9C%EC%A0%81%ED%99%94-%EA%B3%BC%EC%A0%95/", "categories": "study, next.js", "tags": "next.js", "date": "2023-10-22 16:00:00 +0900", "snippet": "발단서비스를 운영하던 도중 CPU/Memory 사용량은 낮은데 사용자 응답이 매우 느려지는 상황이 발생하였다.서비스는 쿠버네티스로 띄워져있었고, HPA metric으로 CPU와 memory가 걸려있었는데, CPU와 memory가 올라가지 않으니 scale-out도 발생하지 않아 적은 pod으로 계속 서비스 되고 있었다.수동으로 pod을 3~4배 가량 늘려 대응은 하였으나 추후에도 발생할 이슈일 것으로 보여 대응을 하기로 했다.테스트이 이슈는 next.js로 띄워져있는 페이지 중 하나에 페이지에 갑자기 트래픽이 몰려 발생한 이슈..." }, { "title": "next.js caching", "url": "/posts/next-caching/", "categories": "study, next.js", "tags": "next.js, cache", "date": "2023-08-13 22:00:00 +0900", "snippet": "next.js app router의 caching에 관해 공부한 내용입니다.Overviewnext.js 에서 제공하는 caching mechanism에는 다음과 같은 것들이 있다. Mechanism What Where Purpose Duration Request Memoization Return values of functions Server Re-use data in a React Component tree ..." }, { "title": "next.js props drilling", "url": "/posts/next-props-drilling/", "categories": "study, next.js", "tags": "next.js", "date": "2023-08-05 16:00:00 +0900", "snippet": "Data sharing (Solving props drilling)next.js 13.4 이상 App router 기준으로 작성하였습니다.props drilling 예시next.js에서 주로 발생하는 props drilling 형태는 다음과 같습니다. 먼저 필요한 데이터를 서버컴포넌트에서 fetch 합니다. API 노출 방지, 최적화 등 다양한 이유로 서버 컴포넌트(서버 사이드)에서 API를 호출합니다.// Server Component Aimport ServerComponentB from "./ServerCompo..." }, { "title": "React 새로고침/뒤로가기 막기", "url": "/posts/react-%EB%92%A4%EB%A1%9C%EA%B0%80%EA%B8%B0-%EB%A7%89%EA%B8%B0/", "categories": "study, react", "tags": "react", "date": "2023-07-29 22:00:00 +0900", "snippet": "웹페이지에서 사용자가 무언가를 입력하고 뒤로가기를 눌렀을때 다음과 같은 경고창을 띄우는 걸 본적이 있다.이러한 스펙을 개발하기위해서는 다음과 같이 두가지 상황으로 나누어야한다. 새로고침/창닫기/링크이동(외부페이지 이동 or 링크 버튼 클릭) 뒤로가기그렇다면 리액트에서는 구체적으로 어떻게 이 스펙을 구현할 수 있을지 알아보자새로고침/창닫기/링크이동새로고침/창닫기/링크이동의 경우는 간단하다. 해당 이벤트들이 발생할때 window 객체에서 발생시키는 beforeunload 이벤트를 취소시키면 된다.리액트에서 구현하려면 다음과 같..." }, { "title": "IOS 16.5에서 video가 크기 변경이 적용되지 않는 이슈", "url": "/posts/ios-16-5-video/", "categories": "study, js", "tags": "issues", "date": "2023-06-18 18:00:00 +0900", "snippet": "Spec Swiper (ver 4.5.1) 안에 &lt;video&gt; 태그가 slide로 포함되어있음. 현재 가장 가운데 있는 slide 안의 video는 크기를 키우고, slide가 옆으로 이동하면 다시 원래 사이즈로 돌아가도록 함 Issue IOS 16.5 safari에서는 다음과 같이 가운데 slide 안의 video 크기가 정상적으로 확대되지 않음원인 가운데 video 확대를 위한 코드는 다음과 같이 slide-active 상태일때 스타일을 적용하여 크기를 변경..." }, { "title": "es6에서 도입된 문법", "url": "/posts/es6/", "categories": "study, javascript", "tags": "javascript", "date": "2023-06-12 23:00:00 +0900", "snippet": "let, const 키워드를 통한 변수선언기존 자바스크립트에서는 var 키워드로만 변수선언이 가능했다. 하지만, let const 키워드를 추가하여 보다 예측가능한 코드를 작성할 수 있게 됐다.// es6 이전.// var는 재선언 가능var a = "This is string";var a = 1234;// ES6 이후// let은 재선언 불가능let b = "This is string";b = 1234;// const는 재할당 불가능const c = "This is string&q..." }, { "title": "영상이 자동재생되지 않는 문제", "url": "/posts/video-notplaying/", "categories": "study, react", "tags": "issues", "date": "2023-06-03 18:00:00 +0900", "snippet": "다음과 같은 요구사항을 가지는 웹페이지를 개발하고 있었다. swiper로 여러 슬라이드를 띄움 슬라이드 내에는 영상이 존재하고, 각 영상은 자동으로 재생되어야함.단순히 swiper를 연동하고 video 태그에 autoplay 속성을 주면 될거라 생각하여 다음과 같이 코드를 작성했었다.&lt;li className="swiper-slide"&gt; &lt;video poster="poster 주소" autoPlay m..." }, { "title": "DB index", "url": "/posts/index/", "categories": "study, database", "tags": "db", "date": "2023-06-03 16:00:00 +0900", "snippet": "##인덱스란?DBMS에서 데이터베이스 테이블의 모든 데이터를 검색하여 데이터를 찾기에는 시간이 오래 걸리기에, 데이터의 칼럼의 값과 그 데이터가 저장된 레코드의 주소를 키와 값의 쌍으로 만든 것.DBMS의 인덱스는 항상 정렬된 상태를 유지하기 때문에, 원하는 값을 탐색하는데는 빠르지만, 추가/삭제/수정에는 쿼리문 실행 속도가 느려진다.(작업 후 다시 정렬해야하기에)결과적으로 DBMS의 인덱스는 데이터 저장 성능을 희생하고, 읽기 성능을 높이는 기능이다. 따라서 모든 컬럼을 대상으로 인덱스를 생성하면 데이터 저장 성능이 떨어지고..." }, { "title": "딥링크", "url": "/posts/deep-link/", "categories": "study, web", "tags": "web, issue", "date": "2023-05-19 22:00:00 +0900", "snippet": "딥링크는 웹사이트에서 사용자 기기에 설치된 앱을 URI로 실행시킬 수 있도록하는 기술이다.딥링크의 세가지 유형이러한 딥링크에는 세가지 유형이 있다. URI Scheme (초기 형태) 보완 Universal Link (IOS) App Link (Android) URI Scheme앱 개발자가 앱 내 특정 페이지마다 고유한 주소를 설정하고, 그 주소를 웹에서 실행시키면 설정한 특정 페이지가 열리는 형태이다. 이때 주소는 다음과 같은 형태이다.myapp://post?version=1여기서 myap..." }, { "title": "bfcache", "url": "/posts/bfcache/", "categories": "study, web", "tags": "browser", "date": "2023-05-12 22:00:00 +0900", "snippet": "BFCache 사용자가 브라우저 내에서 뒤로가기/앞으로가기를 할 때 이전 페이지를 자바스크립트 heap 영역까지 전체 캐싱하여 메모리에 저장하는 것 HTTP 캐시는 리소스만을 캐시하고 JS 작업까지 캐시하지 않기에 BFCache보다 빠르지 않다.BFCache 작동을 판단할 수 있는 APIpageshow, pagehidepageshow 페이지가 처음 로드 될 때, bfcache에서 페이지가 복원될 때마다 load 이벤트 직후 트리거 된다. event.persisted === true이면 BFCache에서 복구된 것이다.p..." }, { "title": "beforeunload vs pagehide", "url": "/posts/pagehide/", "categories": "study, javascript", "tags": "javascript", "date": "2023-05-11 23:00:00 +0900", "snippet": "ios 모바일에서 새로고침시 현재 스크롤 위치를 고정하기 위해 다음과 같은 코드를 사용하였다.if (sessionStorage.scrollPosition) { window.scrollTo(0, sessionStorage.scrollPosition);}window.onbeforeunload = function () { sessionStorage.scrollPosition = document.body.scrollTop || document.documentElement.scrollTop;};하지만 제대로 동작하지 않았는데, 찾아..." }, { "title": "useState 원리", "url": "/posts/react-state/", "categories": "study, react", "tags": "react", "date": "2023-05-06 18:00:00 +0900", "snippet": "State 변경 시 어떤 일이 벌어질까?리액트의 함수형 컴포넌트는 최초에 한번 실행이 되면서 초기값으로 설정해놓은 상태를 기억한다.const [state, setState] = useState(0);이후 setState가 호출되어 상태가 변경된다면 다시 함수형 컴포넌트가 실행되고 virtual DOM을 리턴한다. 그리고 이전에 리턴했던 virtual DOM과 비교해서 state 값이 달라졌다면 달라진 부분에 해당하는 DOM만 업데이트한다.useState와 Closure클래스형 컴포넌트는 render() 메서드를 통해 상태 변경..." }, { "title": "typescript 조건부타입(extends)", "url": "/posts/typescript-extends/", "categories": "study, typescript", "tags": "typescript", "date": "2023-04-24 22:30:00 +0900", "snippet": "타입 스크립트 2.8부터 다음과 같은 조건부 타입을 사용할 수 있다.T extends U ? X : Y뜻은 T가 U의 서브타입이거나 같은 타입이면 X, 아니면 Y 타입을 할당한다는 것이다.T가 유니온 타입일 경우다음과 같이 T가 유니온 타입일 경우가 있다.type T = "A" | "B" | "C"이러한 경우엔 분배법칙이 성립된다.{"A" extends U ? X : Y} | {"B" extends U ? X : Y} | {"C&..." }, { "title": "Javscript 메모리 누수 방지 및 성능 개선", "url": "/posts/js-memory-leak/", "categories": "study, javascript", "tags": "javascript", "date": "2023-04-18 22:00:00 +0900", "snippet": "Javscript 메모리 관리 이해하기Garbage collector자바스크립트 엔진은 더이상 사용하지 않을 메모리를 놓아주기 위해 garbage collector를 사용한다. garbage collector(GC)는 앱에서 더이상 사용하지 않을 객체를 찾아내고 삭제한다. 따라서 GC는 앱의 object와 변수를 계속 모니터링하고 어떤 것이 여전히 referenced 되고 있는지 트랙킹한다. 그러다 Object가 더이상 쓰이지 않으면 마킹하고 삭제하여 메모리를 놓아준다.GC가 사용하는 이 기법은 mark and sweep이다..." }, { "title": "Typescript 5.0", "url": "/posts/typescript5/", "categories": "study, typescript", "tags": "typescript", "date": "2023-04-17 22:30:00 +0900", "snippet": "typescript 5.0이 나왔다고 하여 간단히 어떤 기능들이 추가되었는지 살펴보았다. 자세한 내용은 Typescript 5.0 번역에서 확인 가능하다.데코레이터데코레이터는 재사용 가능한 방식으로 클래스와 그 멤버를 사용자 정의하는 ECMAScript의 기능이다. 데코레이터는 이전부터 지원되었으나 Typescript 5.0부터는 공식 기능으로 지원한다.function loggedMethod(originalMethod: any, context: ClassMethodDecoratorContext) { const method..." }, { "title": "next.js appDir", "url": "/posts/appdir/", "categories": "study, next.js", "tags": "next.js", "date": "2023-04-13 17:00:00 +0900", "snippet": "AppDirnext.js에서 layout과 routing 경험을 개선시키고 React 최신 기술을 지원하기위해, next.js 13부터 지원하는 디렉터리 방식. 기존의 pages 디렉터리를 대체한다.23년 04월 기준으로 아직은 베타버전으로 프로덕션에서 사용하는 건 추천하지 않는다고 한다. 하지만, 좋은 기능들이 많기에 학습해봐도 괜찮을 거 같다.file conventionsapp directory에서 파일 컨벤션은 다음과 같다. page.js : 해당 path의 UI를 담당하며, public하게 접근할 수 있도록 함. ..." }, { "title": "성능 최적화를 위한 next.js case study", "url": "/posts/nextjs-case-study/", "categories": "study, next.js", "tags": "", "date": "2023-03-27 23:00:00 +0900", "snippet": "원글에서는 TMDB의 클라이언트 웹 어플리케이션을 만들고, 여러번의 실험을 거쳐 성능 최적화에 달성하는 과정을 서술하였습니다.이 글은 기본적으로 원글을 학습하면서 제가 읽기 편한 방식으로 번역/정리한 글입니다. 정확한 내용을 알고 싶으시면 원글을 보시는 걸 추천드립니다.용어 정의 FCP : First Contentful Paint. 사용자가 화면에서 콘텐츠를 볼 수 있는 페이지 로드 타임라인의 첫 번째 지점. LCP : Largest Contentful Paint. 뷰포트에서 가장 큰 콘텐츠 엘리먼트가 나타날때 까지 걸린 ..." }, { "title": "Vite", "url": "/posts/react-vite/", "categories": "study, react", "tags": "vite", "date": "2023-03-21 23:00:00 +0900", "snippet": "최근 create-react-app 대신에 vite를 공식으로 사용하자는 얘기가 나오고 있다. https://github.com/reactjs/reactjs.org/pull/5487 번역본여기서 지적한 문제점들은 다음과 같다 SSR/SSG 지원의 부족 빈 페이지 로드 -&gt; 리액트 번들 로드 -&gt; 리액트 실행 -&gt; 필요한 데이터 패칭 순으로 이어지는 워터폴 문제 발생 모든 앱 코드가 하나의 번들로 묶임 상품 페이지를 로드할때, 장바구니 코드를..." }, { "title": "Headless CMS", "url": "/posts/web-Headless-CMS/", "categories": "study, web", "tags": "web", "date": "2023-03-10 23:00:00 +0900", "snippet": "Headless CMS 란?웹사이트를 만들 때는 반드시 컨텐츠(데이터)가 필요하다. Headless CMS는 컨텐츠를 보여줄 수단인 Head와 컨텐츠를 분리한 구조로, 컨텐츠가 Head에 독립적으로 동작할 수 있도록 하는 구조이다.![1E3qz8MZ8zR7Y3NRghFOvJQ](https://miro.medium.com/v2/resize:fit:1400/format:webp/1E3qz8MZ8zR7Y3NRghFOvJQ.png)기존의 CMS는 컨텐츠와 Head가 강하게 묶여있었다. Html에 하드코딩된 문자열들을 생각하면 된다.&..." }, { "title": "module federation", "url": "/posts/web-module-federation/", "categories": "study, web", "tags": "webpack", "date": "2023-03-05 23:00:00 +0900", "snippet": "webpack module federation여러 개의 개별 빌드가 단일 어플리케이션을 형성할 수 있도록 해주는 webpack의 기능이다. 개별 빌드는 컨테이너처럼 작동하며, 빌드 간에 코드를 노출하고 소비하여 단일 통합 애플리케이션을 생성할 수 있다.Low-Level concepts로컬 모듈과 원격 모듈을 구별한다. 로컬모듈 : 현재 빌드의 일부인 일반 모듈 원격모듈 : 현재 빌드의 일부가 아니며, 원격 컨테이너에서 런타임에 로드되는 모듈원격 모듈을 로드하는 것은 비동기 작업으로 간주된다. 원격 모듈을 사용할 때 이러한 ..." }, { "title": "SWR", "url": "/posts/react-SWR/", "categories": "study, react", "tags": "", "date": "2023-03-04 18:00:00 +0900", "snippet": "SWR은 vercel에서 제작한 React Hooks로, 먼저 캐시(stale)로부터 데이터를 반환한 후, fetch 요청(revalidate)을 하고, 최종적으로 최신화된 데이터를 가져오는 전략이다. 이름은 HTTP 캐시 무효 전략인 stale-while-revalidate에서 유래되었다. 공식사이트전체적인 기능은 React Query와 비슷하며 React Query 쪽의 커뮤니티가 더 크다. 따라서 사용하기 전에 장단을 잘 비교해보고 둘 중 하나를 선택하는 것이 좋을 것 같다.예시import useSWR from '..." }, { "title": "next.js Compiler", "url": "/posts/next.js-Compiler/", "categories": "study, next.js", "tags": "next.js", "date": "2023-02-13 22:00:00 +0900", "snippet": "Next.js의 컴파일러는 바벨 대신에 Rust 기반의 SWC를 JS 번들링에 사용한다. 이는 바벨보다 17배 빠르며, Next.js 12부터 디폴트로 쓰인다. 만약 바벨을 사용하고 싶다면, 다음을 참고하면 된다.Next.js Compiler의 자세한 내용은 공식 가이드에 나와있다. 이 글에서는 주로 사용할만한 요소만 요약하여 정리하였다.Supported Features Styled Components : bebel-plugin-styled-components와 연계하여 styled-components를 위한 바벨 설..." }, { "title": "next.js - Output File Tracing", "url": "/posts/next.js-Output-File-Tracing/", "categories": "study, next.js", "tags": "next.js", "date": "2023-02-12 23:00:00 +0900", "snippet": "next.js는 빌드 시 자동으로 모든 페이지와 그 의존성을 트랙킹하여 배포시 필요한 파일을 알아낸다. 이렇게 함으로써 배포될 파일의 크기를 줄일 수 있다.이전에 도커로 배포할 땐, 모든 의존성을 가져온 후 next start를 실행해야했다. 하지만 next.js 12부터는 Output File Tracing을 통해 .next/ 디렉터리만 있으면 된다. 단, standalone 모드를 켜야한다.동작방식next build가 실행되는 동안, next.js는 @vercel/nft를 사용하여 정적으로 import, require, f..." }, { "title": "next.js - Deployment", "url": "/posts/next.js-Deployment/", "categories": "study, next.js", "tags": "next.js", "date": "2023-02-12 22:00:00 +0900", "snippet": "Next build APInext build를 실행하면, 작성한 소스코드가 최적화된 상태도 빌드된다. 빌드된 결과물 .next 아래에 위치하며 구조는 다음과 같다. .next/static/chunks/pages : 라우트되는 이름 그대로 생성된 js 파일이 위치한다. (/about –&gt; .next/static/chunks/pages/about.js) .next/static/media : next/image를 사용하여 정적으로 삽입된 이미지가 해시되어 위치한다. .next/static/css : 글로벌 CSS ..." }, { "title": "next.js - production 배포 전 체크리스트", "url": "/posts/next.js-production-%EB%B0%B0%ED%8F%AC-%EC%A0%84-%EC%B2%B4%ED%81%AC%EB%A6%AC%EC%8A%A4%ED%8A%B8/", "categories": "study, next.js", "tags": "", "date": "2023-01-31 23:00:01 +0900", "snippet": "next.js로 만든 앱을 배포하기 전 체크리스트 가능한만큼 캐싱을 적용했는가? 데이터베이스와 백엔드가 같은 region에 배포되어있는가? 가능한 최소한의 Javascript를 사용하는 것을 목표로 해라 Javascript 로딩을 가능한 연기하라 logging이 구현되어 있는가? errorHandling이 설정되어있는가? 404 페이지와 500 페이지를 만들었는가? 성능 측정을 해보아라 Lighthouse를 실행해서 테스트해보아라. 브라우저 호환성을 확인하라 성능을 높이기 위해 다음 기능을 사용하라 ..." }, { "title": "next.js - Script 컴포넌트", "url": "/posts/next.js-Script-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8/", "categories": "study, next.js", "tags": "next.js", "date": "2023-01-31 23:00:00 +0900", "snippet": "next.js 에서는 페이지 성능 개선을 위해 서드파티 script를 가져올 수 있는 next/script 컴포넌트를 제공한다.사용법importimport Script from 'next/script'Page script페이지 컴포넌트에서 사용될 수 있으며, 페이지가 브라우저에 로드되면 script를 fetch하고 실행한다.import Script from 'next/script'export default function Dashboard() { return ( &lt;&gt;..." }, { "title": "next.js - Satic HTML Export", "url": "/posts/next.js-Satic-HTML-Export/", "categories": "study, next.js", "tags": "next.js", "date": "2023-01-29 22:00:01 +0900", "snippet": "next export 명령어를 통해 next.js 어플리케이션을 static HTML으로 만들 수 있다. 이렇게 만들어진 Static HTML은 Node.js 서버없이 운영이 가능하다. 다음과 같은 기능이 필요없다면, next export를 사용하는 것이 권장된다. Image Optimization (default loader) Internationalized Routing API Routes Rewrites Redirects Headers Middleware Incremental Static Regenerati..." }, { "title": "next.js - Automatic Static 최적화", "url": "/posts/next.js-Automatic-Static-%EC%B5%9C%EC%A0%81%ED%99%94/", "categories": "study, next.js", "tags": "next.js", "date": "2023-01-29 22:00:00 +0900", "snippet": "next.js에서는 어떤 페이지가 getServerSideProps 또는 getInitialProps를 가지고 있지 않다면, static 페이지로 결정한다. 이렇게 static 페이지로 결정되면, 빌드시 그 페이지는 static 페이지로 빌드된다.static 페이지는 서버사이드에서 렌더되지 않고 바로 유저에게 전달된다. end-user와 서버 사이에 CDN이 있다면 CDN의 적용 또한 받는다.query 객체 처리getServerSideProps 또는 getInitialProps가 없는 페이지는 빌드시 prerender 되어 s..." }, { "title": "next/image 컴포넌트", "url": "/posts/next.js-next-image-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8/", "categories": "study, next.js", "tags": "next.js", "date": "2023-01-28 23:00:00 +0900", "snippet": "next.js에서는 next/image로 자체적인 이미지 컴포넌트를 제공한다. 일반적인 img 태그와는 달리 여러 성능 최적화기법이 내장되어있다. 기본적으로 다음과 같은 기능을 제공한다. 성능 향상 : 각 디바이스에 정확히 맞는 이미지를 모던 웹 포맷에 맞게 제공한다. Visual Stability : Cumulative Layout Shift를 자동으로 예방한다. Cumulative Layout Shift : 웹 화면의 레이아웃이 갑자기 바뀌는 것. 사용자가 의도치않은 동작을 실행할 수 있다. ..." }, { "title": "container 안에서 pm2로 next.js 앱 실행하기", "url": "/posts/next.js-container-%EC%95%88%EC%97%90%EC%84%9C-pm2%EB%A1%9C-next.js-%EC%95%B1-%EC%8B%A4%ED%96%89%ED%95%98%EA%B8%B0/", "categories": "study, next.js", "tags": "kubernetes", "date": "2023-01-25 23:00:00 +0900", "snippet": "next.js는 기본적으로 node.js runtime에서 작동된다. 하지만 node.js 는 싱글 스레드 기반이고, 따라서 트래픽이 많은 환경에서 multi-core를 사용하여 node.js 앱을 구동하기 위해선 다음과 같은 방법이 있다. worker thread child process cluster필자는 next.js에서 가장 사용하기 편한 방식인 cluster를 선택하였고, 편리하게 하기위해 pm2의 cluster 모드를 사용하기로 결정하였다. 환경은 kubernetes 환경으로 하나의 pod에 2~3개의 next..." }, { "title": "next.js - data fetching", "url": "/posts/next.js-data-fetching/", "categories": "study, next.js", "tags": "next.js", "date": "2023-01-23 22:00:00 +0900", "snippet": "next.js에서 사용자의 요청을 받고, 페이지를 만들어낼 때 외부 서버로 필요한 데이터를 요청해야할 때 사용하는 기법이다. data fetching은 기본적으로 페이지 컴포넌트에서 사용할 수 있고 경우에 따라 Custom App 컴포넌트에서도 사용할 수 있다.next 13부터는 app/ 구조를 사용한다면 일반 컴포넌트에서도 가능하다고 하지만, 현재(2023.01)는 next 13의 app/ 구조가 베타버전이라 pages/ 구조를 기준으로 작성하였다. Note: Next.js 13 introduces the app/ dire..." }, { "title": "php 기초 강의", "url": "/posts/php-php-%EA%B0%95%EC%9D%98/", "categories": "study, php", "tags": "", "date": "2022-12-25 16:00:00 +0900", "snippet": "PHP 기초PHP의 원리 웹브라우저에서 웹 서버로 index.php을 달라고 요청보냄 웹서버는 index.php을 처리할 수 있는 프로그램인 php에게 위임함 php는 index.php를 찾고, 해석해서 html 파일을 만듬. 웹 서버는 html을 받고 웹브라우저로 보내줌.PHP의 데이터 타입숫자와 산술연산자 int : 정수형 float : 소수형문자열과 문자열 처리 string ’’ ”” 연산자 . : 문자열 붙이기 ex) “hello “.”world” -&a..." }, { "title": "쿠버네티스 Ingress", "url": "/posts/devops-ingress/", "categories": "study, devops", "tags": "kubernetes", "date": "2022-12-15 19:00:00 +0900", "snippet": "쿠버네티스 Ingressingress는 일반적으로 외부로부터 서버 내부로 유입되는 네트워크 트래픽을 뜻한다.쿠버네티스에서는 ingress라는 리소스 오브젝트가 존재한다. 쿠버네티스의 ingress는 외부에서 쿠버네티스 클러스터 내부로 들어오는 http, https 요청을 어떻게 처리할지 정의한다. 즉, 외부에서 쿠버네티스에서 실행 중인 deployment와 service에 접근하기 위한 일종의 gateway 같은 역할을 담당한다.ingress를 사용하지 않으면 NodePort나 LoadBalancer를 사용하여 외부 요청을 받..." }, { "title": "nginx에서 환경변수 사용하는 방법", "url": "/posts/web-nginx%EC%97%90%EC%84%9C-%ED%99%98%EA%B2%BD%EB%B3%80%EC%88%98-%EC%82%AC%EC%9A%A9/", "categories": "study, web", "tags": "", "date": "2022-12-05 21:00:00 +0900", "snippet": "nginx.conf 에서 환경변수 사용nginx.conf에서 환경변수를 사용하여 동적으로 nginx 설정을 변경하는 방법을 알아보자.envsubst 사용하는 방법envsubst는 인풋으로부터 $VARIABLE 또는 ${VARIABLE}로 되어있는 값을 읽어 환경변수로 바꾸어주는 프로그램이다. envsubst을 활용해서 nginx.template 파일로 환경변수가 사용된 nginx 설정파일을 작성하고, nginx가 실행될 때 envsubst를 사용해 변환해주면 된다.step 1 : nginx.template 파일 작성worker..." }, { "title": "kubernetes 실습", "url": "/posts/devops-kubenetes-%EC%8B%A4%EC%8A%B5/", "categories": "study, devops", "tags": "kubernetes", "date": "2022-11-30 18:29:26 +0900", "snippet": "이 포스트는 subicura 님의 kubenetes 안내서을 읽으며 요약/기록한 게시글입니다.Kubenetes 실습Pod 생성하기YAML로 설정파일 작성하여 생성하기apiVersion: v1kind: Podmetadata: name: echo labels: app: echospec: containers: - name: app image: ghcr.io/subicura/echo:v1 정의 설명 예 apiVersion API 서버..." }, { "title": "kubernetes", "url": "/posts/devops-kubernetes/", "categories": "study, devops", "tags": "kubernetes", "date": "2022-11-30 18:19:26 +0900", "snippet": "이 포스트는 subicura 님의 kubenetes 게시글을 읽으며 요약/기록한 게시글입니다.Kubenetes쿠버네티스(kubenetes, k8s)는 도커 컨테이너를 쉽고 빠르게 배포/확장하고 관리를 자동화해주는 오픈소스 플랫폼이다.단순한 컨테이너 플랫폼이 아닌 마이크로 서비스, 클라우드 플랫폼을 지향하고 컨테이너로 이루어진 것들을 손쉽게 담고 관리할 수 있는 그릇 역할을 한다.쿠버네티스 특징다양한 배포방식쿠버네티스는 다음과 같은 다양한 배포방식을 지원한다. Deployment : 새로운 버전의 매플리케이션을 다양한 전략으로..." }, { "title": "nginx + spring boot+ react로 구성된 앱 dockerfile 작성", "url": "/posts/devops-docker-nginx-+-spring-+-react-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EB%B9%8C%EB%93%9C-%EB%B0%8F-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88-%EC%8B%A4%ED%96%89/", "categories": "study, devops", "tags": "", "date": "2022-11-29 18:19:26 +0900", "snippet": "nginx + spring boot+ react로 구성된 앱 dockerfile 작성docker flow도커에서 이미지를 빌드하고, 앱이 실행되는 컨테이너를 실행하는 과정은 크게 이미지 빌드, 이미지 푸시, 컨테이너 실행 단계로 나뉜다. nginx + spring boot + react로 구성된 앱을 도커 이미지로 관리하려면 먼저 이미지를 빌드할 dockerfile을 잘 작성해야한다.먼저 이번 글에서 사용될 프로젝트(빌드 컨텍스트)의 디렉터리 구조부터 알아보자프로젝트 구조.├── Dockerfile├── nginx.conf└─..." }, { "title": "SockJS", "url": "/posts/web-SockJS/", "categories": "study, web", "tags": "web", "date": "2022-11-23 21:19:26 +0900", "snippet": "SockJS주로 Spring을 사용할 때, WebSocket Emulation 을 위한 라이브러리이다.WebSocket Emulation 이란 우선 웹 소켓으로 소켓 연결을 시도하고, 실패할 경우 HTTP Streaming, Long-Polling 같은 HTTP 기반의 다른 기술로 전환해 다시 연결을 시도하는 것을 말한다. 따라서 다음과 같은 상황에서 도움이 된다 WebSocket을 지원하지 않는 브라우저 Server/Client 중간에 위치한 Proxy가 Upgrade 헤더를 해석하지 못해 서버에 전달하지 못한 상황 S..." }, { "title": "서블릿 컨테이너", "url": "/posts/spring-%EC%84%9C%EB%B8%94%EB%A6%BF-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88/", "categories": "study, spring", "tags": "spring", "date": "2022-11-21 21:19:26 +0900", "snippet": "서블릿 컨테이너는 서블릿들의 생성, 실행, 파괴를 담당하며 서블릿을 관리해준다. 서블릿 컨테이너는 클라이언트의 요청을 받고 응답할 수 있게, 웹 서버와 소켓을 만들어 통신한다.서블릿 컨테이너의 대표적인 무료 서비스로 톰캣이 있다. 톰캣은 웹 서버와 소켓을 만들어 통신하며 JSP와 서블릿이 작동할 수 있는 환경을 제공한다.서블릿이란?웹 프로그래밍에서 클라이언트의 HTTP 요청을 처리하고 응답을 보내는 자바 프로그래밍 기술. Servlet 클래스의 구현 규칙을 따른다.Servlet 은 javax.servlet 패키지에 정의된 인터..." }, { "title": "ElasticSearch", "url": "/posts/DB-ElasticSearch/", "categories": "study, database", "tags": "db", "date": "2022-11-21 21:18:26 +0900", "snippet": " Elasticsearch는 시간이 갈수록 증가하는 문제를 처리하는 분산형 RESTful 검색 및 분석 엔진입니다. Elastic Stack의 핵심 제품인 Elasticsearch는 데이터를 중앙에 저장하여 손쉽게 확장되는 광속에 가까운 빠른 검색, 정교하게 조정된 정확도, 강력한 분석을 제공합니다. (ElasticSearch 공식 문구)ElasticSearch는 Apache Lucene(아파치 루씬) 기반의 Java 오픈소스 분산 검색엔진이다. ElasticSearch를 통해 루씬 라이브러리를 단독으로 사용할 수 있게 되..." }, { "title": "Apache Kafka", "url": "/posts/design-pattern-Apache-Kafka/", "categories": "study, design pattern", "tags": "kafka", "date": "2022-11-18 23:00:26 +0900", "snippet": "Apache Kafka는 빠르고 확장 가능한 작업을 위해 데이터 피드의 분산 스트리밍, 파이프 라이닝 및 재생을 위한 실시간 스트리밍 데이터를 처리하기 위한 목적으로 설계된 오픈 소스 분산형 게시-구독 메시징 플랫폼이다.Kafka는 서버 클러스터 내에서 데이터 스트림을 레코드로 유지하는 방식으로 작동하는 브로커 기반 솔루션이다. Kafka 서버는 여러 데이터 센터에 분산되어 있을 수 있으며 여러 서버 인스턴스에 걸쳐 레코드 스트림(메시지)을 토픽으로 저장하여 데이터 지속성을 제공할 수 있다.Apache Kafka의 구성요소 ..." }, { "title": "커스텀 Annotation", "url": "/posts/java-%EC%BB%A4%EC%8A%A4%ED%85%80-annotation/", "categories": "study, java", "tags": "java", "date": "2022-11-18 18:19:26 +0900", "snippet": "Annotation애노테이션은 Java 5 부터 등장한 기능으로, 사전적의미는 “주석”이지만, 클래스나 메서드 등 타켓에 라벨을 붙여준다. 비즈니스 로직에는 영향을 주지는 않지만, 해당 타켓의 연결 방법이나 소스 코드의 구조를 변경할 수도 있다. 애노테이션은 소스코드에 메타데이터를 삽입하는 것이기 때문에 잘 이용하면 구독성뿐만 아니라 체계적인 소스코드 구성에도 도움을 준다.// 애노테이션 예시@Servicepublic class ArticleService커스텀 Annotation커스텀 애노테이션은 메타 애노테이션을 사용하여 다..." }, { "title": "STOMP", "url": "/posts/web-STOMP/", "categories": "study, web", "tags": "web", "date": "2022-11-17 18:19:26 +0900", "snippet": "STOMP(Simple Text Oriented Messaging Protocol)란?웹소켓 위에서 동작하는 서브 프로토콜로, 클라이언트와 서버가 서로 통신하는데 있어 메시지의 형식, 유형, 내용 등을 정의해주는 프로토콜이다.웹 소켓 프로토콜은 Text 또는 binary 두가지 유형의 메시지 타입을 정의하지만 메시지의 내용에 대해서는 정의하지 않는다. 즉, 웹 소켓만 사용해서 구현하게 되면 해당 메시지가 어떤 요청인지, 어떤 포맷으로 오는지 정해져있지 않아 일일이 구현해야한다.이때 STOMP라는 프로토콜을 서브 프로토콜로 사용..." }, { "title": "mybatis.type-aliases-package", "url": "/posts/spring-mybatis.type-aliases-package-%EB%8F%99%EC%9E%91%EC%9B%90%EB%A6%AC/", "categories": "study, spring", "tags": "", "date": "2022-11-17 16:19:26 +0900", "snippet": "mybatis mapper에서 requestType이나 parameterType으로 클래스를 참조할 때 다음과 같이 패키지명을 다 써줘야하는 불편함이 있다.&lt;mapper namespace="com.hello.spring.article.mapper.ArticleMapper"&gt; &lt;select id="selectArticles" resultType="com.hello.spring.article.dto.ArticleDto"&gt; ..." }, { "title": "좋은 로깅을 위해 알아야할 13가지", "url": "/posts/design-pattern-%EC%A2%8B%EC%9D%80-%EB%A1%9C%EA%B9%85%EC%9D%84-%EC%9C%84%ED%95%B4-%EC%95%8C%EC%95%84%EC%95%BC%ED%95%A0-%EA%B2%83/", "categories": "study, design pattern", "tags": "design", "date": "2022-11-17 15:23:26 +0900", "snippet": "이 글은 Logging Best Pratices : The 13 You Should Know를 기반으로 요약, 학습한 글입니다.좋은 로깅을 위해 알아야할 13가지Don’t write logs by yourselfprintf을 사용하거나 파일에 직접 입력하지마라. 로깅을 위한 표준 라이브러리를 사용하라.표준 라이브러리를 사용할 때 장점 어플리케이션의 다른 부분과 잘 조화됨을 보장할 수 있음 올바른 곳에 로그를 남길 수 있음.표준 라이브러리 종류 syslog() log4j, logbackLog at the proper le..." }, { "title": "SpEL - Spring Expression Language", "url": "/posts/spring-SpEL/", "categories": "study, spring", "tags": "", "date": "2022-11-14 23:19:26 +0900", "snippet": "이 문서는 개인적인 목적이나 배포하기 위해서 복사할 수 있다. 출력물이든 디지털 문서든 각 복사본에 어떤 비용도 청구할 수 없고 모든 복사본에는 이 카피라이트 문구가 있어야 한다. (출처)SpEL - Spring Expression Language객체를 조회하고 조작하는 기능을 제공하며, 메서드 호출, 물자열 템플릿 기능 등의 여러가지 추가기능을 제공하는 표현식 언어이다. OGNL 등 자바에서 사용가능한 여러 EL이 있지만, SpEL은 Spring 프로젝트 전반에 걸쳐 사용하기 위해 만들어졌으며 스프링 3.0부터 지원한다.Sp..." }, { "title": "빈 스코프", "url": "/posts/spring-%EB%B9%88-%EC%8A%A4%EC%BD%94%ED%94%84/", "categories": "study, spring", "tags": "", "date": "2022-11-13 21:19:26 +0900", "snippet": "빈 스코프는 말 그대로 빈이 존재할 수 있는 범위를 뜻한다. 스프링에서는 다음과 같은 스코프를 지원한다 싱글톤 : 기본 스코프. 스프링 컨테이너의 시작과 종료까지 유지되는 가장 넓은 범위의 스코프 프로토타입 : 스프링 컨테이너는 프로토타입 빈의 생성과 의존관계 주입까지만 관여하고 더는 관리하지 않는 매우 짧은 범위의 스코프이다. 웹 관련 스코프 request : 웹 요청이 들어오고 나갈때까지 유지되는 스코프 session : 웹 세션이 생성되고 종료될 때까지 유지되는 스코프 applica..." }, { "title": "ResponseStatus vs ResponseEntity", "url": "/posts/spring-@ResponseStatus-vs-ResponseEntity/", "categories": "study, spring", "tags": "", "date": "2022-11-13 18:23:26 +0900", "snippet": "​@ResponseStatus응답으로 보낼 데이터의 HttpStatus를 명시해주는 방법이다. 컨트롤러에서 Body 데이터만 반환하는 경우 HttpStatus를 명시하기 위해 사용한다.@ResponseStatus(HttpStatus.OK)@GetMapping("/article")public ArticleListDto getArticles(SelectArticlesQuery query) { ArticleListDto articleListDto = articleService.getArticleList(query..." }, { "title": "단일책임원칙(SRP)", "url": "/posts/design-pattern-%EB%8B%A8%EC%9D%BC%EC%B1%85%EC%9E%84%EC%9B%90%EC%B9%99/", "categories": "study, design pattern", "tags": "oop", "date": "2022-11-13 18:19:26 +0900", "snippet": "좋은 OOP 설계에서 지켜져야하는 규칙인 SOLID 중 S인 규칙으로, 하나의 모듈은 하나의 책임을 가져야한다는 원칙을 말한다. 클래스는 단 한개의 책임을 가져야한다. 하나의 모듈은 오직 하나의 액터에 대해서만 책임져야한다. 클래스를 변경하는 이유는 단 한개여야 한다.여기서 책임은 액터에 대한 책임을 의미한다. 하나의 액터에 대해 하나의 책임을 지는 객체를 정의하고 사용하라는 것이다. 액터는 시스템과 상호작용하는 시스템 외부의 어떤 존재를 뜻하는 말로, 시스템의 이해관계자로 정의할 수 있다. 쇼핑 앱을 예로 들면, 물건을..." }, { "title": "RestTemplate", "url": "/posts/spring-RestTemplate/", "categories": "study, spring", "tags": "", "date": "2022-11-12 20:19:26 +0900", "snippet": "스프링에서 지원하는 객체로, 간편하게 Rest 방식 API를 호출할 수 있는 스프링 내장 클래스다.스프링 3.0부터 지원하였고, json, xml 응답을 모두 받을 수 있다.특징 Blocking I/O 기반의 동기방식을 사용하는 템플릿 RESTful 형식에 맞추어진 템플릿 Header, Content-Type 등을 설정하여 외부 API 호출 Server to Server 통신에 사용동작원리 애플리케이션 내부에서 REST API에 요청하기 위해 RestTemplate의 메서드를 호출한다. RestTemplate은 Me..." }, { "title": "lombok 사용시 주의점", "url": "/posts/java-lombok-%EC%82%AC%EC%9A%A9%EC%8B%9C-%EC%A3%BC%EC%9D%98%EC%A0%90/", "categories": "study, java", "tags": "", "date": "2022-11-12 19:19:26 +0900", "snippet": "lombok은 @Getter, @Setter 같은 애노테이션 기반으로 Getter/Setter 메서드를 자동으로 생성해주는 편리한 라이브러리이다.하지만 편리함에 남용하는 애노테이션들이 있다.lombok 사용 시 주의해야할 애노테이션@AllArgsConstructor클래스에 존재하는 모든 필드에 대한 생성자를 자동으로 생성한다.@AllArgsConstructorpublic class User { private String name; private String userId; // 자동 생성 public User(Stri..." }, { "title": "json 응답 시 특정 필드 빼고 보내기", "url": "/posts/spring-Json-%EC%9D%91%EB%8B%B5-%EC%8B%9C-%ED%8A%B9%EC%A0%95-%ED%95%84%EB%93%9C-%EB%B9%BC%EA%B3%A0-%EB%B3%B4%EB%82%B4%EA%B8%B0/", "categories": "study, spring", "tags": "", "date": "2022-11-12 18:19:26 +0900", "snippet": "스프링에서는 Http 메시지 컨버터를 통해 사용자가 보낸 데이터와 서버에서 내보내는 데이터를 자동으로 변환해준다. 만약 다음과 같은 객체를 응답으로 보낼 경우 Content-Type이 application/json이라면, 스프링은 다음 객체를 자동으로 Json 형식으로 바꾸어 내보낸다.@AllArgsConstructor@Getterpublic class UserDto { private String userId; private String password;}// UserDto user = new UserDto("..." }, { "title": "junit - spy", "url": "/posts/java-junit-Spy/", "categories": "study, java", "tags": "", "date": "2022-11-12 18:19:26 +0900", "snippet": "Mock 객체와는 달리 객체의 특정 메서드만 stub으로 대체할 수 있는 방법을 제공한다.다음과 같이 만약 테스트 대상 객체의 특정한 메서드를 stub 처리하고 싶을 때 사용한다.@ExtendWith(MockitoExtension.class)public class BoardServiceTest{ @Spy private BoardService boardService; @Test public void 인증_실패시_예외발생() { // boardService의 인증을 담당하는 메서드를 stub 처리함 doR..." }, { "title": "equals(), hashCode()", "url": "/posts/java-equals-hashcode-%EB%8C%80%EC%B2%B4/", "categories": "study, java", "tags": "java", "date": "2022-11-12 15:19:26 +0900", "snippet": "equals()와 hashCode() 메서드는 모든 자바 객체의 부모인 Object 클래스에 정의되어있다. 따라서 모든 자바 객체는 equals()와 hashCode() 메서드를 가지고 있다.equals()현재 객체와 파라미터로 들어온 객체가 같은 지 검사하기 위해 사용한다. 기본적으로는 두 객체의 메모리 주소가 같아야 동일한 객체가 된다.public boolean equals(Objecet obj) { return (this == obj);}이러한 equals 객체를 오버라이드하여 필드값이 같은 객체를 같은 객체라고 판단하..." }, { "title": "Content-Disposition", "url": "/posts/web-Content-Disposition/", "categories": "study, web", "tags": "web", "date": "2022-11-11 18:20:26 +0900", "snippet": "HTTP Response Body에 오는 컨텐츠의 기질/성향을 알려주는 HTTP 헤더 속성이다. inline : 디폴트 값. body에 있는 값이 웹 페이지에 표시되어야한다는 뜻. Content-Disposition : inline 최신 브라우저에서 &lt;a&gt; 태그의 download 속성은 Content-Disposition : inline으로 가져온다. attachment : body에 있는 데이터를 다운 받아야한다는 뜻. filename이 명시되어있다면..." }, { "title": "스프링 캐시", "url": "/posts/spring-%EC%8A%A4%ED%94%84%EB%A7%81-%EC%BA%90%EC%8B%9C/", "categories": "study, spring", "tags": "spring", "date": "2022-11-11 18:19:26 +0900", "snippet": "[spring] 스프링 캐시캐시란 반복적으로 데이터를 불러올 때 지속적으로 DBMS 혹은 서버에 요청하는 것이 아닌 메모리에 데이터를 저장하였다가 데이터를 불러다가 쓰는 것을 말한다. 서버나 DBMS의 부담을 줄여주고, 메모리에 저장되어있기 때문에 많은 시스템에서 사용할수 있다.캐시는 Long Tail 법칙에 따라 시스템 리소스 사용의 대부분을 차지하는 20%의 요청을 캐싱하면 시스템 전체가 매우 빨라진다고 할 수 있다.CacheManager스프링 부트에서 spring-boot-starter-cache을 추가하여 구성할 수 있..." }, { "title": "PathPattern과 servletPath", "url": "/posts/spring-PathPattern%EA%B3%BC-servletPath/", "categories": "study, spring", "tags": "spring", "date": "2022-11-10 18:25:26 +0900", "snippet": "[spring] PathPattern과 servletPath문제다음과 같이 interceptor를 등록할 때, addPathPatterns()에 /api/**를 등록하였지만, /api/article을 요청했을 때 인터셉터가 제대로 동작하지 않았다. 하지만 /**로 등록하니 제대로 동작하였다.public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(authInterceptor) .order(1) .addPathPatterns(&..." }, { "title": "checked vs unchecked exception", "url": "/posts/java-checked-vs-unchecked-exception/", "categories": "study, java", "tags": "java", "date": "2022-11-10 18:24:26 +0900", "snippet": "[java] checked vs unchecked exception자바에서 프로그램에 이상이 있을 때 던져지는 Throwable은 Error와 Exception이 있다. Error : 시스템이 비정상적인 상황에 있다는 것을 의미. 시스템 레벨에서 발생하는 심각한 오류이기 때문에 개발자가 미리 예측하거나 처리할 수 없다. ex) OutOfMemoryError, ThreadDeath Exception : 개발자들이 만든 애플리케이션 코드에서 예외가 발생했다는 것을 의미한다. Check..." }, { "title": "HPA", "url": "/posts/devops-HPA/", "categories": "study, devops", "tags": "kubernetes", "date": "2022-11-10 18:23:26 +0900", "snippet": "[devops] HPAHorizontalPodAutoscaler의 약자로, k8s에서 CPU 사용률을 체크하여 Pod의 개수를 스케일링하는 기술이다. 지정한 메트릭을 controller가 체크하여 부하에 따라 필요한 pod의 replica 수가 되도록 자동으로 pod 수를 늘리거나 줄일 수 있는 기술이다.Auto scalingAuto scaling은 사용자가 정의한 주기(스케줄링)나 이벤트(모니터링 알람)에 따라 서버를 자동으로 생성하거나 삭제한다. 서비스 트래픽에 따라 서버를 늘리거나 줄여 발생하는 요금을 최소화한다.![0F..." }, { "title": "Helm", "url": "/posts/devops-Helm/", "categories": "study, devops", "tags": "kubernetes", "date": "2022-11-10 18:21:26 +0900", "snippet": "HelmKubernetes 패키지 관리를 도와주는 도구. node.js의 npm과 같은 역할을 수행한다.Helm을 사용하면 쿠버네티스 클러스터에서 동작하도록 작성된 패키지들을 관리할 수 있다. 즉, Helm을 사용하면 클러스터에 배포한 애플리케이션을 쉽게 설치, 업데이트, 삭제할 수 있다.일반적으로 쿠버네티스는 여러 오브젝트로 구성되어있는데, 만약 애플리케이션을 새로 업데이트하고 싶으면, 애플리케이션을 포함하는 모든 오브젝트마다 kubectl 명령을 날려야하는데, Helm을 사용하면 하나의 명령으로 애플리케이션을 배포할 수 있..." }, { "title": "Canary 테스트", "url": "/posts/devops-Canary-%ED%85%8C%EC%8A%A4%ED%8A%B8/", "categories": "study, devops", "tags": "", "date": "2022-11-10 18:20:26 +0900", "snippet": "[devops] Canary 테스트안정적인 버전을 릴리즈하기 전에 테스트 버전을 일부 사용자에게 배포하는 것을 말한다.만약 카나리 버전에 심각한 버그가 발생한다해도 사용하는 사용자가 적기 때문에 피해를 최소화할 수 있다. 또한 안정적인 버전과 테스트 버전이 모두 배포된 상태이기 때문에 A/B 테스트가 가능하다.유저가 직접 카나리 버전을 설치하는 경우도 있지만, 유저는 모르고 사용하도록 할 수 있다. 일부의 유저만 새로운 버전으로 서비스를 제공하는 방식이다.출처https://codechacha.com/ko/what-is-cana..." }, { "title": "blue-green 배포", "url": "/posts/devops-blue-green-%EB%B0%B0%ED%8F%AC/", "categories": "study, devops", "tags": "", "date": "2022-11-10 18:19:26 +0900", "snippet": "[devops] blue-green 배포애플리케이션의 이전 버전에 있던 사용자 트래픽을 이전 버전(blue)과 거의 동일한 새 버전(green)으로 점진적으로 이전하는 애플리케이션 배포 모델이다. 이때 두 버전 모두 프로덕션 환경에서 실행 상태를 유지한다.blue에서 green으로 완전히 이전되면 blue는 롤백에 대비하여 대기 상태에 두거나 프로덕션에서 가져온 후 업데이트하여 다음 업데이트의 템플릿으로 삼을 수 있다.k8s는 클라우드 네이티브 애플리케이션의 마이크로서비스를 패키지화하는 컨테이너의 오케스트레이션을 지원하며, 개..." }, { "title": "스프링 파일업다운로드", "url": "/posts/spring-%ED%8C%8C%EC%9D%BC-%EC%97%85%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C/", "categories": "study, spring", "tags": "", "date": "2022-11-09 18:19:26 +0900", "snippet": "[spring] 파일 업/다운로드HTML 폼 전송 방식HTML에서 폼을 전송하는 방식(Content-Type)에는 다음 두가지가 있다. application/x-www-form-urlencoded : 문자와 같은 데이터를 키와 함께 전송하는 방식 ex) username=Kim&amp;age=20 multipart/form-data : 여러 개의 데이터를 파트별로 나누어 전송하는 방식. 파일과 같은 바이너리 데이터를 전송할 때 쓰임. Content-Type: multipa..." }, { "title": "Spring 파라미터 Validation", "url": "/posts/spring-Validation/", "categories": "study, spring", "tags": "", "date": "2022-11-09 18:19:26 +0900", "snippet": "[spring] 파라미터 validationBindingResultHTTP 메세지 컨버터에서 발생한 데이터 바인딩 오류를 담아 컨트롤러에서 이용할 수 있는 객체. 다음과 같이 바인딩 오류가 발생할 수 있는 파라미터 바로 다음에 파라미터로서 추가한다.@PostMapping("/add")public String addItemV1(@ModelAttribute Item item, BindingResult bindingResult)컨트롤러에서 데이터 바인딩 오류가 발생하면 400 오류를 클라이언트로 전달한다. 하지만 ..." }, { "title": "유닛테스트 구현 검증이란?", "url": "/posts/design-pattern-%EC%9C%A0%EB%8B%9B%ED%85%8C%EC%8A%A4%ED%8A%B8%EC%97%90%EC%84%9C-%EA%B5%AC%ED%98%84-%EA%B2%80%EC%A6%9D%EC%9D%B4%EB%9E%80/", "categories": "study, design pattern", "tags": "design", "date": "2022-11-09 18:19:26 +0900", "snippet": "비즈니스 코드를 먼저 작성하고 유닛테스트를 작성할 때, 완성된 코드를 보고 테스트 코드를 작성하다보면 내부 구현에 맞추어 테스트를 작성하게 되고, 구현 자체를 검증하게 되는 일이 많이 발생하였다.유닛 테스트에서는 구현이 아닌 행동을 검증(test behaviour, not implementation)해야한다. 따라서 구현을 검증한 테스트 코드를 고치려 했으나 어디까지가 구현을 검증한 것인지가 모호했다. A 메서드 안에서 B 메서드를 제대로 호출했는지를 보는 것도 구현 검증일까? 구현 검증을 피하고자 DB를 검사해도 되는 걸..." }, { "title": "Keep-Alive", "url": "/posts/web-keep-alive/", "categories": "study, web", "tags": "web", "date": "2022-11-05 18:51:26 +0900", "snippet": "아래와 같이 http 헤더를 보다보면 keep-alive라고 명시된 부분이 보일 때가 있다. connection : keep-alive Keep-Alive:timeout=5, max=1000이 keep-alive는 무슨 뜻일까 궁금하여 찾아보았다.keep-aliveconnection : keep-alive connection은 네트워크 연결을 현재 요청이 종료된 이후에도 유지할 것인가를 설정하는 헤더이다. 다음과 같은 값을 지정할 수 있음 close : 한 요청이 종료되면 연결을 끊음. 디폴트 ..." }, { "title": "CORS", "url": "/posts/web-CORS/", "categories": "study, web", "tags": "cors", "date": "2022-11-05 18:44:26 +0900", "snippet": " CORS : Cross-Origin Resource Sharing(교차 출처 리소스 공유)서로 다른 출처(Origin)에서 리소스를 공유할 때 적용되는 정책이다.출처(Origin)이란?URL은 다음과 같이 여러 개의 구성요소로 이루어져있다.여기서 출처란 protocol + host + port 을 뜻한다. 이 조합이 같아야 같은 출처로 인정된다. 이때 http, https는 각각 80 443 포트로 추정되어 생략되어도 된다 하지만 만약 http://google.com:80 같이 포트 번호가 명시되어있다면 포트번호까지 모..." }, { "title": "mybatis dynamic field", "url": "/posts/spring-mybatis-dynamic-field/", "categories": "study, spring", "tags": "", "date": "2022-11-05 18:41:26 +0900", "snippet": "게시글 검색을 개발하는 도중 검색의 대상이 되는 field(title | nickname | content)를 동적으로 mybatis 쿼리문 상에 사용해야할 일이 있었다. 따라서 다음과 같이 #{} 를 사용하여 변수를 넣었으나 오류가 발생했다.&lt;where&gt; &lt;when test="type != 'hashtag'"&gt; atc.#{type} LIKE concat('%', #{keyword}, '%') &lt;/whe..." }, { "title": "InvalidDefinitionException", "url": "/posts/spring-InvalidDefinitionException/", "categories": "study, spring", "tags": "", "date": "2022-11-05 18:36:26 +0900", "snippet": "@ResquestBody로 받는 매개변수의 클래스에 @NoArgsConstructor를 빼니 InvalidDefinitionException 에러가 발생했다.@PostMapping("/login")public String login(@Valid @RequestBody LoginRequestBody loginRequestBody)@Getter@AllArgsConstructor@ToStringpublic class LoginRequestBody { com.fasterxml.jackson.databind.exc...." }, { "title": "interceptor", "url": "/posts/spring-Interceptor/", "categories": "study, spring", "tags": "spring", "date": "2022-11-05 18:35:26 +0900", "snippet": "스프링 인터셉터는 스프링 MVC가 제공하는 기술로, 서블릿 필터하고는 적용순서, 범위, 사용방법이 다르다.소개스프링 인터셉터 흐름 HTTP 요청 -&gt; WAS -&gt; 필터 -&gt; 서블릿 -&gt; 스프링 인터셉터 -&gt; 컨트롤러 스프링 인터셉터는 디스패처 서블릿과 컨트롤러 사이에서 컨트롤러 호출 직전에 호출됨. 스프링 인터셉터는 스프링 MVC가 제공하는 기능으로, 디스패처 서블릿 이후에 등장하게 된다. 스프링 MVC의 시작점이 디스패처 서블릿이라고 생각하..." }, { "title": "HTTP 메시지 컨버터", "url": "/posts/spring-HTTP-%EB%A9%94%EC%8B%9C%EC%A7%80-%EC%BB%A8%EB%B2%84%ED%84%B0/", "categories": "study, spring", "tags": "spring", "date": "2022-11-05 18:34:26 +0900", "snippet": "스프링에서 컨트롤러를 개발하다보면, url 파라미터를 long 으로 받아도 문제없이 작동된다.@GetMapping(path = "/article/{articleId}")public String getArticleDetail(@PathVariable long articleId) {위와 같은 컨트롤러가 있을 때, 유저는 /article/123라는 요청을 보내면 자동으로 getArticleDetail의 매개변수인 articleId에 123 값이 문제없이 할당된다.URL 파라미터는 기본적으로 문자열로 인식다. 하지만..." }, { "title": "Pure DI - IoC가 없는 DI", "url": "/posts/spring-DI%EB%8A%94-IOC%EB%A5%BC-%ED%95%84%EC%9A%94%EB%A1%9C%ED%95%98%EC%A7%80-%EC%95%8A%EB%8A%94%EB%8B%A4(%EC%A0%95%EB%A6%AC)/", "categories": "study, spring", "tags": "spring", "date": "2022-11-05 18:34:26 +0900", "snippet": "이 포스트는 참고문서를 필자가 이해하기 쉽게 정리한 내용입니다.DI는 IoC를 사용하지 않아도 된다DI(Dependency Injection)다음 PizzaStore 클래스는 정해진 개수의 Pizza인스턴스를 가지고 있다. 만약 Pizza가 더 필요하면 PizzaStore를 직접 수정해야하다. 이는 OCP를 위반하며 DI로 해결해줄 수 있다public class PizzaStore{ private readonly Pizza[] pizzas = new Pizza[] { new P..." }, { "title": "converter", "url": "/posts/spring-converter/", "categories": "study, spring", "tags": "spring", "date": "2022-11-05 18:33:26 +0900", "snippet": "controller 인자로 enum 받는 문제를 해결했던 것은 enum 타입에 맞춘 Converter를 bean에 등록하는 것이었다. 단순히 bean에 등록하는 것으로 controller에서 httpConverter 동작할 때 이것을 적용한 것이다. 이 뒤에 무엇이 있을까 하여 조사해보았다.스프링에서는 기본적인 타입 변환은 자동으로 지원한다. 따라서 파라미터로 넘어온 문자 데이터가 컨트롤러에서 직접 Integer 타입으로 변환된다.@GetMapping(path = "/{articleId}")public Str..." }, { "title": "controller에서 enum을 인자로 받기", "url": "/posts/spring-controller%EC%97%90%EC%84%9C-enum%EC%9D%84-%EC%9D%B8%EC%9E%90%EB%A1%9C-%EB%B0%9B%EA%B8%B0/", "categories": "study, spring", "tags": "spring", "date": "2022-11-05 18:32:26 +0900", "snippet": "검색 API를 개발하는 과정에서 다음과 같이 검색 타입을 enum 으로 지정하였고, controller의 param으로 받으려고 했다.@Getter@AllArgsConstructorpublic enum SearchType { TITLE("title"), CONTENT("content"), HASHTAG("hashtag"), NICKNAME("nickname"); private final String type;}public class ArticleSearch..." }, { "title": "ArgumentResolver 활용", "url": "/posts/spring-ArgumentResolver-%ED%99%9C%EC%9A%A9/", "categories": "study, spring", "tags": "spring", "date": "2022-11-05 18:31:26 +0900", "snippet": "ArgumentResolver 활용ArgumentResolver는 스프링 MVC 구조의 어댑터 핸들러에서 핸들러에 필요한 파라미터를 만들어주는데 호출하는 부분이다. 이 ArgumentResolver를 직접 구현하여 활용하면 다양한 상황에서 편리하게 적용할 수 있다.로그인 회원 정보를 편리하게 받아오는 예제컨트롤러@GetMapping("/")public String homeLoginV3ArgumentResolver(@Login Member loginMember, Model model)Login 어노테이션@Tar..." }, { "title": "Valid vs Validatedated", "url": "/posts/spring-@Valid-vs-@Validated/", "categories": "study, spring", "tags": "spring", "date": "2022-11-05 18:25:26 +0900", "snippet": "@Valid JSR-303 표준 객체 제약조건 검증 어노테이션 ArgumentResolver에 의해 처리된다. 검증에 오류가 있다면 MethodArgumentNotValidException 예외가 발생하며 디스패처 서블릿에 기본으로 등록된 예외 리졸버인 DefaultHandlerExceptionResolver에 의해 400 에러가 발생한다. 스프링에서는 ArgumentResolver에 의해 처리되기에 @Valid는 기본적으로 컨트롤러에서만 동작하며 다른 계층에서는 검증되지 않는다. 다른 계층에서 파라미터를 검증하기 위해..." }, { "title": "클래스 초기화 블록", "url": "/posts/java-%ED%81%B4%EB%9E%98%EC%8A%A4-%EC%B4%88%EA%B8%B0%ED%99%94-%EB%B8%94%EB%A1%9D/", "categories": "study, java", "tags": "java", "date": "2022-11-05 18:24:26 +0900", "snippet": "클래스 초기화 블록어떤 클래스에 static 변수를 추가했더니 github copilot이 다음과 같은 문장을 추천해주었다.public class Crypto { private static CryptoProperties cryptoProperties; // github copilot suggestion static { cryptoProperties = new CryptoProperties(); } ...처음보는 문법이기에 조사해보았더니 클래스 초기화 블록이라는 단어를 접할 수 있었다.클래스 초기화 블록클래스의 멤버변수를 초..." }, { "title": "pinpoint", "url": "/posts/devops-pinpoint/", "categories": "study, devops", "tags": "pinpoint", "date": "2022-11-05 18:23:26 +0900", "snippet": "Pinpoint Pinpoint는 대규모 분산 시스템의 성능을 분석하고 문제를 진단, 처리하는 플랫폼입니다. 2012년 7월에 개발을 시작해 2015년 1월 9일에 오픈소스로 공개했습니다Pinpoint 개발 동기와 Pinpoint의 특징인터넷 서비스의 시스템 복잡도가 증가함에 따라 장애나 성능 문제가 발생했을 때 해결이 어려워졌다. 네이버의 시스템도 마찬가지였다. 시스템 복잡도가 높아지며 발생하는 문제를 해결하기 위해 n계층 아키텍처를 효과적으로 추적할 수 있는 새로운 플랫폼을 개발하기로 했고 그것이 Pinpoint이다.Pi..." }, { "title": "istio", "url": "/posts/devops-istio/", "categories": "study, devops", "tags": "istio", "date": "2022-11-05 18:21:26 +0900", "snippet": "Istio istio는 service mesh를 위한 tool입니다. istio를 사용하면 MSA(Micro Service Architecture)를 적용할 때 반복적으로 설정해야하는 L7/모니터링을 쉽고 간편하게 설정할 수 있습니다.Service meshService mesh는 API 등을 사용하여 마이크로 서비스 간 통신을 안전하고, 빠르고, 신뢰할 수 있게 만들기 위해 설계된 전용 인프라 계층이다. Service mesh는 보통 application 서비스에 경량화 프록시를 사이드카 방식으로 배치하여 서비스 간 통신을..." }, { "title": "사이드카 패턴", "url": "/posts/devops-%EC%82%AC%EC%9D%B4%EB%93%9C%EC%B9%B4-%ED%8C%A8%ED%84%B4/", "categories": "study, devops", "tags": "sidecar", "date": "2022-11-05 18:20:26 +0900", "snippet": "사이드카 패턴쿠버네티스의 패턴 중 하나로, 어플리케이션 컨테이너와 독립적으로 동작하는 별도의 컨테이너를 붙이는 패턴이다. 어플리케이션 컨테이너의 변경이나 수정 없이 독립적으로 동작하는 컨테이너를 붙였다 뗐다 할 수 있다.파드에서의 사이드카파드는 쿠버네티스에서 가장 기본적인 배포 단위로서 자신에게 속한 컨테이너들에게 런타임제약을 걸 수 있다. 예를 들어 모든 컨테이너는 동일한 노드에 배치되고, 동일한 파드 수명주기를 공유하게 할 수 있다. 파드는 또한 컨테이너들이 볼륨을 공유하고 로컬 네트워크 또는 호스트 IPC를 통해 서로 통..." }, { "title": "API 예외처리", "url": "/posts/spring-API-%EC%98%88%EC%99%B8%EC%B2%98%EB%A6%AC/", "categories": "study, spring", "tags": "spring", "date": "2022-11-05 18:17:26 +0900", "snippet": "예외 발생 시에도 JSON 응답 보내기ResponseEntity를 사용해서 응답을 보내면 된다.public ResponseEntity&lt;String&gt; errorPage500Api() { String result = "error"; return new ResponseEntity(result, HttpStatus.BAD_REQUEST.value());}ResponseEntity를 사용해서 응답하기 때문세 메시지 컨버터가 동작하면서 클라이언트에 JSON이 반환된다.스프링부트 예외 처리Basi..." }, { "title": "AOP", "url": "/posts/spring-AOP/", "categories": "study, spring", "tags": "spring", "date": "2022-11-05 18:16:26 +0900", "snippet": " AOP = Aspect Oriented Programming = 관점 지향 프로그래밍관점 지향이란 어떤 로직을 기준으로 핵심적인 관점, 부가적인 관점으로 나누어서 보고 그 관점을 기준으로 모듈화 하겠다는 것이다. 여기서 모듈화란 어떤 공통된 로직이나 기능을 하나의 단위로 묶는 것을 말한다.AOP에서 각 관점을 기준으로 모듈화한다는 것은 코드들을 부분적으로 나누어서 모듈화하겠다는 의미다. 개발을 하다보면 서로 다른 파일에서 같은 코드를 반복해서 쓰는 경우가 있는데 이것을 흩어진 관심사(crosscutting concerns)..." }, { "title": "ModelAttribute와 NoArgsConstructor 추가 시 아무것도 안들어가는 이슈", "url": "/posts/spring-%EC%BB%A8%ED%8A%B8%EB%A1%A4%EB%9F%AC-%ED%8C%8C%EB%9D%BC%EB%AF%B8%ED%84%B0-%ED%81%B4%EB%9E%98%EC%8A%A4%EC%99%80-@NoArgsConstructor-%EC%9D%B4%EC%8A%88/", "categories": "study, spring", "tags": "spring", "date": "2022-11-05 18:15:26 +0900", "snippet": "제목 그대로 다음과 같이 @NoArgsConstructor 애노테이션을 @ModelAttribute로 적용되는 컨트롤러 파라미터 클래스에 추가하니 제대로 쿼리를 날려도 아무것도 안들어가는 이슈가 발생했다.@GetMapping("/article")public ResponseEntity getArticles(@ModelAttribute SelectArticlesQuery query)@Getter@AllArgsConstructor@NoArgsConstructor@ToStringpublic class SelectAr..." }, { "title": "컨트롤러 매개변수/반환 타입 유형", "url": "/posts/spring-%EC%BB%A8%ED%8A%B8%EB%A1%A4%EB%9F%AC-%EB%A7%A4%EA%B0%9C%EB%B3%80%EC%88%98-%EB%B0%98%ED%99%98-%ED%83%80%EC%9E%85-%EC%9C%A0%ED%98%95/", "categories": "study, spring", "tags": "spring", "date": "2022-11-05 18:14:26 +0900", "snippet": "요청파라미터(쿼리, form 데이터)HttpServletRequest@RequestMapping("/request-param-v1")public void requestParamV1(HttpServletRequest request, HttpServletResponse response)HttpServletRequest에서 조회해서 꺼내올 수 있다.@RequestParam @RequestMapping("/request-param-v2") public String requestParamV2( ..." }, { "title": "예외 처리와 오류페이지", "url": "/posts/spring-%EC%98%88%EC%99%B8%EC%B2%98%EB%A6%AC%EC%99%80-%EC%98%A4%EB%A5%98%ED%8E%98%EC%9D%B4%EC%A7%80/", "categories": "study, spring", "tags": "spring", "date": "2022-11-05 18:13:26 +0900", "snippet": "서블릿 예외처리서블릿의 예외처리 방식Exception WAS &lt;- 서블릿 컨테이너 &lt;- 필터 &lt;- 서블릿 &lt;- 인터셉터 &lt;- 컨트롤러(예외발생) 웹 애플리케이션에서 발생한 예외를 잡지 못하면, 예외는 서블릿 컨테이너에까지 전달된다. 서블릿 컨테이너는 넘어온 예외를 기준으로 등록된 오류페이지를 조회한다. 등록된 것이 없다면 기본으로 등록되어있는 500 Internal Server Error 페이지를 클라이언트로 반환한다.response.sendError() W..." }, { "title": "스프링 MVC 구조", "url": "/posts/spring-%EC%8A%A4%ED%94%84%EB%A7%81-MVC-%EA%B5%AC%EC%A1%B0/", "categories": "study, spring", "tags": "architecture", "date": "2022-11-05 18:12:26 +0900", "snippet": "과정 클라이언트가 서버로 요청하면 먼저 Dispatcher Servlet 받음 Dispatcher Servlet은 http 요청의 URL과 메소드를 보고 이것을 처리할 수 있는 핸들러(컨트롤러)를 찾기 위해 핸들러 매핑 과정을 진행한다. 핸들러를 찾으면 이 핸들러를 처리할 수 있는 핸들러 어댑터를 조회한다. 핸들러 어댑터를 실행시킨다. 핸들러 어댑터는 요청을 적절히 처리한 다음 핸들러를 호출한다. 핸들러는 작업을 완료한 후 핸들러 어댑터에게 작업 결과를 반환한다. 핸들러 어댑터는 이 결과를 가공하여 ModelAndV..." }, { "title": "서블릿 필터", "url": "/posts/spring-%EC%84%9C%EB%B8%94%EB%A6%BF-%ED%95%84%ED%84%B0/", "categories": "study, spring", "tags": "spring", "date": "2022-11-05 18:11:26 +0900", "snippet": "서블릿 필터스프링에서 공통 관심사를 처리하기 위한 방법으로, 스프링 인터셉터와 함께 쓰이는 방법이다. 스프링 AOP를 사용하여 공통관심사를 처리할 수 있지만 웹과 관련된 공통 관심사는 서블릿 필터나 스프링 인터셉터를 사용하는 것이 좋다. 서플릿 필터나 스프링 인터셉터는 HttpServletRequest를 제공해주기 때문이다.소개필터 흐름 HTTP 요청 -&gt; WAS -&gt; 필터 -&gt; 디스패처 서블릿 -&gt; 컨트롤러필터는 디스패처 서블릿이 호출되기 전에 호출된다. 필터를 통해 다음과..." }, { "title": "쿠키 인증 vs 세션 vs JWT", "url": "/posts/web-%EC%BF%A0%ED%82%A4-%EC%9D%B8%EC%A6%9D-vs-%EC%84%B8%EC%85%98-vs-JWT/", "categories": "study, web", "tags": "web", "date": "2022-09-30 18:19:26 +0900", "snippet": "쿠키사용자가 로그인하면, 사용자 식별자를 암호화 또는 평문으로 쿠키에 담는 방식. 요청이 오면 쿠키에 담긴 식별자를 보고 유저를 구분한다. 장점 유효한 쿠키인지만 확인하면 되기에 서버자원과 비용을 아낄 수 있다. 서버를 무상태(stateless)로 만들어줌. 암호화를 하면 brute force 대응 가능(특정 횟수 이상 인증 실패시 ip 잠금) 간단히 구현 가능 단점 쿠키가 탈취되면 위장 가능 세션인증이 완료되면 사용자 식별자를 서버(DB 등)에..." }, { "title": "프론트 아키텍쳐 흐름", "url": "/posts/%ED%94%84%EB%A1%A0%ED%8A%B8-%EC%95%84%ED%82%A4%ED%85%8D%EC%B3%90-%EB%B3%80%ED%99%94/", "categories": "study, design pattern", "tags": "architecture", "date": "2022-09-17 22:00:00 +0900", "snippet": "프론트 아키텍처 흐름MVC Model - View -Controller로 나눈 아키텍처 Model 컨트롤러가 호출했을때, 요청에 맞는 역할을 수행한다. 비즈니스 로직을 구현하는 영역으로 응용프로그램에서 데이터를 처리하는 부분 뷰나 컨트롤러에 대해서 어떤 정보도 알지 말아야한다. Controller 클라이언트의 요청을 받았을 때, 그 요청에 대해 실제 업무를 수행하는 모델컴포넌트를 호출한다. 또한 클라이언트가 보낸 데이터가 있다면, 모델에 전달하기 쉽게 데이터를 가공한다...." }, { "title": "스프링 게층구조", "url": "/posts/%EC%8A%A4%ED%94%84%EB%A7%81-%EA%B3%84%EC%B8%B5%EA%B5%AC%EC%A1%B0/", "categories": "study, spring", "tags": "spring", "date": "2022-09-13 11:00:00 +0900", "snippet": "스프링 계층구조Web 계층 컨트롤러(@Controller)와 JSP/Freemarker 등의 뷰 템플릿 영역 이외에도 필터(@Filter), 인터셉터, 컨트롤러 어드바이스(@Controller Advice) 등 외부 요청과 응답에 대한 전반적인 영역을 나타낸다. 외부 요청과 응답에 대한 전반적인 영역. 어플리케이션의 진입점이기 때문에 다른 레이어에서 발생한 예외도 처리함. 인증을 관리하고 권한없는 사용자의 인가를 거부하는 역할도 함. 데이터 처리 : DTO만 처리해야함.Service 계층 @Service가 사용되는 서..." }, { "title": "WAS vs 웹서버", "url": "/posts/was-webserver/", "categories": "study, web", "tags": "web", "date": "2022-09-12 20:00:00 +0900", "snippet": "WAS(Web Application Server) DB 조회나 다양한 로직처리를 요구하는 동적인 컨텐츠를 제공하기 위해 만들어진 Application Server HTTP를 통해 컴퓨터나 장치에 애플리케이션을 수행해주는 미들웨어(소프트웨어 엔진)이다. 웹 컨테이너 혹은 서블릿 컨테이너 라고도 불린다. 컨테이너란, jsp, servlet을 실행시킬 수 있는 소프트웨어를 말함 즉, WAS란 JSP, servlet 구동환경을 제공한다. 쉬운 설명 : 포트에 연결되어 요청을 프로그램으로 쏴주..." }, { "title": "Open Graph(OG) 프로토콜", "url": "/posts/opengraph/", "categories": "study, web", "tags": "web", "date": "2022-09-11 16:00:00 +0900", "snippet": "Open Graph 프로토콜HTML문서의 head에는 &lt;meta&gt; 태그로 사이트의 정보가 표기되어있는 경우가 많다. 이는 크롤러가 무엇이 제목이고, 무엇이 내용인지 파악하기 쉽도록 하기 위해서 표기한 것이다.페이스북의 Open Graph 프로토콜은 이러한 메타태그의 표기 방법은 통일한 것이다. 이 Open Graph 프로토콜이 우리가 보는 미리보기 화면의 실체를 구성하는 메타 데이터 표기 방법이다.작동원리 사용자가 링크를 입력창에 입력한다. 페이스북, 네이버 블로그, 카카오톡은 입력창의 문자열이 “..." }, { "title": "Mysql Count 속도", "url": "/posts/Mysql-Count%EC%9D%98-%EC%86%8D%EB%8F%84/", "categories": "study, database", "tags": "db", "date": "2022-09-08 18:19:26 +0900", "snippet": "Mysql의 Select Count(*) 얼마나 빠를까?사용하는 데이터베이스 엔진에 따라 다르다. InnoDB : O(n) MyISAM : O(1)이유는 MyISAM의 경우 row가 몇 개 있는지 저장하고 있기에 Count(*) 쿼리가 들어오면 그 저장한 값을 바로 리턴하여 O(1)이 된다.Count(*) vs Count(column) vs Count(*) WhereCount(*)이 가장 빠르다.이유는 Count(*) 의 경우 내부적으로 값을 확인하지 않고, row의 개수만 확인한다.하지만 column을 주면 내부적으로 값..." }, { "title": "리액트 리팩토링", "url": "/posts/%EB%A6%AC%EC%95%A1%ED%8A%B8%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81/", "categories": "study, react", "tags": "", "date": "2022-09-04 21:00:00 +0900", "snippet": "리액트 리팩토링useEffect 사용useEffect 올바른 사용 props, state 변경에 따라 다른 데이터, state를 업데이트해야할 때 useEffect의 사용을 자제하자. useEffect vs EventHandler 중 어느 쪽에서 유저 인터랙션을 처리해야할까? useEffect 내에서 data fetching을 할 때 주의해야할 점.race condition 문제주로 API 요청을 보낼 때 발생함. 요청을 여러 번 보낼 때, 앞 요청이 끝나기 전에 뒤 요청이 먼저 끝날 수도 있다. 이러한 상황을 항상 고려..." }, { "title": "자바 기본", "url": "/posts/java-%EA%B8%B0%EB%B3%B8/", "categories": "study, java", "tags": "", "date": "2022-09-04 14:00:00 +0900", "snippet": "Eclipse 프로젝트 생성new Java project를 하면 여러 선택지가 나옴. Project layout use project folder as root for sources and class files 빌드 전 소스코드와 빌드 후 생성된 결과물이 root에 한번에 생김 Create separate folders for sources and class files 빌드 전 소스코드와 빌드 후 생성된 결과..." }, { "title": "useEffect 올바른 사용", "url": "/posts/useEffect-%EC%98%AC%EB%B0%94%EB%A5%B8-%EC%82%AC%EC%9A%A9/", "categories": "study, react", "tags": "react", "date": "2022-08-24 14:00:00 +0900", "snippet": "useEffect 올바른 사용props, state 변경에 따라 다른 데이터, state를 업데이트해야할 때useEffect를 사용하면 안된다.props, state가 변경이 되면 리렌더링이 발생하는데, 리렌더링이 끝난 후 useEffect가 호출되어 다시 리렌더링이 발생하기 때문이다.따라서 다음과 같이 코드를 변경해야한다.데이터를 업데이트해야하는 경우// useEffect로 업데이트function TodoList({ todos, filter }) { const [newTodo, setNewTodo] = useState(&#..." }, { "title": "바벨", "url": "/posts/babel/", "categories": "study, javascript", "tags": "javascript", "date": "2022-08-23 14:00:00 +0900", "snippet": "바벨바벨이란?자바스크립트 트랜스파일러.보통 모던 자바스크립트를 호환성을 위해 예전 문법으로 변환할때 사용한다.Typescript와 JSX 코드를 변환할 때도 사용한다.바벨 설정 파일 예시{ "presets": [ [ "@babel/env", { "targets": { "chrome": "79", // 예시) 크롬 79까지 지원하는 코드를 만든다. "edge&q..." }, { "title": "선언형프로그래밍", "url": "/posts/%EC%84%A0%EC%96%B8%ED%98%95/", "categories": "study, design pattern", "tags": "declare", "date": "2022-08-19 21:00:00 +0900", "snippet": "선언형, 명령형 그리고 추상화선언형이란? 명령형은 어떻게(How)에, 선언형은 무엇(What)에 집중한다.선언형은 명령형 코드에서 ‘어떻게’를 감추고 ‘무엇을’만 노출하는 방식의 추상화이다. 일종의 리팩토링이다.예시를 들면 다음과 같다.// 명령형 코드 : 배열에 있는 모든 숫자를 하나씩 제곱해서 result 배열에 넣는다.function double(arr) { let results = []; for (let i = 0; i &lt; arr.length; i++) { results.push(arr[i] * ..." }, { "title": "React-query 에서 cache를 사용하는 방법", "url": "/posts/react-query-cache/", "categories": "study, react", "tags": "", "date": "2022-08-19 21:00:00 +0900", "snippet": "useQuery 설정React-Query 공식문서상 캐싱개념은 stale과 cachetime을 통해 이루어진다useQuery의 옵션으로 staletime과 cachetime을 보낼 수 있다. staletime : fetch를 통해 전달받은 데이터는 리액트 쿼리의 자료구조 내용중 캐시에 저장되는데, 이 캐시데이터의 ‘신선한 상태’가 언제까지 될지를 말해주는 옵션 default = 0, 받아오는 즉시 stale하다고 판단하여 캐싱데이터와 무관하게 계속해서 fetching을 날림 cachetime : ..." }, { "title": "DB 페이징 기법", "url": "/posts/%ED%8E%98%EC%9D%B4%EC%A7%95/", "categories": "study, database", "tags": "db", "date": "2022-08-16 21:00:00 +0900", "snippet": "페이징DB 에서 페이징을 구현하는 방법오프셋 페이징limit과 offset을 활용하여 페이징을 구현하는 방법.SELECT * FROM tableORDER BY timestampOFFSET 10LIMIT 5장점 구현이 간단하다.단점 Offset을 정말로 건너 뛰는 게 아니라, 한번은 읽는다. 따라서 만약 Offset이 100만이라면, 100만개의 데이터를 읽고 그다음 Limit으로 할당된 데이터를 읽는다. 사용자가 다음 페이지로 넘어가기 전에 새로운 데이터가 입력된다면, 다음 페이지에서 중복데이터가 보일 수 있다. 사용자..." }, { "title": "자바스크립트 실행 컨텍스트", "url": "/posts/%EC%8B%A4%ED%96%89%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8/", "categories": "study, javascript", "tags": "javascript", "date": "2022-08-14 17:30:00 +0900", "snippet": "실행컨텍스트 코드의 실제 진행상황을 추적하는데 필요한 정보들을 모아둔 구조 함수가 ()로 호출되면 실행컨텍스트가 생성됨. 실행 컨텍스트에는 파라미터를 포함한 지역 메모리와 실행문이 있음. 함수가 종료되면 실행 컨텍스트는 사라진다.자바스크립트의 함수 객체 서브루틴으로 실행할 수 있는 객체 동작을 나타내는 실행코드가 있으면서, 일반 객체와 동일하게 동작할 수 있다. 서브루틴 실행 : sum() 일반 객체 : sum.a = 1; 콜스택 현재 실행되고 ..." }, { "title": "코테대비 SQL 문법", "url": "/posts/%EC%BD%94%ED%85%8C-%EB%8C%80%EB%B9%84-sql-%EB%AC%B8%EB%B2%95/", "categories": "study, db", "tags": "", "date": "2022-08-01 14:00:00 +0900", "snippet": "정렬SELECT * FROM ANIMAL_INS ORDER BY ANIMAL_ID ASCORDER BY ANIMAL_ID DESCORDER BY NAME ASC, DATETIME DESC조건SELECT ANIMAL_ID, NAME FROM ANIMAL_INSWHERE INTAKE_CONDITION="Sick"WHERE INTAKE_CONDITION&lt;&gt;"Aged"WHERE (A.SEX_UPON_INTAKE&lt;&gt;"Spayed Female&..." }, { "title": "CSS 최적화", "url": "/posts/css-%EC%B5%9C%EC%A0%81%ED%99%94/", "categories": "study, css", "tags": "css", "date": "2022-07-31 22:00:00 +0900", "snippet": "CSS 최적화가 필요한 이유CSS는 렌더링을 막는다.CSS의 존재만으로도, CSS가 파싱되기 전까지 브라우저는 렌더링이 지연된다. 만약 브라우저가 CSS가 없는 페이지를 그대로 노출하면, 스타일적용이 되지않은 페이지가 나타나기 때문이다. 이를 FOUC라고 불린다.CSS는 HTML 파싱도 막을 수 있다브라우저가 CSS가 파싱되기 전까지 콘텐츠를 보여주지 않더라고, HTML의 로딩된 부분만을 일단 보여줄 수 있다. 그러나 스크립트의 경우 async defer 가 없다면 파싱을 막게 된다. 스크립트는 잠재적으로 페이지를 조작할 여자..." }, { "title": "클래스형 컴포넌트 vs 함수형 컴포넌트", "url": "/posts/%ED%81%B4%EB%9E%98%EC%8A%A4%ED%98%95-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8/", "categories": "study, react", "tags": "react", "date": "2022-07-31 21:00:00 +0900", "snippet": "클래스형 컴포넌트import React, { Component } from 'react';class Hello extends Component { static defaultProps = { name: '이름없음' }; render() { const { color, name, isSpecial } = this.props; return ( &lt;div style=&gt; {isSpecial &amp;&amp; &l..." }, { "title": "React에서 Key를 사용하는 이유", "url": "/posts/react-key/", "categories": "study, react", "tags": "react", "date": "2022-07-28 22:00:00 +0900", "snippet": "리액트로 개발을 하다가 리스트를 만들면, 다음과 같이 key를 사용하라는 안내문이 뜬다.평소엔 무시하고 key를 그냥 넣었지만, 왜 그런지 갑자기 궁금해져 찾아보았다.Recursing On Children해당 내용은 리액트 공식문서에 Recursing On Children으로 나와있다.리액트 리렌더링 과정에서 리스트를 처리할때, 리액트는 기본적으로 실제 DOM의 리스트와 virtual DOM의 변경된 리스트를 비교한다.// 전&lt;ul&gt; &lt;li&gt;first&lt;/li&a..." }, { "title": "JS 코테 대비 메소드 정리", "url": "/posts/js-%EC%BD%94%ED%85%8C-%EB%8C%80%EB%B9%84/", "categories": "알고리즘", "tags": "", "date": "2022-07-21 22:00:00 +0900", "snippet": "문법 for … of : 반복가능한 객체 (Array, String, Set) for … in : 객체. 키를 뽑아냄.Set Set.prototype.add(value) Set.prototype.clear() Set.prototype.delete(value) Set.prototype.forEach((value, key, set) =&gt; {}) 값, 키, set을 받음 Set.ptototype.has(value)Map Map.prototype.set(key, value)..." }, { "title": "학습 내용 요약", "url": "/posts/summary/", "categories": "study, summary", "tags": "summary", "date": "2022-07-19 14:00:00 +0900", "snippet": "프론트엔드Virtual DOM리액트의 작동방식은 상태변화 시, 그 컴포넌트와 하위 컴포넌트를 모두 교체하기에 많은 DOM 변화가 일어난다.이때 리액트에서는 DOM을 추상화한 객체인 Virtual DOM을 변경하고, 변경이 완료되면 실제 DOM과의 차이점을 확인한다음 한번의 리렌더로 해결한다.Virtual DOM은 메모리에 존재하여 메모리를 차지한다는 단점이 있다.리액트도 DOM API를 사용하여 뷰를 변화시키기에, DOM API보다 더 빠르진 않다. Virtual DOM은 리액트를 빠르게 하기 위한 수단.링크React key리..." }, { "title": "virtual DOM와 리렌더링", "url": "/posts/virtual-Dom/", "categories": "study, react", "tags": "react", "date": "2022-07-17 14:00:00 +0900", "snippet": "Virtual DOM은 DOM을 추상화한 가상의 객체이다.DOM 이란?HTML 문서를 파싱하여 문서의 구성요소들을 객체로 구조화하여 나타낸 것.HTML Elements, 속성, CSS style, Events, Methods 를 구조화해서 나타낸 객체고, 이 객체를 이용해서 웹페이지 구성요소를 제어할 수 있다.Virtual DOM은 이러한 DOM을 추상화한 가상의 객체이다.Virtual DOM 이 해결하고자 하는 문제React 작동 방식은 상태가 변경된 컴포넌트는 그 컴포넌트부터 자식 컴포넌트까지 리렌더링한다. 하지만 이런 식..." }, { "title": "React 18로 업데이트 방법, 문제점 정리", "url": "/posts/react18%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8/", "categories": "projects, forkie", "tags": "", "date": "2022-06-03 14:00:00 +0900", "snippet": "기존에 React 17을 사용하던 Forkie Player 프로젝트를 React 18로 업데이트 하는 기록업데이트 하는 이유 automatic batching 동영상이 끝났을때 다음 영상으로 넘어가거나, 현재 영상의 시작부분으로 돌아가기 위해선 동영상의 timeupdate, ready, pause, ended 등등의 상태에 대응할 필요가 있다. 이때 이벤트 핸들러 안에서 여러 상태업데이트를 할때, automatic batching이 없을때는 서로 다른 타이밍에 발생하는 업데이트를 대응하기가 까다로웠다..." }, { "title": "velog 메모", "url": "/posts/velog-%EB%A9%94%EB%AA%A8/", "categories": "study, react", "tags": "", "date": "2022-04-24 23:00:00 +0900", "snippet": "리액트 리렌더링https://velog.io/@eunbinn/when-does-react-render-your-component children으로 넘어온 컴포넌트는 사용되는 컴포넌트가 리렌더링되도, 리렌더링의 영향을 받지 않는다. 리렌더링은 현재 컴포넌트에서 변화된 것이 없어도, 자식으로 전파된다. but 현재 컴포넌트에 useMemo를 사용하면, 변화가 없을시 자식으로도 전파되지 않음 useMemo도 써야할 때를 알아야한다. (구조변경 등의 방법으로 리렌더링을 막을 수 있음)" }, { "title": "함수형 프로그래밍 (이해하기 쉽게)", "url": "/posts/%ED%95%A8%EC%88%98%ED%98%95%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D/", "categories": "study, design pattern", "tags": "fp", "date": "2022-03-30 14:00:00 +0900", "snippet": "액션, 계산, 데이터순수함수 vs 부수효과를 대체한 개념들.데이터 : 이벤트에 대한 사실. 액션에 의해 변화됨액션 : 데이터를 변화시킬 수 있음. 실행시점이나 횟수에 의존하여 언제하느냐에 따라 결과가 달라지면 액션이다.계산 : 입력값을 통해 출력을 만들어내는 것. 같은 입력에 대해 항상 같은 출력값만 내놓아야한다. 외부 세계에 영향을 주면 안됨.함수형 프로그래밍 예시)// 함수형 프로그래밍 관점에서 분리해보자.function App() { // 데이터 const [count, setCount] = useState(0); ..." }, { "title": "view", "url": "/posts/view/", "categories": "study, database", "tags": "db", "date": "2022-03-02 23:25:00 +0900", "snippet": "View다른 테이블을 기반으로 만들어진 가상 테이블. 논리적으로만 존재하는 테이블.장점 질의문을 쉽게 작성할 수 있음 데이터 보안유지에 도움이 됨. 데이터 관리가 편해짐특징 ALTER 문으로 뷰를 재정의하는 것은 불가능 기본 테이블 삭제 시 같이 삭제된다. 독자적인 인덱스를 가질 수 없음.**뷰생성 **CREATE VIEW 뷰_이름[(속성_리스트)] AS SELECT 문 [WITH CHECK OPTION] AS SELECT 문 : 뷰를 정의. ORDER BY는 사용불가 WITH CHECK OPTION : 뷰에 삽입..." }, { "title": "IPv6", "url": "/posts/ipv6/", "categories": "study, network", "tags": "network", "date": "2022-03-02 14:00:00 +0900", "snippet": "IPv6 128비트 사용. Multicast 대신에 Broadcast를 함. ICMPv6 ARP, IGMP 기능흡수 ARP : IP주소를 MAC 주소로 변환하는 프로토콜 IPv4 에서 IPv6로의 전환 정책 Dual stack : 두가지 다 지원하도록 하여, 두가지 주소를 모두 사용할 수 있도록 하는 것 tunneling : 종단 사이에 IPv4를 쓰는 홉이 있을때, 그 중간 단..." }, { "title": "deadlock 발생 조건", "url": "/posts/deadlock/", "categories": "study, os", "tags": "os", "date": "2022-03-02 14:00:00 +0900", "snippet": "deadlock 발생 조건아래 4가지 조건을 모두 만족하면 데드락 발생가능성이 있음 상호배제(Mutual Exclusion) 한 리소스는 한번에 한 프로세스만이 사용할 수 있음 Hold and wait No preemption Circular waitdeadlock 방지법 Prevention 교착 상태가 발생할 수 있는 요구조건을 만족시키지 않게 함으로써 방지 Avoidance 교착상태 발생 가능성이 있는 자원할당을 하지 않음 ex) 은행..." }, { "title": "17088 등차수열 변환", "url": "/posts/17088-%EB%93%B1%EC%B0%A8%EC%88%98%EC%97%B4%EB%B0%98%ED%99%98/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2022-03-02 14:00:00 +0900", "snippet": "알고리즘 첫번째와 두번째 원소 사이의 공차를 구함 두번째 원소부터 마지막원소까지의 공차를 만족하는 경우만으로 dfs를 진행함 마지막원소까지 공차를 만족하는 모든 경우의 수를 구하고, 그 중 최소 연산으로 만족하는 경우의 수를 반환함. 위 과정을 첫번째 원소와 두번째 원소 사이의 공차의 경우의수만큼 진행하고, 그 중에서 최소 연산의 수를 구한다. 각 원소별로, (-1, 0, 1) 세 개의 연산을 할 수 있으므로 공차의 경우의 수는 9개. 삽질 다이나믹을 적용할 수 있을거..." }, { "title": "서브네팅", "url": "/posts/subnetting/", "categories": "study, network", "tags": "network", "date": "2022-02-27 21:34:00 +0900", "snippet": "서브네팅사용가능한 IP 주소 범위를 분할하여 사용하는 것특징 2의 배수로, 같은 크기로 분할주소할당서브넷 마스크는 다음과 같음 네트워크 주소사용부분 모두 1 + 호스트 주소 사용부분 모두 0각 서브넷에서는 다음과 같이 2개의 주소는 예약되어있음 네트워크 주소 : (기존 네트워크 부 + 분할부분 매핑 주소) + (남은 호스트부분 모두 0) 브로드캐스트 주소 : (기존 네트워크 부 + 분할 매핑 주소) + (남은 호스트 부분 모두 1)따라서 서브넷에서 사용가능한 주소 범위는, 네트워크 주소와 브로드캐스트 주소를 제외한 나머..." }, { "title": "관계 데이터 모델", "url": "/posts/data/", "categories": "study, database", "tags": "db", "date": "2022-02-25 22:30:00 +0900", "snippet": "관계 데이터 모델용어 릴레이션 : 하나의 개체에 관한 데이터를 2차원 테이블로 저장한 것. 릴레이션 스키마 : 릴레이션의 이름과 속성이름으로 정의. 정적. 내포 릴레이션 인스턴스 : 릴레이션에 존재하는 튜플들의 집합. 동적. 외연 도메인 : 하나의 속성이 가질 수 있는 모든 값의 집합. 적합성 판단의 기준이 됨 데이터베이스의 구성 데이터베이스 스키마 : 데이터베이스의 전체구조. 릴레이션 스키마의 모음 데이터베이스 인스턴스 : 데이터베이스를 구성하는 릴..." }, { "title": "반정규화", "url": "/posts/denormalized/", "categories": "study, database", "tags": "db", "date": "2022-02-25 22:00:00 +0900", "snippet": "반정규화시스템의 성능 향상, 개발 및 운영의 편의성을 위해 정규화된 데이터 모델을 통합, 중복, 분리하는 과정으로 의도적으로 정규화 원칙을 위배하는 행위.과도한 반정규화는 성능 저하를 불러일으킴. 시스셈의 성능과 관리효율성은 증가하지만, 데이터의 일관성 및 정합성을 저하될 수 있음. 따라서 사전에 데이터의 일관성 및 정합성을 우선할지, 데이터베이스의 성능과 단순화를 우선으로 할지 결정해야함 방법 테이블 통합, 테이블 분할, 중복 테이블 추가, 중복 속성 추가테이블 통합두개의 테이블이 조인되는 경우가..." }, { "title": "응집도와 결합도", "url": "/posts/%EC%9D%91%EC%A7%91%EB%8F%84/", "categories": "study, design pattern", "tags": "", "date": "2022-02-24 23:47:00 +0900", "snippet": "응집도모듈 내부의 기능적인 집중 정도. 높을 수록 좋다.우연적 응집도 &lt; 논리적 응집도 &lt; 시간적 응집도 &lt; 절차적 응집도 &lt; 교환적 응집도 &lt; 순차적 응집도 &lt; 기능적 응집도기능적 응집도(Functional Cohesion) : 모듈 내부의 모든 기능이 단일한 목적을 위해 수행되는 경우순차적 응집도(Sequential Cohesion) : 모듈 내에서 한 활동으로 부터 나온 출력값을 다른 활동이 사용할 경우교환적 응집도(Communication Coh..." }, { "title": "스키마", "url": "/posts/scheme/", "categories": "study, database", "tags": "", "date": "2022-02-24 00:24:00 +0900", "snippet": "내부 스키마데이터베이스가 실제로 저장되는 방법을 정의한 것.개념 스키마데이터베이스 전체의 논리적 구조를 정의한 것. 데이터베이스 하나에 하나만 존재.개체, 관계, 제약조건, 보안정책 등을 정의한다.외부 스키마사용자에게 필요한 데이터베이스를 정의한 것" }, { "title": "블랙박스/화이트박스 테스트", "url": "/posts/blackbox-whitebox/", "categories": "study, software engineering", "tags": "", "date": "2022-02-23 00:18:00 +0900", "snippet": "블랙박스 테스트소프트웨어의 내부 구조나 작동 원리는 모르는 상태에서 동작을 검사하는 방식기법 동등 분할 기법 : 프로그램 입력 도메인을 테스트 케이스가 산출될 수 있는 데이터 클래스로 분류하는 방법 경계값 분석 기법 : 입력 조건의 중간 값보다 경계값에서 에러가 발생될 확률이 높으므로 오류 예측 기법 : 놓치기 쉬운 오류들을 감각 및 경험으로 찾아보는 방법 원인 결과 그래프 기법 : 입력 데이터 간 관계가 출력에 미치는 영향을 그래프로 표현하여 오류를 발견 의사결정 테이블 테스팅 : 논리적 조건이나 상황에서 입력 조건..." }, { "title": "전위식/후위식", "url": "/posts/prefix-postfix/", "categories": "study, algorithm", "tags": "", "date": "2022-02-23 00:04:00 +0900", "snippet": "전위식연산자를 먼저 표시하고 연산에 필요한 피연산자를 나중에 표시(A + B) * (C - D)((A + B) * (C - D))*(+(AB)-(CD))*+AB-CD후위식피연산자를 먼저 표시하고, 연산자를 나중에 표시(A+B) * (C-D)((A+B) * (C-D))((AB)+(CD)-)*AB+CD-*" }, { "title": "통합 테스트", "url": "/posts/integration-test/", "categories": "study, software engineering", "tags": "", "date": "2022-02-22 23:59:00 +0900", "snippet": "통합 테스트상향식 통합 테스트프로그램의 하위 모듈에서 상위모듈로 통합하면서 테스트하는 기법. 하위 모듈을 클러스터로 결합 더미 모듈인 드라이버 작성 통합된 클러스터 단위로 테스트 테스트 완료 후 클러스터는 프로그램 구조의 상위로 이동해 결합하고, 드라이버는 실제 모듈로 대체 됨.하향식 통합 테스트프로그램의 상위모듈에서 하위모듈로 통합하면서 테스트하는 기법 주요 제어 모듈은 작성된 프로그램을 사용. 주요 제어 모듈의 종속 모듈은 Stub으로 대체 DFS, BFS로 하위 모듈인 Stub을 한번에 하나씩 실제 모듈로 교체..." }, { "title": "럼바우 분석기법", "url": "/posts/rumbaugh/", "categories": "study, software engineering", "tags": "", "date": "2022-02-22 01:50:00 +0900", "snippet": "럼바우 분석기법모델링 기법중 하나로 그래픽으로 표현한 분석기법객체 모델링 기법이라고도 한다.3단계로 구성 객체 모델링 동적 모델링 기능 모델링1. 객체 모델링(object modeling)객체 다이어그램을 표시함. 정보 모델링이라고도 하며, 시스템에서 요구되는 객체를 찾아내어 속성과 연산 식별 및 객체들 간의 관계를 규정하여 클래스 다이어그램으로 표현한 것2. 동적 모델링상태 다이어그램을 통해 시간의 흐름에 따라 객체들을 모델링한다.객체들관계 관계에는 제어흐름, 상호작용, 동적순서 등의 관계가 있다.3. 기능..." }, { "title": "UML", "url": "/posts/UML/", "categories": "study, software engineering", "tags": "", "date": "2022-02-22 01:15:00 +0900", "snippet": "UML(Unified Modeling Language)통합 모델링 언어를 사용하여 시스템 상호작용, 업무 흐름, 시스템 구조, 컴포넌트 관계 등을 그린 도면.프로그래밍을 단순화 시켜 표현하여 의사소통 하기 좋고, 대규모 프로젝트에서는 로드맵을 만들거나 개발을 위한 시스템 구축의 기본을 마련한다.요구사항 모델링에 사용되는 기법 중 하나구성요소 사물 (things) 구조사물 : 시스템의 개념적, 물리적 요소를 표현. (ex : 클래스, usecase, 컴포넌트) 행동사물 : 시간과 공간에 따른 요소들의 ..." }, { "title": "객체지향 분석론", "url": "/posts/oop-analysis/", "categories": "study, software engineering", "tags": "", "date": "2022-02-22 01:00:00 +0900", "snippet": "Coad YourdonE-R 다이어그램을 사용하여 객체의 행위를 모델링.객체 식별, 구조 식별, 주제정의, 속성과 인스턴스 연결 정의, 연산과 메시지 연결 정의 등의 과정으로 주로 관계를 분석 하는 기법Booch미시적, 거시적 개발 프로세스를 모두 사용하는 분석 방법.클래스와 객체들을 분석 및 식별하고 클래스의 속성과 연산을 정의JacobsonUseCase를 사용하여 분석.Wirfs-Brock 방법분석과 설계간 구분이 없으며, 고객 명세서를 평가해서 설계작업까지 연속적으로 수행하는 기법출처https://m.blog.naver...." }, { "title": "CASE", "url": "/posts/case/", "categories": "study, software engineering", "tags": "", "date": "2022-02-22 01:00:00 +0900", "snippet": "CASE(Computer Aided Software Engineering)소프트웨어 개발 시 사용되는 분석 자동화 도구. 소프트웨어 개발 과정의 일부나 전체를 자동화하는 도구이다.CAD 기기와 유사한 것이라고 생각하면 됨. 요구분석 -&gt; 설계 -&gt; 구현 -&gt; 검사 및 디버깅 과정을 CASE를 활용하여 자동화함CASE의 장점 개발 속도가 빨라짐. 하나의 tool을 활용함으로써, 표준화된 개발 환경을 구축할 수 있음. 따라서 커뮤니케이션이 용이해짐. 오류 수정이 쉬워지고, 이로인해 소프..." }, { "title": "GoF", "url": "/posts/Gof/", "categories": "study, design pattern", "tags": "", "date": "2022-02-22 01:00:00 +0900", "snippet": "GoF(Gang of Four)GoF에서는 23가지 디자인패턴을 3가지 유형으로 분류함. 생성 패턴(Creation pattern) 객체를 생성하는데 관련된 패턴들 객체가 생성되는 과정의 유연성을 높이고, 코드의 유지를 쉽게함 구조 패턴(Structural Pattern) 프로그램 구조에 관련된 패턴들 프로그램 내의 자료구조나 인터페이스 구조 등 프로그램의 구조를 설계하는데 활용할 수 있는 패턴들 행동 패턴(Behavioral Pattern) ..." }, { "title": "운영체제 소개", "url": "/posts/cs-os/", "categories": "study, os", "tags": "os", "date": "2022-02-21 02:00:00 +0900", "snippet": "운영체제 정의컴퓨터 시스템의 4가지 요소 유저 어플리케이션 OS 하드웨어운영체제 유저 관점 : 어플리케이션을 수행함. 컴퓨터 사용을 편리하게 해줌 시스템 관점 : 자원할당자. 어플리케이션과 i/o 장치의 수행을 다루는 프로그램 컨트롤러커널OS에 속하며, 컴퓨터에서 항상 수행되는 프로그램.하드웨어의 자원을 자원이 필요한 프로세스에게 나눠주고, 프로세스 제어, 메모리 제어, 시스템 콜 등을 수행하는 부분이다.컴퓨터 시스템 수행컴퓨터 시스템 조직cpu, disk(buffer), memory, usb(buffer), gra..." }, { "title": "Arrow function", "url": "/posts/arrowfunction/", "categories": "study, javascript", "tags": "javascript", "date": "2022-02-18 12:50:00 +0900", "snippet": "화살표함수기존의 function 표현방식보다 간결하게 함수를 표현할 수 있다. 화살표함수는 항상 익명이며, 자신의 this, arguments, super, new.target을 바인딩하지 않는다. 따라서 생성자로는 사용할 수 없다. 화살표함수 도입 영향 : 짧은 함수, 상위 스코프 this짧은 함수var materials = ["Hydrogen", "Helium", "Lithium", "Beryllium"];materials.map(function (m..." }, { "title": "Promise, async/await", "url": "/posts/promise-async-await/", "categories": "study, javascript", "tags": "javascript", "date": "2022-02-18 12:30:00 +0900", "snippet": "Promise비동기로 처리하기 위한 방법 중 하나.다음과 같이 사용//Promise 선언var _promise = function (param) { return new Promise(function (resolve, reject) { window.setTimeout(function () { if (param) { resolve("해결 완료"); } else { reject(Error("실패!!")); } }, 3000); })..." }, { "title": "this에 대해서", "url": "/posts/this/", "categories": "study, javascript", "tags": "javascript", "date": "2022-02-18 12:10:00 +0900", "snippet": "this에 대해서자바스크립트에서 모든 함수는 실행될때마다 함수 내부에 this라는 객체가 추가된다. arguments라는 유사배열 객체와 함께 함수 내부로 암묵적으로 전달되는 것이다. 그렇기 때문에 자바스크립트에서의 this는 함수가 호출된 상황에 따라 그 모습을 달리한다.상황 1. 객체의 메서드를 호출할때객체의 프로퍼티가 함수일 경우 메서드라고 부른다. this는 함수를 실행할 때 함수를 소유하고 있는 객체를 참조한다. 즉, 해당 메서드를 호출한 객체로 바인딩 된다. A.B일때, B함수 내부에서의 this는 A를 가리키는 것..." }, { "title": "closure", "url": "/posts/closure/", "categories": "study, javascript", "tags": "javascript", "date": "2022-02-09 02:00:00 +0900", "snippet": "Closure두개의 함수로 만들어진 환경으로 이루어진 특별한 객체의 한 종류이다. 여기서 환경이란, 클로저가 생성될 때 그 범위에 있던 여러 지역변수들이 포함된 context를 말한다. 이 클로저를 통해서 자바스크립트에는 없는 private 속성/메소드, public 속성/메소드를 구현할 수 있다.클로저 생성하기다음은 클로저가 생성되는 조건이다. 내부 함수가 익명 함수로 되어 외부 함수의 반환값으로 사용된다. 내부 함수는 외부 함수의 실행 환경(execution environment)에서 실행된다. 내부 함수에서 사용되는..." }, { "title": "hoisting", "url": "/posts/hoisting/", "categories": "study, javascript", "tags": "javascript", "date": "2022-02-09 01:20:00 +0900", "snippet": "호이스팅은 변수를 끌어올리는 것. var로 선언된 모든 변수 선언을 hoist한다. hoist란 변수의 정의가 그 범위에 따라 선언과 할당으로 분리되는 것을 의미한다. 즉, 함수 내의 선언들을 모두 끌어올려서 해당 함수 유효 범위의 최상단에 선언하는 것자바스크립트에서 변수의 선언은 끌어올려진다. 다음의 코드를 보자.function getX() { console.log(x); // undefined; var x = 100; console.log(x); // 100}getX();다른 언어의 경우 첫번째 console.log..." }, { "title": "javascript 이벤트루프", "url": "/posts/eventloop/", "categories": "study, javascript", "tags": "javascript", "date": "2022-02-08 11:20:00 +0900", "snippet": "이벤트 루프javascript를 공부하다보면 아래와 같은 말을 종종 듣는다. 싱글스레드 기반으로 동작하는 자바스크립트 이벤트 루프를 기반으로 하는 싱글스레드 Node.js정말 싱글 스레드인가? 어떻게 싱글 스레드인가? 이벤트 루프는 무엇인가? 를 간단히 알아보기 위해 자바스크립트가 동작하는 환경과 엔진에 대해 알아보자.Javascript Enginejavascript로 작성한 코드를 해석하고 실행하는 인터프리터. 주로 웹브라우저에서 사용되지만, node.js에서는 V8 엔진이 사용된다.구글에서 개발한 V8 엔진을 비롯해, ..." }, { "title": "알고리즘 개요", "url": "/posts/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EA%B0%9C%EC%9A%94/", "categories": "study, algorithm", "tags": "", "date": "2022-02-07 11:00:00 +0900", "snippet": "DP복잡한 문제를 간단한 여러 하위문제로 나누어 푸는 방법두가지 구현방식이 존재함 top-down 여러개의 하위문제로 나누고, 하위문제를 푼 다음, 그것들을 결합하여 최종적으로 최적해를 구한다. 이때 하위문제로 나눌때 같은 하위문제를 가지고 있는 경우가 있다. 이때의 최적해를 저장해서 사용하여 같은 하위문제를 다시 풀지 않도록 하는 방법을 메모이제이션이라고 한다. bottom-up 하위문제들의 해로 상위문제의 최적해를 구한다. 피보나치 예시top-downf (in..." }, { "title": "singleton", "url": "/posts/architecture/", "categories": "cs, design pattern", "tags": "singleton", "date": "2022-02-07 01:00:00 +0900", "snippet": "Singleton 패턴애플리케이션에서 인스턴스를 하나만 만들어 사용하기 위한 패턴이다.커넥션 풀, 스레드 풀, 디바이스 설정 객체 등의 경우, 인스턴스를 여러 개 만들게하면 자원을 낭비하게 되거나 버그를 발생시킬 수 있으므로 오직 하나만 생성하고 그 인스턴스를 사용하도록 하는 것이 목적이다.구현하나의 인스턴스만을 유지하기 위해 인스턴스 생성에 특별한 제약을 걸어둬야한다. new를 실행할 수 없도록 생성자에 private 접근 제어자를 지정 유일한 단일 객체를 반환할 수 있도록 정적 메소드를 지원. 유일한 단일 객체를 참조..." }, { "title": "데이터베이스 개요", "url": "/posts/database-%EA%B0%9C%EC%9A%94/", "categories": "study, database", "tags": "db", "date": "2022-02-04 11:40:00 +0900", "snippet": "데이터베이스파일시스템의 문제점 앱과 상호 연동되어있기 때문에 다음과 같은 문제가 발생함. 데이터 종속성 데이터 중복성 데이터 무결성 데이터베이스 특징 데이터 독립성 물리적 독립성 : 데이터베이스를 수정하더라도, 응용 프로그램을 수정할 필요는 없음 논리적 독립성 : 하나의 논리적인 구조로 다양한 응용 프로그램의 논리적 요구를 만족시켜줄 수 있음. 데이터의 무결성 여러 경로를 통해 잘못된 데이터가 발생하는 것을 방지하기 위해, 데이..." }, { "title": "React) JS 다운로드 시간 동안의 로딩 화면", "url": "/posts/react-%EB%A1%9C%EB%94%A9-%EC%B2%98%EB%A6%AC/", "categories": "study, react", "tags": "", "date": "2022-02-03 03:00:00 +0900", "snippet": "최근 프로젝트에서 리액트 앱을 배포하였는데, 인터넷 속도가 느린 환경에서 자바스크립트를 다운로드 받는 시간이 오래 걸린다는 걸 눈치챘다.눈치챘다는 표현이 어울리는데 왜냐하면 사실 당연한 건데 이제까지 신경쓰지 않았던 부분이기 때문이다. 리액트앱은 매우 큰 자바스크립트 파일 하나로 이루어져있고, 이를 다운로드 받는 것은 당연히 오래 걸리는 일이기 때문이다.따라서 이 시간 동안 단순한 로딩 화면이라도 띄울수있다면 사용자 경험을 향상 시킬 수 있을 것이라 생각되어 관련된 공부를 하게 되었다.리액트 앱이 띄워지기까지사용자가 우리의 앱..." }, { "title": "2631 줄세우기", "url": "/posts/2631%EC%A4%84%EC%84%B8%EC%9A%B0%EA%B8%B0/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2022-01-30 14:00:00 +0900", "snippet": "알고리즘 LIS를 구하고, LIS를 구성하지 않는 요소들만 배치해주면 됨 LIS를 구하는 방법으론 n이 최대 200이니 O(n^2)을 써도 충분함 LIS 구하는 코드 O(n^2) 이전 요소들 중에 자기보다 작은 것 중, 가장 큰 LIS를 가진 요소에 이어 붙임 for (int i = 1; i &lt;= n; i++) { save[i] = 1; for (int j = 1; j &lt; n; j++..." }, { "title": "운영체제 개요", "url": "/posts/os/", "categories": "study, os", "tags": "os", "date": "2022-01-25 02:00:00 +0900", "snippet": "프로세스와 스레드의 차이프로세스실행중인 프로그램으로, 메모리에 적재되어 CPU의 할당을 받을 수 있는 것을 말함.OS로부터 주소공간, 파일, 메모리등을 할당받으며, 이것들을 총칭하여 프로세스라고한다.할당받는 메모리 공간 프로세스 스택 : 함수의 매개변수, 복귀주소, 로컬 변수 같은 임시자료를 저장 데이터 섹션 : 전역변수들을 수록 힙 : 프로세스 실행 중에 동적으로 항당되는 메모리프로세스 제어 블록(Process Control Block, PCB)PCB는 특정 프로세스에 대한 중요한 정보를 저장하고 있는 OS의 자료구조이..." }, { "title": "1522 문자열 교환", "url": "/posts/1522/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2022-01-24 21:00:00 +0900", "snippet": "알고리즘 문자열에서 a의 개수를 ac 라고 할때, 시작점을 문자열의 맨처음부터 맨 끝까지 이동하면서, 길이가 ac인 문자열을 안에 b가 몇개있는지 검사한다. 이때 검사한 b의 개수 중 최소가 필요한 최소의 교환횟수 쉽게 말해서, 길이가 ac인 문자열 안에 b를 최소로 포함한 문자열을 a로 채우는 문제로 치환한 것이다.코드#include &lt;iostream&gt;using namespace std;string str;int ans = 2000000000;int min(int a,int b) { return..." }, { "title": "network 개요", "url": "/posts/network/", "categories": "study, network", "tags": "network", "date": "2022-01-24 12:00:00 +0900", "snippet": "HTTP의 GET과 POST 비교GET 데이터가 HTTP Request Message의 헤더부분의 url에 담겨서 전송됨. url의 끝에 ? 뒤에 데이터를 붙여 요청. 데이터의 크기가 제한적임. 데이터가 url에 노출되므로 보안에 약함POST 데이터가 HTTP Requst Message의 바디부분에 담김 데이터 크기가 커도 됨. GET 방식보다 보안적임.어디에 무엇을 적용할까? 서버에서 데이터를 가져와서 보여줄때 GET 방식이 적합함 서버의 값이나 상태 등을 변경할때 PO..." }, { "title": "DataStructure 개요", "url": "/posts/datastructure/", "categories": "study, datastructure", "tags": "자료구조", "date": "2022-01-23 14:00:00 +0900", "snippet": "Array vs Linked ListArray 논리적 저장 순서와 물리적 저장순서가 일치함. 장점 Random Access : 인덱스로 원하는 원소에 바로 접근이 가능하여, O(1)에 해당원소로 접근가능 언어에따라 다르지만, 필요한 만큼만 메모리를 차지함 단점 중간에 한 원소를 삭제하거나, 새로운 원소를 넣을때, 다른 원소를 shift 해줘야하여 O(n)의 시간이 걸림. Linked Li..." }, { "title": "(Typescript) React typescript hack", "url": "/posts/typescript-react-hack/", "categories": "study, react", "tags": "", "date": "2022-01-22 01:00:00 +0900", "snippet": "React 에서 Typescript 사용시 참고할만한 Typing 기법을 기록한 곳React.HTMLArributes&lt;[HTMLElement]&gt;한 Element에 부여될 속성 값들을 모두 참조할때 사용하면 좋다. 다음과 같이 기본 HTML 엘리먼트를 스타일링해서 사용할때 유용함export interface ITextButtonProps extends IProps, React.HTMLAttributes&lt;HTMLDivElement&gt; { text?: string | JSX.Elem..." }, { "title": "(CSS)다른 엘리먼트의 이벤트 발생 시, 스타일 적용법", "url": "/posts/(CSS)%EB%8B%A4%EB%A5%B8-%EC%97%98%EB%A6%AC%EB%A8%BC%ED%8A%B8%EC%9D%98-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%B0%9C%EC%83%9D-%EC%8B%9C,-%EC%8A%A4%ED%83%80%EC%9D%BC-%EC%A0%81%EC%9A%A9%EB%B2%95/", "categories": "study, css", "tags": "", "date": "2022-01-22 01:00:00 +0900", "snippet": "yourlist_web_renewal 중, 한 input 엘리먼트의 placeholder가 보일시와 안보일 시, label 엘리먼트에 각기 다른 스타일을 적용해야하는 일이 있었다.현재 사용중인 tailwind에선 어떻게 사용해야할지 모르겠으나 순수 CSS 방식으로 하면 다음과 같다..form_field:placeholder-shown ~ .form__label { font-size: 1rem; top: 0; cursor: text;}.form_field:focus ~ .form__label { top: -1.2rem; ..." }, { "title": "flex, grid로 남은 곳 꽉채우기", "url": "/posts/flex,grid%EB%A1%9C-%EB%82%A8%EB%8A%94%EA%B3%B5%EA%B0%84-%EC%B1%84%EC%9A%B0%EA%B8%B0/", "categories": "study, css", "tags": "", "date": "2022-01-21 14:00:00 +0900", "snippet": "최근 프로젝트를 하면서, 다른 요소들의 크기는 정해져있을때 남은 한 요소의 크기를 부모 요소에서 남는 공간만큼 부여하는 부분이 많았다(그리고 앞으로도 많을 것 같다)위 부분을 구현할때, flex와 grid를 통해 해결했어서 그것을 기록 하는 차원에서 이 글을 적어본다.flex만약 다음과 같이 div 엘리먼트들이 배치되어있고, 우리는 가운데 expand를 container의 남는 공간만큼 확장 시키고 싶다고 하자..container { display: flex; background-color: burlywood;}.leftI..." }, { "title": "13144 List of Unique Numbers", "url": "/posts/13144/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2022-01-18 20:00:00 +0900", "snippet": "알고리즘(내방식) 숫자를 왼쪽에서 오른쪽으로 살펴가면서, 그 수가 나온 인덱스를 기록(saveIdx)한다. i를 기록할때 이미 기록된 인덱스가 있을 경우, 그 인덱스부터 이전에 체크가 안된 인덱스들은 i 까지만 연속해서 숫자를 뽑을 수 있다. 따라서 이에 해당하는 경우의 수를 정답에 추가시켜준다.알고리즘(투포인터) 내가 한 방식에서 관점만 바꾸면 투포인터 방법이 된다. 왼쪽에서 오른쪽으로 end를 하나씩 늘려가며 숫자를 살펴가면서, 현재 수를 체크한다. 만약 i에서..." }, { "title": "MVVM, MVP 패턴", "url": "/posts/MVVM-MVP/", "categories": "study, design pattern", "tags": "architecture", "date": "2022-01-18 14:00:00 +0900", "snippet": "MVVMModel - View - View Model 의 약자로, 프로그램의 비지니스 로직과 프레젠테이션 로직을 UI로 명확하게 분리하는 패턴구성요소 Model : 데이터를 보관하고 있는 부분으로, 데이터를 불러오거나 업데이트하는 로직이 있음. View Model : Model에 데이터를 요청하고 가공함. 비지니스 로직을 처리. View로부터 입력을 받아 적절한 처리를 함 View : UI 담당. UI에 연관된 로직만 수행한다. 필요한 데이터는 View Model과 data binding을 통해 얻는다. 사용자의 입력을 ..." }, { "title": "Git", "url": "/posts/git/", "categories": "study, software engineering", "tags": "", "date": "2022-01-18 00:12:00 +0900", "snippet": "GitGIT의 개발 목표 빠른 속도 단순한 구조 비선형적인 개발(수천 개의 동시다발적인 브랜치) 완벽한 분산(DVCS) 대형 프로젝트에도 유용할 것Inside of GitGit은 기본적으로 파일시스템의 스냅샷을 저장한다.(커밋 당시의 GIT 디렉터리의 모든 파일 정보를 저장) 또한 파일 및 스냅샷을 해시하여 바뀐 버전인지 아닌지 빠르게 체크함이후 스냅샷들의 크기가 커지면, 주기적으로 git gc(garbage collection)을 통해 delta를 만듦gitignore.gitignore 에서는 아래와 같은 glob ..." }, { "title": "MVC 패턴", "url": "/posts/MVC-%ED%8C%A8%ED%84%B4/", "categories": "study, design pattern", "tags": "architecture", "date": "2022-01-17 00:33:00 +0900", "snippet": "MVC 컴포넌트의 역할Model 컨트롤러가 호출했을때, 요청에 맞는 역할을 수행한다. 비즈니스 로직을 구현하는 영역으로 응용프로그램에서 데이터를 처리하는 부분이다. DB에 연결하고 데이터를 추출하거나 CRUD 등의 작업을 수행한다. 상태의 변화가 있을때, 컨트롤러와 뷰에 통보해 후속 조치 명령을 받을 수 있게 된다. 뷰나 컨트롤러에 대해서 어떤 정보도 알지 말아야한다.Controller 클라이언트의 요청을 받았을 때, 그 요청에 대해 실제 업무를 수행하는 모델컴포넌트를 호출한다. 또한 클라이언트가 보낸 데이터가 있다면..." }, { "title": "TDD", "url": "/posts/TDD/", "categories": "study, design pattern", "tags": "tdd", "date": "2022-01-17 00:30:00 +0900", "snippet": "TDD 란?Test-Driven Development의 약자로, 테스트가 코드 작성을 주도하는 개발방식이며, 매우 짧은 개발 사이클의 반복에 의존하는 소프트웨어 개발 프로세스이다.우선 개발자는 요구되는 새로운 기능에 대한 자동화된 테스트케이스를 작성하고, 해당 테스트를 통과하는 가장 간단한 코드를 작성한다. 일단 테스트를 통과하는 코드를 작성하고, 리팩토링하는 과정을 거치는 것이다.장점코드를 작성하기 전에 보다 요구사항에 집중테스트를 작성하기 전에 개발자는 해당 기능의 요구사항과 명세를 분명히 이해하고 있어야한다. 이는 use..." }, { "title": "RESTful API", "url": "/posts/RESTful-API/", "categories": "study, web", "tags": "rest", "date": "2022-01-16 23:00:00 +0900", "snippet": "REST란, REprensentational State Transfer의 약자이다. 따라서, RESTful API는 REST의 기본 원칙을 잘 지킨 API를 의미한다.REST는 하나의 아키텍쳐로, Resource Oriented Architecture이다. API 설계의 중심에 자원(resource)가 있고, HTTP Method를 통해 자원을 처리하도록 설계하는 것이다. www와 같은 분산 하이퍼 미디어 시스템을 위한 소프트웨어 아키텍처의 한 형식으로, 자원을 정의하고 자원에 대한 주소를 지정하는 방법 전반에 대한 패턴RE..." }, { "title": "9935 문자열 폭발", "url": "/posts/9935-%EB%AC%B8%EC%9E%90%EC%97%B4-%ED%8F%AD%EB%B0%9C/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2022-01-15 02:00:00 +0900", "snippet": "매우 어렵게 구현하여 풀긴하였으나, 스택을 사용하면 매우 직관적으로 편하게 구현할 수 있었다.스택을 사용하여 문제를 푼 경험이 적어서 스택을 떠오르지 못한 것 같다스택을 사용한 문자열 문제를 몇문제 풀어봐야할 것 같음내 풀이 : 빡센 구현알고리즘 앞에서부터 검사하다가 폭발문자열과 일치하는 문자열이 발견되면, 그 문자열을 폭파시키고, 없어진 다음 문자열을 참조하도록 함. 예를들어, 문자열 인덱스 3~6이 폭파되었다면, to[3] = 7 을 저장하여, 검사하는 위치를 앞으로 당겨서 다시 검사할때, 폭파된 문자열을 점프하도록 한..." }, { "title": "함수형 프로그래밍", "url": "/posts/%ED%95%A8%EC%88%98%ED%98%95-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D/", "categories": "study, design pattern", "tags": "fp", "date": "2022-01-14 14:00:00 +0900", "snippet": "부수 효과가 없는 순수함수를 1급 객체로 간주하여 파라미터로 넘기거나 반환값으로 사용하는 것을 가능하도록 하여, 참조 투명성을 지키도록 하는 프로그래밍 패러다임순수 함수형 프로그래밍만으로 개발을 하기에는 무리가 있다. 따라서 적절히 조절해가면서 개발을 해나가자.요약함수형 프로그래밍의 가장 큰 특징 두 가지 immutable data first class citizen으로서의 functionimmutable data각 함수는 외부의 값을 변경하지 않고, side effect를 일으키지 않도록 하는 순수함수여야함.first-c..." }, { "title": "알고리즘 팁(WIP)", "url": "/posts/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%ED%8C%81-%EC%A0%95%EB%A6%AC/", "categories": "알고리즘", "tags": "", "date": "2022-01-14 14:00:00 +0900", "snippet": "구조 관련정렬된 배열의 앞 뒤로 새로운 요소를 삽입 해야되는 경우 필요한 길이의 2배길이의 배열을 선언한 다음, (start, to)로 인덱스를 트래킹해가면서 넣는다. 이때 start, to의 초깃값은 2배 길이의 배열의 딱 중간이어야함(그래야 최솟값만 들어가거나, 최댓값만 들어갈때도 배열이 터지지 않음)소수판별 floor(sqrt(n)) 까지만 검사해도 됨.라이브러리 관련string &lt; &gt; == + 등의 연산자 사용가능 .substr(index, size) 첫번째에는 시작 ..." }, { "title": "Object Oriented Programming", "url": "/posts/oop/", "categories": "study, design pattern", "tags": "oop", "date": "2022-01-13 14:00:00 +0900", "snippet": "OOP는 너무 거대한 개념이라 다 다루지는 못하지만 간단히만 알아보자간단한 설명OOP : 중심적 프로그래밍 패러다임. 현실 세계의 사물들을 객체라고 보고, 그 객체로부터 개발하고자 하는 애플리케이션에 필요한 특징들을 뽑아와 프로그래밍하는 것(추상화)장점 OOP로 작성한 코드는 재사용성이 높다. 자주 사용되는 로직을 라이브러리로 만들어두면 개발로드가 줄어들고, 신뢰성도 확보가 됨 라이브러리를 각종 예외상황에 맞게 잘 구현하면, 개발자가 사소한 실수를 해도 에러를 컴파일 ..." }, { "title": "React v18 주요 변경점", "url": "/posts/reactv18/", "categories": "study, react", "tags": "react", "date": "2022-01-10 00:20:00 +0900", "snippet": "React v18의 정식 출시가 코앞에 있고, 가장 큰 변경점 중 하나가 서버사이드 렌더링에 관한 내용이라는 소식을 듣고 마침 SSR 에 대해 흥미가 있던 터라 한번 React v18의 주요 변경점이 뭔지 공부해보기로 했다.먼저 기존의 React v17을 v18로 마이그레이션 하는 것은 문제 없다고 한다. 리액트 팀에서 이 부분을 특히 신경써서 만들었다고 하니 믿어도 될 것같다.Automatic batching먼저 리액트의 배치에 대해 알아야한다.Batching리액트에서 더 나은 성능과 예상치않는 버그의 방지를 위해 여러 개의..." }, { "title": "좋은 코드란 무엇인가?", "url": "/posts/%EC%A2%8B%EC%9D%80%EC%BD%94%EB%93%9C%EB%9E%80/", "categories": "study, clean code", "tags": "", "date": "2022-01-06 02:00:00 +0900", "snippet": "좋은 코드란 무엇일까?일반적으로 좋은 코드라고 하면 다음 세가지를 얘기한다. 읽기 좋은 코드 테스트가 용이한 코드 중복이 없는 코드그럼 왜 위 세가지가 갖춰진 코드를 좋은 코드라 하는 걸까? 그리고 어떻게 이 세가지를 지키며 코드를 작성할수 있을까?왜 좋지 않은 코드가 생산되는가?우리는 항상 좋은 코드를 작성하기 위해 노력한다.하지만 언제나 이것을 놓치는 시점이 존재하기에 좋지 않은 코드가 나오게 된다.그럼 어떠다가 놓치게 되는 것일까?우리가 마주치는 상황 이미 운영중인 서비스의 CS 대응하며 수정 변경된 요구사항, ..." }, { "title": "css 선택자", "url": "/posts/css%EC%84%A0%ED%83%9D%EC%9E%90/", "categories": "study, css", "tags": "", "date": "2022-01-05 14:00:00 +0900", "snippet": "*아무 표시 없이 사용하면 페이지에 있는 모든 요소가 대상* { margin : 0; padding : 0;}특정 요소의 모든 자식 요소에 적용할 수도 있음#container * { border : 1px solid black;}주의점 : 남발할 시 성능저하를 불러일으킴#Xid를 대상으로 삼음#container { width : 960px; margin : auto;}주의점 : id는 유연성이 부족하기 때문에, 꼭 id를 사용해야하는지 자문하자. 따라서 찾기 어려운 요소에만 id를 사용하자.Xcla..." }, { "title": "redux-saga concepts", "url": "/posts/redux-saga/", "categories": "study, react", "tags": "redux", "date": "2022-01-02 18:19:26 +0900", "snippet": "Declarative Effectsimport { takeEvery } from "redux-saga/effects";import Api from "./path/to/api";function* watchFetchProducts() { yield takeEvery("PRODUCTS_REQUESTED", fetchProducts);}function* fetchProducts() { const products = yield Api.fetch("/products&quo..." }, { "title": "generator 문법", "url": "/posts/generator/", "categories": "study, javascript", "tags": "javascript", "date": "2022-01-02 18:19:26 +0900", "snippet": "Generator 문법 자바스크립트의 기능이며 Redux-saga의 핵심 기능이다. 함수를 특정 구간에 멈춰놓고, 원할때 다시 돌아가게 할 수 있음. 또 반환을 여러번 할수 있다.예시function* generate() { yield 1; yield 2; yield 3; return 4;}const generator = generate(); 제너레이터 함수를 호출 하면, 제너레이터 객체를 반환한다. 이 객체에 있는 메서드인 .next()를 호출하면 yield 한 값을 반환하고 코드의 흐름을 멈춤.generator..." }, { "title": "Decision Tree를 이용한 서울 미세먼지 예측 모델", "url": "/posts/%EB%AF%B8%EC%84%B8%EB%A8%BC%EC%A7%80%EC%98%88%EC%B8%A1%EB%AA%A8%EB%8D%B8/", "categories": "projects", "tags": "", "date": "2022-01-02 18:00:00 +0900", "snippet": "소스코드 깃허브 링크모델 소개 중국 도시별 미세먼지 PM10 농도와, 서울의 기온, 풍속, 습도를 가지고, 서울의 하루 뒤 미세먼지 PM10 농도를 예측하는 모델 서울의 기온, 풍속, 습도는 현재(중국 미세먼지 데이터 날짜와 일치)와 다음날(서울 미세먼지 데이터 날짜와 일치) 데이터를 모두 넣음. 학습할 중국의 도시는 공업이 발달한 도시로 선정하였으며, 다음과 같이 총 10개 시를 선정 베이징, 항저우, 청도, 충칭, 칭다오, 난징, 톈진, 쑤저우, 우한, 상하이 데이터..." }, { "title": "자바스크립트 데코데이터 패턴", "url": "/posts/%EB%8D%B0%EC%BD%94%EB%A0%88%EC%9D%B4%ED%84%B0%ED%8C%A8%ED%84%B4/", "categories": "study, javascript", "tags": "javascript", "date": "2021-12-31 16:31:00 +0900", "snippet": "데코레이터 패턴 하나의 코드를 다른 코드로 래핑하거나 javascript 함수를 래핑하는 방법 동일한 클래스의 다른 객체에는 영향을 주지 않고, 정적/동적으로 개별 객체에 동작을 추가할 수 있는 디자인 패턴이다. 문법 let variable = function(object) { object.property = 'characteristic';} @variableclass GFG { }console.log(GFG.property); 현재는 아..." }, { "title": "14658 하늘에서 별똥별", "url": "/posts/14658/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-12-30 14:00:00 +0900", "snippet": "알고리즘 한 점에서 x축으로의 범위를 정하고, 다른 점에서 y축으로의 범위를 정하는 방식으로 모든 두개의 점 조합으로 트래펄린을 설치한다.코드#include &lt;iostream&gt;using namespace std;int n, m, l, k;pair&lt;int, int&gt; s[110];int max(int a, int b) { return a &lt; b ? b : a;}int main() { cin &gt;&gt; n &gt;&gt; m &..." }, { "title": "4485 젤다", "url": "/posts/4485-%EC%A0%A4%EB%8B%A4/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-12-27 23:00:00 +0900", "snippet": "알고리즘 문제를 해석하면, 한 점에서 다른 한 점까지 가는데 최소경로를 찾는 문제로 해석할 수 있다. 따라서 다익스트라 알고리즘을 적용하면 쉽게 풀수있다. 이때 한 칸에 저장된 값은, 그 칸으로 접근하는 경로의 길이로 치환하여 품. 여담 문제 내에 다익스트라라는 꽤나 많은 힌트가 있었으나, dfs에 사로잡혀서 오래 걸렸다. 2차원 배열을 대상으로 다익스트라를 구현한 문제는 처음이었기에 꽤나 신선했다.코드#include &lt;iostream&gt;#include &lt;qu..." }, { "title": "1613 역사", "url": "/posts/1613-%EC%97%AD%EC%82%AC/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-12-26 23:00:00 +0900", "snippet": "알고리즘BFS 입력으로 들어온, 알려진 전후관계로 그래프를 만듦. 체크를 해야하는 원소 두개가 들어오면, 먼저 첫번째 거에서 bfs를 시작하여 두번째거가 나오면 첫번째가 앞에 있는 것이므로 -1를 출력 찾지못하면, 두번째에서 첫번째를 BFS로 찾음. 찾으면 1을 출력. 찾지못하면, 0 을 출력 시간 복잡도 : O(NM) 사건의 개수 - N, 찾아야하는 선후관계 - M N은 최대 400, M은 최대 50000임. 대략 2천만번의 연산 필요. ..." }, { "title": "react 작동원리부터 tailwindcss 사용까지", "url": "/posts/react-%EC%9E%91%EB%8F%99%EC%9B%90%EB%A6%AC%EB%B6%80%ED%84%B0-tailwindcss-%EC%82%AC%EC%9A%A9%EA%B9%8C%EC%A7%80/", "categories": "study, react", "tags": "", "date": "2021-12-26 18:19:26 +0900", "snippet": "개요 yourlist 웹 리뉴얼 프로젝트에서 tailwind css를 사용하기로 결정하였고, create-react-app을 사용하여 리액트앱을 만든 후, tailwind css를 추가하였다. 이때 tailwind css는 먼저 css 파일을 컴파일 하고, 컴파일 이후의 css 파일을 import 하는 방식으로 동작한다. 따라서 매번 리액트 앱을 실행하기 전에 새로 컴파일을 해줘야하는데, create-react-app에 tailwindcss 추가하는 가이드를 따라서 하면, 런타임 중에 세이브만으로도 서버를 껐다가 킬 필요..." }, { "title": "flutter (rn 개발자를 위한 정리 4)", "url": "/posts/flutter-(rn-%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%A5%BC-%EC%9C%84%ED%95%9C-%EC%A0%95%EB%A6%AC4)/", "categories": "study, flutter", "tags": "", "date": "2021-12-23 02:00:00 +0900", "snippet": "Props ReactNative 에서 대부분의 컴포넌트는 매겨변수나 속성을 props로 전달함 Flutter에서는 매개변수가 있는 생성자에서 받은 속성을 final로 표시된 지역변수나 함수에 할당함 // Flutterclass CustomCard extends StatelessWidget { CustomCard({@required this.index, @required this.onPress}); final index; final Function onPress; @override Wi..." }, { "title": "flutter (rn 개발자를 위한 정리 3)", "url": "/posts/flutter-(rn-%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%A5%BC-%EC%9C%84%ED%95%9C-%EC%A0%95%EB%A6%AC3)/", "categories": "study, flutter", "tags": "", "date": "2021-12-22 21:00:00 +0900", "snippet": "Flutter 위젯ViewsView 컨테이너 React Native 에서는 View 가 컨테이너이고 Flexbox를 이용한 레이아웃, 스타일, 터치 핸들링, 접근성제어를 지원 Flutter에서는 Container나, Column, Row, Center같은 위젯 라이브러리의 핵심 레이아웃 위젯을 사용할 수 있음. 레이아웃 위젯 FlatList, SectionList ListView : 목록의 수가 적은 경우에 가장 적합함. 무거운 목록이거나 무한 스크롤 목록일때는 ListVie..." }, { "title": "flutter (rn 개발자를 위한 정리 2)", "url": "/posts/flutter-(rn-%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%A5%BC-%EC%9C%84%ED%95%9C-%EC%A0%95%EB%A6%AC-2)/", "categories": "study, flutter", "tags": "", "date": "2021-11-25 21:00:00 +0900", "snippet": "기본앱 생성 IDE 에서 생성하는 방법 커맨드라인에서 생성하는 방법$ flutter create &lt;projectname&gt;앱 실행 IDE에서 run 클릭 최상위 디렉토리에서 flutter run 입력importimport 'package:flutter/material.dart';import 'package:flutter/cupertino.dart'; // ios 위젯import 'package:flutter/widgets.dart';import '..." }, { "title": "flutter (rn 개발자를 위한 정리 1)", "url": "/posts/flutter-(rn-%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%A5%BC-%EC%9C%84%ED%95%9C-%EC%A0%95%EB%A6%AC)/", "categories": "study, flutter", "tags": "", "date": "2021-11-25 21:00:00 +0900", "snippet": "진입점main() { // 항상 최상단 앱의 진입점 main() 이 있어야함.}콘솔print("hello world")변수 dart 는 타입검사를 하는 언어 정적 타입 검사와 런타임 타입검사를 동시에 사용하며, 변수의 값이 변수의 정적타입과 항상 일치하는지 검사함. 타입 추론을 하기에 일부 타입표기는 생략가능String name = 'dart'; // 명시적 타입 선언var othername = 'dart'; //타입 추론 초기화하지 않은 변수는 null값을 가짐.va..." }, { "title": "LocateC", "url": "/posts/st-algorithm/", "categories": "projects", "tags": "", "date": "2021-11-25 18:19:26 +0900", "snippet": "개발 계기 코로나 전에 커피를 마시면서 자주 길거리를 걸어다녔는데, 항상 다 마시고 남은 빈 통만 들고 한참을 쓰레기통을 찾아다녔던 기억이 있었다. 그런데 학과 내에서 학교 생활에 도움을 줄 서비스를 개발하는 공모전을 하였고, 위 기억을 되살려서 학교 내 흡연장소, 쓰레기통의 위치를 알려주는 서비스를 개발해보기로 하였다.서비스 소개 교내에 위치한 흡연장소, 쓰레기통의 위치를 사진과 함께 확인 가능. 가장 가까운 곳에 위치한 흡연장소/쓰레기통을 안내 사용자는 관리자에게 서버에 등록되지않은 새로운 위치를 추가하도록 요청을..." }, { "title": "Yourlist", "url": "/posts/yourlist/", "categories": "projects", "tags": "", "date": "2021-11-24 14:38:44 +0900", "snippet": "서비스 소개 사용자가 원하는 유튜브 영상의, 원하는 부분만을 가지고 재생목록을 만들 수 있도록 하는 서비스 회원가입을 통해 회원이 될 수 있고, 회원은 재생목록을 무제한으로 만들 수 있지만, 비회면원 5개까지만 가능 i18n을 통한 글로벌 언어 제공 앱, 웹에서 모두 사용가능하도록 개발. 서비스 링크 web playstore새롭게 배운 점react-native, expo 처음으로 react-native와 react-native의 개발 플랫폼인 expo를 통해 앱개발을 하였다..." }, { "title": "flutter 첫 걸음", "url": "/posts/flutter-%EA%B3%B5%EB%B6%80/", "categories": "study, flutter", "tags": "", "date": "2021-11-23 23:00:00 +0900", "snippet": "1단계 : Starter Flutter app void main() =&gt; runApp(MyApp()); 처럼 화살표 함수 사용가능 한줄 함수에 화살표를 사용한다. 최상단 앱 : StatelessWidget을 상속받아 앱 자체를 위젝으로 만든다. Flutter에서는 정렬, 여백, 레이아웃 등 모든 것이 위젯임. Scaffold 위젯은, app bar, title, body 속성을 기본으로 제공함. 위젯은 build() 를 오버라이딩하여 하위 위젯을 어떻게 표현할지 명시 가능2단계 :..." }, { "title": "2629 양팔저울", "url": "/posts/2629-%EC%96%91%ED%8C%94%EC%A0%80%EC%9A%B8/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-11-18 14:00:00 +0900", "snippet": "알고리즘 모든 경우를 따지기 위해서 각 추가 구슬 반대쪽에 올라가는 경우, 저울에 안올라가는 경우, 구슬과 같은 쪽에 올라갈 경우를 모두 고려해야함 배낭문제를 응용하여, 추를 하나씩 모든 무게에 대해 검사를 한다. true이면 구슬 반대쪽 저울에 추만으로 만들 수 있는 무게라는 뜻. 현재 추를 사용하여 만들 수 있는 무게면 true표시 현재 추를 사용하지않고 이전 추로만으로도 만들 수 있는 무게면, 현재추를 구슬과 같은 저울에 놓고 만들 수 있는 무게도 true 표시 위..." }, { "title": "10986 나머지합", "url": "/posts/10986-%EB%82%98%EB%A8%B8%EC%A7%80%ED%95%A9/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-09-10 20:00:00 +0900", "snippet": "알고리즘 구간합 정의 : i ~ j 까지의 구간합 = (1~ j 까지의 구간합) - (1 ~ i -1 까지의 구간합) 위 정의에 따라 우리가 구해야하는 (i ~ j 까지의 구간합) % m = 0 은 ((1~ j 까지의 구간합) % m - (1 ~ i -1 까지의 구간합) % m) % m 과 같음 따라서 (1~ j 까지의 구간합) % m = (1 ~ i -1 까지의 구간합) % m 인 (i, j) 가 조건을 만족함. 코드#include &lt;iostream&gt;typed..." }, { "title": "12906 새로운 하노이탑", "url": "/posts/12906-%EC%83%88%EB%A1%9C%EC%9A%B4-%ED%95%98%EB%85%B8%EC%9D%B4%ED%83%91/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-09-08 20:00:00 +0900", "snippet": "알고리즘 평범하게 BFS하면 되는데, 방문 표시를 어떻게 하느냐가 이번 문제의 관건이다. 난 각 기둥의 문자열 사이에 “/” 라는 문자를 추가하여 상태를 구분짓고, 이것을 set에 저장하였다. 이렇게 상태를 구분지어본 것은 처음이라, 코드를 짜면서도 계속 내 코드를 의심했다 코드#include &lt;iostream&gt;#include &lt;queue&gt;#include &lt;set&gt;using namespace std;struct nod..." }, { "title": "9576 책 나눠주기", "url": "/posts/9576-%EC%B1%85-%EB%82%98%EB%88%A0%EC%A3%BC%EA%B8%B0/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-09-02 20:00:00 +0900", "snippet": "알고리즘 a, b에서 b의 순서대로 정렬 1 2 1 1 이런식으로 입력이 들어오면, 1 2에서 1에 넣고 1 1에선 넣을 수가 없기 때문에. 정렬된 (a, b)를 앞에서부터 검사하는데, a에서 b까지 1씩 증가하면서 가능하면 책을 배정하고, b까지 검사했는데도 남아있는 책이없으면 패스한다. 코드#include &lt;iostream&gt;#include &lt;list&gt;#include &lt;cstring&gt;#include &lt..." }, { "title": "expo push notification", "url": "/posts/paasta-noti/", "categories": "study, react-native", "tags": "", "date": "2021-09-02 16:00:00 +0900", "snippet": "paasta notification 회의 방법들 expo 서버로 보내는 방법 (https://docs.expo.dev/push-notifications/sending-notifications/) expo에서 제공하는 라이브러리를 사용하면 됨. but 자바 sdk를 제공하긴 하는데 이건 maven 기반이고, 현재 우리 프로젝트는 gradle 기반임 fcm, apns 에 직접 보내는 방법 (https://docs.expo.dev/pu..." }, { "title": "타입스크립트", "url": "/posts/typescript/", "categories": "study, typescript", "tags": "", "date": "2021-08-25 16:00:00 +0900", "snippet": "타입스크립트 연습 tsconfig.json compilerOptions:{ ​ “outDir” : “./dist” // 컴파일된 파일 저장. expo 에선 안해도 될듯 } jsx -&gt; tsx, js -&gt; ts 기본타입let mightBeUndefined: string | undefined = undefined; // string 일수도 있고 undefined 일수도 있음let nullableNumber: n..." }, { "title": "1918 후위표기식", "url": "/posts/1918-%ED%9B%84%EC%9C%84%ED%91%9C%EA%B8%B0%EC%8B%9D/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-08-24 21:00:00 +0900", "snippet": "알고리즘 괄호치기 / 는 앞에서부터 가까운 두 괄호를 묶음. 그다음 + - 로 마찬가지로 함. 양쪽에 모두 없으면 패스함 출력 여는괄호, 연산자는 스택에 넣기. 닫는 괄호가 나오면 여는괄호가 나올때까지 쭉 빼기. 알파벳은 그냥 출력. 코드#include &lt;iostream&gt;#include &lt;string&gt;#include &lt;stack&gt;using namespace std..." }, { "title": "expo 배포", "url": "/posts/expo-%EB%B0%B0%ED%8F%AC/", "categories": "study, react-native", "tags": "", "date": "2021-08-24 16:00:00 +0900", "snippet": "EXPO 앱 google playstore 배포 app.json 수정 expo build:android -t app-bundle google play app signing 이 선행되어야함. https://docs.expo.dev/distribution/app-signing/ keystore를 구글 개발자계정에서 받을 수 있는듯? https://support.google.com/googleplay/android-developer/answer/984..." }, { "title": "React-boilterplate 설명", "url": "/posts/react-boilerplate/", "categories": "study, react", "tags": "", "date": "2021-08-18 16:00:00 +0900", "snippet": "react-boilerplate 설명 react-boilerplate 라는, 리액트 프로젝트를 처음 시작할때 create-react-app을 대체하여 사용하기에 아주 좋아보이는 프로젝트가 있다. https://github.com/react-boilerplate/react-boilerplate 상당히 많은 초기 설정을 하는데, 너무 많아서 뭐가 뭔지 알아보기가 어렵다. 따라서 이 초기설정들을 설명해놓은 글이 있고, 이 포스트는 이 글을 보고 공부한 내용을 적은 것이다. ht..." }, { "title": "9251 LCS", "url": "/posts/9251-LCS/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-08-17 21:00:00 +0900", "snippet": "알고리즘https://velog.io/@emplam27/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EA%B7%B8%EB%A6%BC%EC%9C%BC%EB%A1%9C-%EC%95%8C%EC%95%84%EB%B3%B4%EB%8A%94-LCS-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-Longest-Common-Substring%EC%99%80-Longest-Common-SubsequenceLCS 알고리즘을 굉장히 잘 설명한 글이 있어 링크로 대체코드#include &lt;ios..." }, { "title": "2096 내려가기", "url": "/posts/2096-%EB%82%B4%EB%A0%A4%EA%B0%80%EA%B8%B0/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-08-12 21:00:00 +0900", "snippet": "알고리즘 내려올 수 있는 곳은 굉장히 한정되어있고, 순차적으로 가장 작은값을 or 가장 큰값을 구하기만 하면 되는 문제이기에 일차원 DP로 해결이 가능하다. 이런 종류의 문제를 “슬라이딩 윈도우” 라고 부르는 것 같다코드#include &lt;iostream&gt;using namespace std;int n, ansMax = -1, ansMin = 2000000000, cur[2][3], tCur[2][3];int min(int a, int b) { return a &lt; b ? a : b;}int..." }, { "title": "17387 선분교차 2", "url": "/posts/17387-%EC%84%A0%EB%B6%84%EA%B5%90%EC%B0%A8-2/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-08-08 21:00:00 +0900", "snippet": "알고리즘 양 선분이 평행할 경우, 또는 한 쪽이 x축에 평행하고, 다른 축이 y축에 평행할 경우엔 조건문을 통해 범위를 잘 검사하여 처리 나머지 경우엔 다음과 같은 방법을 사용한다. 첫번째 선분을 이루는 직선이 두번째 선분을 양분하고, 그 반대도 양분하면 두 선분은 교차한다. 이때 한 직선이 한 선분을 양분하는지 알아보는 식은 다음과 같다. f1 = (y21-y11)(x12-x11) - (x21-x11)(y12-y11) ..." }, { "title": "11779 최소비용구하기 2", "url": "/posts/11779-%EC%B5%9C%EC%86%8C%EB%B9%84%EC%9A%A9%EA%B5%AC%ED%95%98%EA%B8%B0-2/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-08-07 21:00:00 +0900", "snippet": "알고리즘 일반적인 다익스트라로 푸는데, 풀면서 이전에 어디서 왔는지를 기록한다. 다익스트라가 끝나고, 끝에서부터 왔던길을 되돌아가며 경로를 찾아낸다.코드#include &lt;iostream&gt;#include &lt;queue&gt;#include &lt;vector&gt;#include &lt;cstring&gt;#define INF 2000000000000typedef long long ll;using namespace std;int n, m, s, e, pa..." }, { "title": "1208 부분수열의 하1 2", "url": "/posts/1208-%EB%B6%80%EB%B6%84%EC%88%98%EC%97%B4%EC%9D%98-%ED%95%A92/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-08-06 21:00:00 +0900", "snippet": "알고리즘 수를 두개 집합으로 나눔. 나눈 집합 각각에 대해 모든 경우를 살펴봐서 각 집합 내부에서 s에 도달한 횟수를 세고, 만들 수 있는 수와 그 수가 나온 횟수를 센다. 위에서 센 횟수로, 양 집합에서 만들 수 있는 수가 a, b이고, 이 a, b가 각 집합에서 나온 횟수를 c, d라고 하면, a + b = s 일때, c * d만큼의 수를 전체 경우의 수에 더한다. 이때 save 배열을 set으로 구현한다면 메모리 사용량이 대폭 줄어든다.(각 집합이 최대 20개이니, 나올..." }, { "title": "1504 특정한 최단경로", "url": "/posts/1504-%ED%8A%B9%EC%A0%95%ED%95%9C-%EC%B5%9C%EB%8B%A8%EA%B2%BD%EB%A1%9C/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-07-26 22:00:00 +0900", "snippet": "알고리즘 1 -&gt; v1 -&gt; v2 -&gt; n 과 1 -&gt; v2 -&gt; v1 -&gt; n 중 어느것이 더 짧은지 확인하면 된다. 즉, 원점이 1일때, v1 일때, v2일때 다익스트라를 진행하고, 거기서 나온 거리로 위 거리를 계산하여 출력하면 된다.코드#include &lt;iostream&gt;#include &lt;queue&gt;#include &lt;vector&gt;#define INF 2100000000;..." }, { "title": "115653 구슬탈출4", "url": "/posts/15653-%EA%B5%AC%EC%8A%AC%ED%83%88%EC%B6%9C4/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-07-24 22:00:00 +0900", "snippet": "알고리즘 푸는 방법은 bfs지만, 구현이 더 까다로운 문제이다. visit은 빨간구슬, 파란구슬의 위치를 동시에 기록하여 두 구슬 모두 같은 자리로 다시 가지 않도록 한다. 각 방향으로 기우릴때는 일단 한쪽을 먼저 보낸다고 가정하고, 실제로 먼저 보내는지 검사한 후 아니라면 다른쪽을 먼저 보낸다. 한쪽이 구멍에 먼저 빠지면, (-1,-1) 위치로 보내어 충돌이 발생하지 않도록 한다.코드#include &lt;iostream&gt;#include &lt;list&gt;#define INF 20..." }, { "title": "12872 플레이리스트", "url": "/posts/12872-%ED%94%8C%EB%A0%88%EC%9D%B4%EB%A6%AC%EC%8A%A4%ED%8A%B8/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-07-23 00:00:44 +0900", "snippet": "알고리즘 dp를 사용 save[101][101] 로 메모이제이션을 위한 배열을 선언 한 곳에는 이제까지 들어간 새로운 곡의 수, 한 곳에는 현재 깊이 원리 m == 0 일때, 현재 깊이 d에서는 이전 곡의 구성이 어찌됐든 간에, 현재~끝에 나와야할 곡의 구성의 수는 같다. 하지만 m이 0이 아니고, 모든 곡이 나와야한다는 조건이 있으므로, 이전곡의 구성에 영향을 받는데, 이때 이전에 들어간 개별적인 곡의 수가 몇개였는지에 대해서만 생각해주면 된다. ..." }, { "title": "15989 1,2,3 더하기 4", "url": "/posts/15989-1,2,3-%EB%8D%94%ED%95%98%EA%B8%B0-4/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-07-10 21:00:44 +0900", "snippet": "알고리즘 save[0] = 1로 초기화 i : 1~3 에서 j : i~m 까지 save[j] += save[j-i] 를 반복 원리 i = 1에서는 1로만 쭉 더한 것(3 = 1 + 1 + 1) 경우만 따짐. i = 2에서는 1로만 더한 것에 2를 추가한 경우를 따진다. i = 3에서도 마찬가지. ex) m = 4일때 i = 1 ..." }, { "title": "1890 점프", "url": "/posts/1890-%EC%A0%90%ED%94%84/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-07-09 21:00:44 +0900", "snippet": "알고리즘 save[0][0] = 1 부터 시작해서, 모든 점을 순서대로 방문하며 자신의 위, 왼쪽 점들 중 자신에게로 올 수 있는 모든 점들을 save[i][j]에 저장한다.코드#include &lt;iostream&gt;typedef long long ll;using namespace std;int n, map[101][101];ll save[101][101];ll dp();int main() { cin &gt;&gt; n; for (int i = 0; i &lt; n; i++) for..." }, { "title": "11048 이동하기", "url": "/posts/11048-%EC%9D%B4%EB%8F%99%ED%95%98%EA%B8%B0/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-07-09 21:00:44 +0900", "snippet": "알고리즘 (1,1)에서부터 순서대로 모든 점을 대상으로 visit (i -1, j), (i, j-1), (i-1,j-1) 이 가지고 있는 점 중 가장 큰 점을 visit(i,j) 에 저장한다.코드#include &lt;iostream&gt;using namespace std;int visit[1010][1010], n, m, map[1010][1010];int max(int a, int b) { return a &gt; b ? a : b;}int main() { ios::sync_with_stdio(fa..." }, { "title": "14529 Where's Bessie", "url": "/posts/14529-Where's-Bessie/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-07-08 21:10:44 +0900", "snippet": "알고리즘 그냥 조건에 맞춰서 풀면 되는 문제이다. 가능한 모든 사각형에 대해 검사하고, pcl을 뽑아낸 다음에, 마지막으로 ‘다른 pcl에 포함되는 pcl’을 제거하고 출력하면 된다. ‘한 색은 연속된 지역이 하나이고, 다른 색은 두개 이상이어야한다. ‘ 라는 조건은 다음과 같이 하면된다. 인접한 같은 색에 대해서만 BFS를 진행하고, BFS 진행된 총 수 t를 센다. t가 현재 지역에서 그 색의 수와 같으면 첫번째 조건이 만족되었다고 표시, 그것이 아니라면 두번째 조건이 만족했다고 표시하는데, ..." }, { "title": "브라우저 동작원리", "url": "/posts/%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC/", "categories": "study, web", "tags": "browser", "date": "2021-07-07 21:10:44 +0900", "snippet": "기본 구조 사용자 인터페이스 브라우저 엔진 : 사용자 인터페이스와 렌더링 엔진 사이의 동작을 제어 렌더링 엔진 : 요청한 콘텐츠를 표시. HTML 을 요청하면 HTML과 CSS를 파싱하여 화면에 표시함. 통신 UI 백엔드 : 콤보박스와 창 같은 기본적인 장치를 그림. 플랫폼에서 명시하지 않은 일반적인 인터페이스로서 OS 사용자 인터페이스 체계를 사용함. 자바스크립트 해석기 자료저장소 : Localstorage, SessionStorage, Cookie 크롬은 각 탭마다 별도의 렌더링 엔진 인스턴스를 유지함. 즉,..." }, { "title": "10875 뱀", "url": "/posts/10875-%EB%B1%80/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-07-07 21:10:44 +0900", "snippet": "알고리즘 뱀이 성장하면서 생기는 가로직선, 세로직선을 따로 저장. 새롭게 생기는 직선이 이전에 저장된 직선에 걸리는지 확인. 이때, 직선은 정렬하여 뱀이 나아가는 방향에서 가장 가까운 직선에 걸리도록 하자.여담 깔끔하게 구현하기에 매우 애를 먹었고, 가능하지도 않았다. 그만큼 분리하여 생각해야하는 조건들이 많고, 그렇기에 실수를 할 가능성이 매우 크다. 알고리즘 자체를 간단하므로, 틀렸습니다가 뜬다면 실수를 하지 않았는지를 중점으로 찾아보면 될 것 같다.코드#include &lt;iostream&gt;#..." }, { "title": "9874 Wormholes", "url": "/posts/9874-Wormholes/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-07-05 19:10:44 +0900", "snippet": "알고리즘 페어링 쌍을 만들고, 사이클이 있는지 확인하는 작업을 모든 페어링 쌍을 대상으로 하면 됨. 헤멘 부분 사이클을 찾을때 같은 페어링쌍을 다시 방문하는게 아니라, 같은 점을 다시 방문하는지 확인해야함. check 를 언제 초기화해야하는지 헷갈림 -&gt; 확실히하려면 매 시작점마다하면 됨. 여담 출척 Olympiad&gt; USA Computing Olympiad 인데, 요즘 이 출처의 문제들을 풀면서 느낀 게, 이 출처의 문제들은 내용자체는 간단한데, 예외 또는 특수..." }, { "title": "5827 What's Up With Gravity", "url": "/posts/5827-What's-Up-With-Gravity/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-07-04 21:10:44 +0900", "snippet": "알고리즘 BFS 사용 시작할때 C는 떨구고 시작. bfs를 진행하는데 왼쪽, 오른쪽, 중력 바꿔서 검사하고 떨군다. 떨어지는 도중에 D를 만나면, 만났다고 표시 -&gt; 끝나고 바로 ans 업데이트 이때 visit은 해당 위치에서 해당 중력일때 최소 flip을 저장함 나는 이동후 떨구기 전과 떨군 후 두가지만 visit 표시했지만, 더 할 수도 있을 거 같음. 시간 더 줄이기 우선순위 큐 사용하여, flip 횟수가 적은 것을 우선하기 ..." }, { "title": "React native 기초", "url": "/posts/react-native-%EA%B3%B5%EB%B6%80/", "categories": "study, react-native", "tags": "", "date": "2021-07-03 19:30:44 +0900", "snippet": "개발환경 만들기 필요한 것 : 안드로이드 스튜디오, expo, 안드로이드 emulator npm install -g expo-cli 안드로이드 스튜디오 설치 -&gt; 설치 후 sdk도 설치 안드로이드 스튜디오을 키고, configuration에 들어가서 AVD manager을 누르고, create virtual device를 눌러 가상머신 만들기 프로젝트 시작 expo init [프로젝트명] expo start 하면 실행됨 아까 만든 가상머신을 실행시키고, run on and..." }, { "title": "14466 소가 길을 건너간 이유 6", "url": "/posts/14466-%EC%86%8C%EA%B0%80-%EA%B8%B8%EC%9D%84-%EA%B1%B4%EB%84%88%EA%B0%84-%EC%9D%B4%EC%9C%A0-6/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-07-02 21:10:44 +0900", "snippet": "알고리즘 모든 소를 대상으로 ‘길’이 있는 길로 다니지 말고 BFS를 함 다른 소를 만날때마다 count를 1씩 증가시키고, 한 소의 bfs가 다 끝나면 n - count를 ans에 저장 ans / 2를 출력코드#include &lt;iostream&gt;#include &lt;list&gt;#include &lt;cstring&gt;#include &lt;vector&gt;using namespace std;int n, k, r, map[101][101];list..." }, { "title": "16639 괄호 추가하기 3", "url": "/posts/16639-%EA%B4%84%ED%98%B8-%EC%B6%94%EA%B0%80%ED%95%98%EA%B8%B0-3/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-06-30 23:30:44 +0900", "snippet": "알고리즘 브루트포스로 함. 서로 이웃한 정수끼리 연산을 하는 조합을 하는데, 모든 조합을 전부 다 돌린다. 숫자는 최대 10개이고, 브루트포스로 했을때 O(n!) 이므로, 충분히 시간안에 돌릴 수 있다. 코드#include &lt;iostream&gt;using namespace std;long long N, num[10];char op[10];long long calculate(int deep);long long max(long long a, long long b) { retur..." }, { "title": "18500 미네랄 2", "url": "/posts/18500-%EB%AF%B8%EB%84%A4%EB%9E%84-2/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-06-29 19:38:44 +0900", "snippet": "알고리즘 2933 미네랄과 상당히 유사한 문제이지만, “분리된 클러스터의 각 열중 맨 아래 부분이 아닌 부분이 다른 클러스터 위에 떨어질 수 있다”라는 조건을 하나 더 생각해야함. 막대를 던지고 클러스터가 분리되면 바닥에 닿았는지 판단. 닿지 않았다면 아래로 내리는데, 내려갈 수 있는 미네랄을 다 검사. 가장 적게 내려갈 수 있는 거리만큼 클러스터를 내림.코드#include &lt;iostream&gt;#include &lt;list&gt;#include &lt;algorithm&am..." }, { "title": "10021 watering the field", "url": "/posts/10021-watering-the-field/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-06-24 19:38:44 +0900", "snippet": "알고리즘 크루스칼 모든 필드 사이의 거리를 계산하고, 양 쪽 필드의 번호와 거리를 벡터에 저장한다. 거리순으로 정렬하고, 크루스칼 알고리즘 시행 이때, 단순히 union-find 알고리즘으로 하면 find에서 시간초과가 난다. 따라서 set을 활용해서 크루스칼을 하면 된다.헤맨 지점 set을 이용해서 할때, 서로 다른 집합이었다가 합쳐질때, 하나만 옮기면 안된다. 그 set에 속해있는 모든 점을 다 옮겨야함 꼭 첫번째 집합으로 합쳐지지 않는다. 따라서, 모든 점들이 같은 집합에 있는지 하나하나 확인해봐야한다.코드#..." }, { "title": "5213 과외맨", "url": "/posts/5213-%EA%B3%BC%EC%99%B8%EB%A7%A8/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-06-19 19:38:44 +0900", "snippet": "알고리즘 BFS로 하나씩 찾아갈 수 있지만, 나는 연결되어있는 타일을 그래프로 표현한 다음 BFS를 하였다. BFS를 할땐, visit을 -1로 초기화하고, 이전에 온 곳을 기록하는 형식으로 하자. 그러면 마지막에서부터 온곳을 탐색하며 가서 첫번째 타일까지 가면 된다. 코드#include &lt;iostream&gt;#include &lt;vector&gt;#include &lt;list&gt;#include &lt;cstring&gt;usi..." }, { "title": "Graph ql front", "url": "/posts/GraphQL-front/", "categories": "study, graphql", "tags": "", "date": "2021-06-19 12:35:44 +0900", "snippet": "설치yarn add @apollo/react-hooks apollo-boost graphql사용법 GraphQL API로 요청을 보낼때는, 요청문을 axios나 fetch를 사용하여 POST 메소드로 보내야함. 하지만 Apollo를 사용하면, 위와같이 하지 않아도 된다. 먼저, Apollo client를 만들어야함. Apollo client 셋업다음과 같이, apollo.js 파일을 생성하고, client를 만들어주자. uri에는 graphQL 서버의 주소가 들어가면 된다.// s..." }, { "title": "Graph ql back", "url": "/posts/GraphQL-back/", "categories": "study, graphql", "tags": "GraphQL", "date": "2021-06-19 12:35:44 +0900", "snippet": "Qraph QL 이 해결할 수 있는 문제 over-fetching : 요청한 것보다 많이 보내줌. ex) username만 필요한데, 프로필 사진도 보냄 under-fetching : 하나의 작업을 위해 여러 요청을 보내게 하는 것. QraphQL에는 URL이 존재하지 않음. 하나의 엔드포인트만 있고, 그곳으로 쿼리를 보내면, 쿼리에 맞춰서 알아서 데이터를 보내준다. 즉, 정확히 요청한 데이터만 보내준다. 시작https://www.npmjs.com/package/graphql-..." }, { "title": "3108 logo", "url": "/posts/3108-logo/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-06-16 12:35:44 +0900", "snippet": "알고리즘 겹치는 사각형을 각기 다른 집합으로 분류 + (0,0) 점이 속하는 집합이 있는지 확인 앞에서부터 겹치는 사각형을 확인하고, 겹치면, 큐의 맨 앞에 넣어서 DFS 방식으로 집합을 분류 매번 모든 사각형을 검사하는데, 이미 분류된 사각형은 다시 검사하지 않아도 됨. 이때 (0,0,0,0)인 사각형(사실은 점)을 1개 더 저장하여, 검사할때 이 점도 검사하도록 하여, 클래스가 분류되면, 원점이 속하는 집합이 있는거고, 분류되지 않으면 없는 것이니, 이것으로 최종 결과를 계산함.코드#include &lt;io..." }, { "title": "1039 교환", "url": "/posts/1039-%EA%B5%90%ED%99%98/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-06-13 19:38:44 +0900", "snippet": "알고리즘 DP 사용. bottom-up 방식으로 끝까지 간 후, 하나씩 올리면서 가장 큰 걸 반환 0&lt;= i &lt; j &lt;= M 일때, array[i] == 0 이고, j == M이면 바꾸지 않음. 10 이하는 무조건 -1, 나머지는 0이 반환되면 한번도 끝까지 도달하지 못한 것이므로 K번 바꾸기 연산이 불가능한 것. 즉, -1 출력코드#include &lt;iostream&gt;#include &lt;cstring&gt;#include &lt;alg..." }, { "title": "Junctions/Seoul 2021 해커톤", "url": "/posts/junction_portfolio/", "categories": "projects", "tags": "", "date": "2021-05-26 21:38:44 +0900", "snippet": "Junctions/Seoul 2021 해커톤 Junction 은 핀란드에서 시작된 국제 해커톤으로, 2박 3일동안 진행된다. Autocrypto, Microsoft, SIA, AWS game tech 4개의 기업이 파트너로 참가하고, 참가자들은 이 4개의 기업 중 하나를 자신이 참가할 track으로 선택하여 참여한다. 수상은 track 별로 1,2,3등과 전체 우승으로 나뉜다. track 별 수상자는 기업의 관련자가 뽑으며, 전체 우승은 참가자들의 투표로 결정된다.역할 및 결과 우리팀 Hippy는 AWS game tec..." }, { "title": "로티(lottie) 애니메이션 적용", "url": "/posts/lottie_animation/", "categories": "study, css", "tags": "", "date": "2021-05-24 21:38:44 +0900", "snippet": "lottie 애니메이션Junction 해커톤을 하며 lottie 애니메이션을 접하게 되었다.간단히 코드로 불러올 수 있으며, json 파일로 불러올 경우 색상 변경 등의 커스터마이징도 가능하여 활용도가 높다.불러오는 법(react 기준) npm install @lottiefiles/react-lottie-player 다음 코드 삽입 &lt;Player src={fireVideo} background="transparent" speed="1" style..." }, { "title": "CSS 단위", "url": "/posts/CSS_unit/", "categories": "study, css", "tags": "css", "date": "2021-05-24 21:38:44 +0900", "snippet": "em : 부모의 단위에 배수를 더하는 것.body {​ font-size:14px;}div {​ font-size:1.2em;}면 div엔 16.8px로 들어간다.이때 부모의 크기에서 배수를 더하는 거라서, 자식마다 em을 써서 내려가면 크기는 계속 배수로 증가하게 된다.remroot의 단위에 배수를 더하는 것. 최상위 태그에 대해서 배수로만 구하는 거라, 자식의 깊이가 깊어져도 크기가 기하급수적으로 커지지 않는다.vh : viewport height, vw : viewport width뷰포트의 높이값과 너비값에 맞추어 사용한..." }, { "title": "CSS position 에 관하여", "url": "/posts/CSS_position/", "categories": "study, css", "tags": "", "date": "2021-05-24 21:38:44 +0900", "snippet": "static 기본 속성 원래 있어야할 위치에 있다. top, left, right, bottom 으로 위치 조절 불가능relative static 일 때의 위치를 기준으로 조절 가능. 겹치는 element 가 있으면, z-index로 결정한다.absolute position:static 을 가지고 있지 않은 부모를 기준으로 위치가 조절 된다.fixed 브라우저 기준으로 위치 조절 가능" }, { "title": "JunctionXSeoul 2021 후기", "url": "/posts/junction_seoul_2021_review/", "categories": "projects", "tags": "", "date": "2021-05-24 17:38:44 +0900", "snippet": "결과AWS game tech 트랙 우승!후기해커톤 첫 참여였지만 좋은 팀원들과 함께해서 트랙 우승을 이루어내 기뻤다.front-end 참여하여 메인 기능과는 보다는 디자이너님이 만들어주신 뷰 구현에 힘을 쏟았다.배운 것도 많았고, 더 공부할 것도 많이 보였다. 배운 것 tool 관련 trello 기본 사용법 figma 사용법 slack 을 통한 협업 lottie animation, json을 이용한 색상 및 동작 변형 ..." }, { "title": "16928 뱀과 사다리게임", "url": "/posts/16928/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-05-19 23:38:44 +0900", "snippet": "알고리즘 BFS로 가능한 경우를 찾아가면 된다. 그런데 주의점은, 뱀을 타는게 더 이득일 경우가 있다는 것만 주의하자 ex) 2 12 6030 9965 29 코드#include &lt;bits/stdc++.h&gt;using namespace std;int N, M, box[110];list&lt;pair&lt;int,int&gt;&gt; q;bool visited[110];int BFS();int main() { cin &gt;&..." }, { "title": "14238 출근기록", "url": "/posts/14238/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-05-18 23:38:44 +0900", "snippet": "알고리즘 DP 사용 앞에서부터 백트래킹처럼 조건이 맞을때 전진 만약 끝까지 갔으면 gotAns = true 설정하여, true이면 바로 종료. 만약 끝까지 안갔으면, save에 저장 이때 save[현재-2 문자][현재-1문자][남은 A][남은 B][남은 C] 로 함 현재 상태를 결정하는 건, 전전문자, 전 문자, 남은 A의 개수, 남은 B의 개수, 남은 C의 개수로 결정되므로. 사용한 A,B,C 의 개수가 같다면, 현재-2 이전의 문자 배열은 상관없기 때문이다. DP 함수 시작부분..." }, { "title": "2437 저울", "url": "/posts/2437/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-05-17 18:38:44 +0900", "snippet": "알고리즘 정렬 후 앞에서부터 검사 (이전까지 연속적으로 잴 수 있는 무게의 최댓값 + 1) 보다 현재 추가 더 무거우면 break. 최댓값 + 1이 잴수없는 최소의 값임.코드#include &lt;bits/stdc++.h&gt;using namespace std;int n, weights[1010];int main() { cin &gt;&gt; n; for (int i = 0; i &lt; n; i++) cin &gt;&gt; weights[i]; sort(&amp..." }, { "title": "15971 두 로봇", "url": "/posts/15971/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-05-17 18:38:44 +0900", "snippet": "알고리즘 BFS로 시작 정점부터, 이제까지 경로 중에 있었던 가장 큰 weight(bigest)와 이제까지의 weight를 모두 합한 값(length)을 저장해나가며 진행. 이때 visit을 표시할 때, true/false 값이 아닌, length을 저장. BFS를 진행할 땐, node에 저장된 length와 현재 로봇의 length와 그 통로의 weight를 합한 값이 작을때만 진행. 3을 위해서는 visit 배열을 큰 값으로 초기화해야함.코드#include &lt;bits/stdc++.h&gt;usin..." }, { "title": "Firebase/Function firestore, storage 쓰기, 읽기", "url": "/posts/firebase_function_firestore_storage/", "categories": "study, firebase", "tags": "firebase", "date": "2021-05-15 16:00:00 +0900", "snippet": "firebase cloud function에서 firestore와 storage의 이벤트트리거를 등록하는 예제는 firebase 문서에 자세히 설명되어있다.여기서는 cloud functions에서 firestore와 storage에 접근하여 읽고 쓰는 법을 간단한 코드로 보여줄 것이다. firestore firebase cloud function 가이드에 나온대로 admin을 초기화시켜준다. admin.firestore() 로 firestore 관련 함수를 호출하면 끝. const admin..." }, { "title": "Firebase/Function Puppeteer 사용하기", "url": "/posts/Firebase_cloudfunction_puppeteer/", "categories": "study, firebase", "tags": "firebase", "date": "2021-05-15 16:00:00 +0900", "snippet": "일반 노드에서의 환경과 같이 사용하면 된다. 홈페이지에 접속 후 스크린샷을 찍어 스토리지에 업로드하는 예시const puppeteer = require("puppeteer");const { Storage } = require("@google-cloud/storage");const storage = new Storage();router.post("/", (req, res) =&gt; { (async () =&gt; { const browser = awa..." }, { "title": "REACT/CORS 개발 환경에서, 외부 API 와 연결할때 쿠키가 생성되지 않는 문제", "url": "/posts/React_cors_problem_under_dev_mod/", "categories": "study, react", "tags": "", "date": "2021-05-14 16:00:00 +0900", "snippet": "이미 서버에 올라간 백엔드 API 와 로그인 작업을 하던 도중에, 백엔드에서 보내는 쿠키를 브라우저가 저장하지 않는 문제를 발견했다.찾아보니 CORS 위반으로 생기지 않는 것이라고 하였다.그럴때 해결방법은 두가지가 있다. package.json에 proxy 설정 추가//package.json{ ... "proxy" : "백엔드 주소" ...}위처럼 하면, proxy에서 통신을 하는 것이라고 하여 CORS 를 위반하지 않고 제대로 쿠키를 저장할 수 있다. setupP..." }, { "title": "노드 스터디 7장", "url": "/posts/node_ch7/", "categories": "study, node", "tags": "", "date": "2021-05-10 16:00:00 +0900", "snippet": "MySQL데이터베이스란 DBMS : 데이터베이스 관리시스템 RDBMS : 관계형 DBMS. 대표적으로 Oracle, mysql, mssql 등이 있음.Datagrip 사용 Datagrip : 데이터베이스를 위한 IDE. 학생인증을 하면 무료사용가능 프로젝트 생성 alt+insert 혹은 좌측 + 클릭 -&gt; 원하는 데이터베이스 클릭 좌측 하단에 download 클릭 이후 유저이름, 비밀번호 생성 후 ok 좌측 상단 초록 화살표 클릭하여 connect 오류 내용 : Server retu..." }, { "title": "노드 스터디 6장", "url": "/posts/node_ch6/", "categories": "study, node", "tags": "", "date": "2021-05-06 16:00:00 +0900", "snippet": "Express 로 웹서버 만들기6.1 익스프레스 프로젝트 시작const express = require("express");const app = express();app.set("port", process.env.PORT || 3000);app.get("/", (req, res) =&gt; { res.send("Hello, Express");});app.listen(app.get("port"), () =&gt; { con..." }, { "title": "노드 스터디 4장", "url": "/posts/node_ch4/", "categories": "study, node", "tags": "", "date": "2021-05-06 14:07:00 +0900", "snippet": "http 모듈로 서버 만들기4.1 요청과 응답 이해하기- req : 요청에 관한 정보- res : 응답에 관한 정보. - .writeHead : 헤더. 응답에 대한 정보를 기록하는 메서드. - 첫번째 인수 : 코드 - 두번째 인수 : 응답에 대한 정보를 알림. - .write : 본문(Body) - 첫번째 인수 - 클라이언트로 보낼 데이터. 여러번 호출해서 데이터를 여러개 보내도 됨. - .end : 응답을 종료함. 여기에도 데이터를 넣..." }, { "title": "8980 택배", "url": "/posts/8980/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-05-03 00:38:44 +0900", "snippet": "8980 택배풀이(그리디)1. 각 도착지별로 정보를 따로 관리함.(같이해도 되긴함)2. 도착지 1~N번까지 입력받은 C로 트럭 용량을 초기화함.3. 도착지 2번부터 정보를 하나씩 뽑아가며 검사. 시작지점~현재 도착지-1 까지에서의 최소값을 뽑고, 그만큼을 2의 배열에서 빼줌.(물론 그 최솟값이 현재 정보에서의 택배량보다 많으면 택배량으로 제한.)* 최솟값으로 뽑을 경우, 최대한 많이 담는 것이므로, 그것이 결국 최댓값이 됨.코드#include &lt;iostream&gt;#include &lt;algori..." }, { "title": "노드 스터디 5장", "url": "/posts/node_ch5/", "categories": "study, node", "tags": "", "date": "2021-04-29 18:07:00 +0900", "snippet": "패키지매지저package.json 설치한 패키지의 버전 관리 노드 프로젝트를 시작하기 전에 무조건 package.json부터 만들고 시작해야함. npm init 으로 프로젝트 생성하면 만들어짐. scripts npm 명령어를 저장해두는 부분. 저장된 명령어를 npm run 으로 실행한다. package-lock.json node_modules에 들어있는 패키지들의 정확한 버전과 의존관계가 담겨있음 npm install –save-dev [패키지] ..." }, { "title": "노드 스터디 3장", "url": "/posts/node_ch3/", "categories": "study, node", "tags": "", "date": "2021-04-29 14:07:00 +0900", "snippet": "Node 3장REPL 사용하기 노드 콘솔은 REPL이라하는데 이유는 Read : 입력한 코드를 읽고 Eval : 해석하고 Print : 결과물을 반환하고 Loop : 종료할 때가지 반복함. 터미널에 node를 입력함으로서 접속가능 간단한 명령어 수행JS 파일 실행 helloworld.js 라는 파일을 만들었으면 node helloworld 로 접근할 수 있음모듈로 만들기 노드는 코드를 모듈로 만들 수 있다는 점에서 브라우저의 자바스크립트와는 다르다. ..." }, { "title": "13303 장애물 경기", "url": "/posts/13303/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-27 12:17:00 +0900", "snippet": "13303 장애물 경기알고리즘1. 장애물을 x 좌표 순으로 정렬. 도착지점의 Y좌표와 걸린 길이(y축 이동만 고려)를 저장하는 set을 선언 후, {startY, 0} 를 추가2. 장애물을 하나씩 꺼내며, 그 장애물에 걸리는 원소만 set에서 꺼내고 하나씩 위로갔을때 거리와 아래로 갔을때 거리를 계산 이때, 꺼낸 것이 10개여도 장애물의 위 아래 각각에서 최소거리가 걸리는 것만 set에 다시 저장 -&gt; 즉, 10개 여도 2개만 저장됨.3. 2에서 검사한 원소는 set에서 제거한다.4. 모든 장애물을 상대로 하..." }, { "title": "9095 1,2,3 더하기", "url": "/posts/9095/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:38:44 +0900", "snippet": "9095 1,2,3 더하기코드#include &lt;iostream&gt;using namespace std;int tc;long long save[12];long long dp(int n);int main() { cin &gt;&gt; tc; while (tc--) { int n; cin &gt;&gt; n; cout &lt;&lt; dp(n) &lt;&lt; endl; }}long long dp(int n) { if (n &lt; 0) r..." }, { "title": "5373 Samsung sw test", "url": "/posts/5373/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:38:44 +0900", "snippet": "5373 Samsung sw test코드#include &lt;iostream&gt;#include &lt;cstring&gt;#define MAX_ROTATE 1000#define MAX_TESTCASE 100#define WHITE 100#define YELLOW 200#define RED 300#define ORANGE 400#define GREEN 500#define BLUE 600using namespace std;typedef struct Command { int location; //큐..." }, { "title": "3568 iSharp", "url": "/posts/3568/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:38:44 +0900", "snippet": "3568 iSharp코드#include &lt;iostream&gt;#include &lt;string&gt;using namespace std;string def, append;char input[130];void print();int main() { cin.getline(input, 130); int i = 0; while (input[i] != ' ') { def.push_back(input[i]); i++; } for (; input[i] != ';'; i++..." }, { "title": "2623 MusicPropram", "url": "/posts/2623/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:38:44 +0900", "snippet": "2623 MusicPropram코드#include &lt;iostream&gt;#include &lt;vector&gt;#include &lt;list&gt;#include &lt;cstring&gt;#define MAX_SINGER 1000#define MAX_PD 100using namespace std;typedef struct Singer { int beforeCount; bool isCheked; vector&lt;int&gt; next;} Sin..." }, { "title": "2473 Three Liquid", "url": "/posts/2473/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:38:44 +0900", "snippet": "2473 Three Liquid코드#include &lt;iostream&gt;#include &lt;algorithm&gt;#include &lt;set&gt;#define MAX_LIQUID 5000using namespace std;int liquid[MAX_LIQUID];int numOfLiquid;int threeLiquid[3];void getZerost();int abs(int a) { return a &lt; 0 ? (-1) * a : a;}void setThre..." }, { "title": "20058 Samsung sw test", "url": "/posts/20058/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:38:44 +0900", "snippet": "20058 Samsung sw test코드#include &lt;iostream&gt;#include &lt;list&gt;#include &lt;cstring&gt;#define MAX_WIDTH 64#define MAX_COMMAND 1000using namespace std;typedef struct Location { int row; int col;} Location;int map[MAX_WIDTH][MAX_WIDTH];int tempMap[MAX_WIDTH][MAX_WIDTH..." }, { "title": "20057 Samsung sw test", "url": "/posts/20057/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:38:44 +0900", "snippet": "20057 Samsung sw test코드#include &lt;iostream&gt;#define MAX_WIDTH 499using namespace std;int map[MAX_WIDTH][MAX_WIDTH];bool visit[MAX_WIDTH][MAX_WIDTH];int width, amountOfSand = 0;int ten, seven, five, two, one, rest;int getLostAmountSand();void setNum(int row, int col) { ten = (float)map..." }, { "title": "20056 Samsung sw test", "url": "/posts/20056/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:38:44 +0900", "snippet": "20056 Samsung sw test코드#include &lt;iostream&gt;#include &lt;cstring&gt;#include &lt;vector&gt;#define MAX_WIDTH 50using namespace std;typedef struct Fire { int mass; int direction; //0이 위, 시계방향으로 0~7 int speed;} Fire;typedef struct Cell { vector&lt;Fire&gt; fire;}..." }, { "title": "19238 Samsung sw test", "url": "/posts/19238/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:38:44 +0900", "snippet": "19238 Samsung sw test코드#include &lt;iostream&gt;#include &lt;list&gt;#include &lt;vector&gt;#include &lt;algorithm&gt;#define MAX_WIDTH 20#define MAX_CUSTOMER 400#define CUSTOMER 123using namespace std;typedef struct Taxi { int row; int col; int fuel;} Taxi;typedef..." }, { "title": "19236 Samsung sw test", "url": "/posts/19236/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:38:44 +0900", "snippet": "19236 Samsung sw test코드#include &lt;iostream&gt;#include &lt;cstring&gt;#define WIDTH 4#define SHARK 100#define BLANK -1using namespace std;typedef struct Fish { int num; int row; int col; int direction; // 위부터 반시계방향으로 1~8 bool isAlive;} Fish;int map[WIDTH][WIDTH];Fish fish[WIDTH*..." }, { "title": "1799 bishop", "url": "/posts/1799/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:38:44 +0900", "snippet": "1799 bishop코드#include &lt;iostream&gt;#include &lt;cstring&gt;#define MAX_N 10using namespace std;int map[MAX_N][MAX_N];bool tempMap[MAX_N][MAX_N] = { 0, };int width, maxCount = 0;void findMaxBishop(int diagonal, int count);bool check(int row, int col);int main() { cin &gt;&am..." }, { "title": "17825 Samsung sw test", "url": "/posts/17825/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:38:44 +0900", "snippet": "17825 Samsung sw test코드#include &lt;iostream&gt;#include &lt;vector&gt;using namespace std;typedef struct Map { int score; int next; int blueNext;} Map;int dice[10];Map map[33] = { {0, 1, -1},{2, 2, -1}, {4, 3, -1}, {6,4 , -1}, {8, 5, -1}, {10, 6, 21}, {12, 7, -1}, {14, 8, -1},..." }, { "title": "17822 Samsung sw test", "url": "/posts/17822/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:38:44 +0900", "snippet": "17822 Samsung sw test코드#include &lt;iostream&gt;#include &lt;cstring&gt;#include &lt;vector&gt;#define MAX_BOARD 50#define MAX_TURN 50using namespace std;int board[MAX_BOARD][MAX_BOARD];int numOfTurn, numOfBoard, numOfNum;int turn[MAX_TURN][3]; //xi, 0 or 1, kiint getSumOf..." }, { "title": "17779 Samsung sw test", "url": "/posts/17779/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:38:44 +0900", "snippet": "17779 Samsung sw test코드#include &lt;iostream&gt;#define MAX_WIDTH 21using namespace std;int map[MAX_WIDTH][MAX_WIDTH];int width;int minDifference();int getMin(int d1, int d2, int x, int y);int main() { cin &gt;&gt; width; for (int i = 0; i &lt; width; i++) for (int j = 0; j &..." }, { "title": "1748 수 이어쓰기 1", "url": "/posts/1748/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:38:44 +0900", "snippet": "1748 수 이어쓰기 1코드#include &lt;iostream&gt;using namespace std;int N;int main() { cin &gt;&gt; N; long long result = 0; int d = 1, count = 0; while (N / d != 0) { // 1~9 : 1 / 10~99 : 2 / 100~999 : 3 d *= 10; count++; } while (count != 0) { d /= 10; result += count * (N - d + 1)..." }, { "title": "17142 Samsung sw test", "url": "/posts/17142/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:38:44 +0900", "snippet": "17142 Samsung sw test코드#include &lt;iostream&gt;#include &lt;cstring&gt;#include &lt;vector&gt;#include &lt;list&gt;#include &lt;algorithm&gt;#define MAX_WIDTH 50#define WALL 1using namespace std;int map[MAX_WIDTH][MAX_WIDTH];typedef struct Virus { int row;..." }, { "title": "17140 Samsung sw test", "url": "/posts/17140/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:38:44 +0900", "snippet": "17140 Samsung sw test코드#include &lt;iostream&gt;#include &lt;algorithm&gt;#include &lt;cstring&gt;#include &lt;vector&gt;#define MAX_NUM 101using namespace std;typedef struct Cell { int value; int count; bool operator &lt; (Cell c) { if (count &lt; c.count..." }, { "title": "1629 곱셈", "url": "/posts/1629/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:38:44 +0900", "snippet": "1629 곱셈코드#include &lt;iostream&gt;using namespace std;long long A, B, C;long long result = 1;int main() { cin &gt;&gt; A &gt;&gt; B &gt;&gt; C; while (B != 0) { if (B % 2 == 1) { result = (result * A) % C; } A = (A * A) % C; B /= 2; } cout &lt;&lt; r..." }, { "title": "16236 Samsung sw test", "url": "/posts/16236/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:38:44 +0900", "snippet": "16236 Samsung sw test코드#include &lt;iostream&gt;#include &lt;list&gt;#define MAX_WIDTH 20using namespace std;typedef struct Location { int row; int col; int distance; bool operator &lt; (Location t) { if (distance &lt; t.distance) return true; else if (t.distance &..." }, { "title": "16235 Samsung sw test", "url": "/posts/16235/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:38:44 +0900", "snippet": "16235 Samsung sw test코드#include &lt;iostream&gt;#include &lt;algorithm&gt;#include &lt;vector&gt;#define MAX_WIDTH 10using namespace std;typedef struct Tree { int age; bool isAlive; bool operator &lt; (Tree t) { return age &lt; t.age; }} Tree;typedef struct Cell {..." }, { "title": "16234 Samsung sw test", "url": "/posts/16234/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:38:44 +0900", "snippet": "16234 Samsung sw test코드#include &lt;iostream&gt;#include &lt;list&gt;#include &lt;cstring&gt;#define MAX_WIDTH 50using namespace std;typedef struct Location { int row; int col;} Location;int map[MAX_WIDTH][MAX_WIDTH];int tempMap[MAX_WIDTH][MAX_WIDTH];int width, lowerBound,..." }, { "title": "15686 Samsung sw test", "url": "/posts/15686/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:38:44 +0900", "snippet": "15686 Samsung sw test코드#include &lt;iostream&gt;#define MAX_WIDTH 50#define MAX_CHIKEN 13using namespace std;typedef struct Location { int row; int col;} Location;int width, maxChiken, numOfHouse, numOfChiken;Location house[2 * MAX_WIDTH];Location chiken[MAX_CHIKEN];int minDistance = 1000..." }, { "title": "15685 Samsung sw test", "url": "/posts/15685/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:38:44 +0900", "snippet": "15685 Samsung sw test코드#include &lt;iostream&gt;#include &lt;vector&gt;#define MAP 101#define MAX_DRAGON 20#define MAX_GENERATION 10using namespace std;typedef struct Location { int row; int col;}Location;bool map[MAP][MAP];int dragon[MAX_DRAGON][4];int numOfdragon;void dragonCurb..." }, { "title": "15684 Samsung sw test", "url": "/posts/15684/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:38:44 +0900", "snippet": "15684 Samsung sw test코드#include &lt;iostream&gt;#define MAX_VERTICAL 10#define MAX_HORIZONTAL 30#define MAX_LINE 270using namespace std;typedef struct Ladder { bool left; bool right;} Ladder;typedef struct Location { int row; int col;} Location;Ladder ladder[MAX_HORIZONTAL][MAX_VERTICAL];..." }, { "title": "15683 Samsung sw test", "url": "/posts/15683/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:38:44 +0900", "snippet": "15683 Samsung sw test코드#include &lt;iostream&gt;#include &lt;cstring&gt;#define MAX_WIDTH 8#define WALL 6#define BLINDSPOT 0using namespace std;typedef struct CCTV { int row; int col; int direction; // 1 : 북, 2 : 동, 3 : 남, 4 : 서 int num;} CCTV;int height, width;int map[MAX_WIDTH][..." }, { "title": "15486 퇴사2", "url": "/posts/15486/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:38:44 +0900", "snippet": "15486 퇴사2코드#include &lt;iostream&gt;using namespace std;int N, T[1500000], P[1500000], DP[1500000];int dp(int deep);int max(int a, int b) { return a &gt; b ? a : b;}int main() { ios::sync_with_stdio(0); cin.tie(0); cin &gt;&gt; N; for (int i = 0; i &lt; N; i++) { cin &..." }, { "title": "14891 Samsung sw test", "url": "/posts/14891/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:38:44 +0900", "snippet": "14891 Samsung sw test코드#include &lt;iostream&gt;#include &lt;cstdio&gt;#define MAX_ROTATE 100#define LEFT 6#define RIGHT 2using namespace std;int command[MAX_ROTATE][2];int gear[4][8];int numOfRotate;int implementCommand();void implementSingleCommand(int gearN, int way);void rotat..." }, { "title": "14890 Samsung sw test", "url": "/posts/14890/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:38:44 +0900", "snippet": "14890 Samsung sw test코드#include &lt;iostream&gt;#include &lt;cstring&gt;#define MAX_N 100using namespace std;int map[MAX_N][MAX_N];int width;int need;int checkEveryArr();bool checkSingleArr(int arr[MAX_N]);int main() { cin &gt;&gt; width &gt;&gt; need; for (int i =..." }, { "title": "14889 Samsung sw test", "url": "/posts/14889/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:38:44 +0900", "snippet": "14889 Samsung sw test코드#include &lt;iostream&gt;#include &lt;cstring&gt;#define MAX_WIDTH 20using namespace std;int numOfPeople;int map[MAX_WIDTH][MAX_WIDTH];int minDifferenceBetweenTwoTeam(int arr[10], int len, int deep);int getSum(int arr[10]);int min(int a, int b);int main() { ..." }, { "title": "14888 Samsung sw test", "url": "/posts/14888/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:38:44 +0900", "snippet": "14888 Samsung sw test코드#include &lt;iostream&gt;#define MAX_NUM 11using namespace std;int num[MAX_NUM];int numOfNum;int operators[4]; // + - * /int operatorPerm[MAX_NUM - 1]; // 1 : +, 2 : -, 3 : *, 4 : /int min = 1000000001;int max = -1000000001;void getMinAndMax(int operatorPerm[MAX_NUM..." }, { "title": "14863 서울에서 경산까지", "url": "/posts/14863/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:38:44 +0900", "snippet": "14863 서울에서 경산까지알고리즘 (dp)1. dp를 평범하게 진행. dp배열은 dp[101][100001] 로 앞에는 현재 위치, 뒤에는 시간2. 시간이 맞으면 진행하되, 안맞으면 result 를 초기화해둔 -20000000이 그대로 있음.3. 0보다 낮으면, 현재 시간포함 0초까지 -20000000로 초기화. 같은 상태에 더 낮은 시간을 가지고 접근하는 걸 막음.다른 방법 (dp)1. dp[100005] 로 시간만으로 dp를 만듬.2. 첫번째 노드의 시간과 값으로 dp 초기화3. dp 배열을 t=k 부터 검사하여 값이 있..." }, { "title": "14503 Samsung sw test", "url": "/posts/14503/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:38:44 +0900", "snippet": "14503 Samsung sw test코드#include &lt;iostream&gt;#define MAX_WIDTH 50using namespace std;int direction;int location[2];int map[MAX_WIDTH][MAX_WIDTH];int height, width;int robot();bool changeDirection();int main() { cin &gt;&gt; height &gt;&gt; width; cin &gt;&gt; lo..." }, { "title": "14502 Samsung sw test", "url": "/posts/14502/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:38:44 +0900", "snippet": "14502 Samsung sw test코드#include &lt;iostream&gt;#include &lt;list&gt;#include &lt;cstring&gt;#define MAX_WIDTH 8using namespace std;typedef struct Cell { int row; int col;} Cell;int map[MAX_WIDTH][MAX_WIDTH];int tempMap[MAX_WIDTH][MAX_WIDTH];int width, height;int getMaxSaf..." }, { "title": "14501 Samsung sw test", "url": "/posts/14501/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:38:44 +0900", "snippet": "14501 Samsung sw test코드#include &lt;iostream&gt;#define MAX_DAY 15using namespace std;int day;int time[MAX_DAY];int proceeds[MAX_DAY];int DP(int d, int proceed);int max(int a, int b);int main() { cin &gt;&gt; day; for (int i = 0; i &lt; day; i++) cin &gt;&gt; time[i] ..." }, { "title": "13460 Gold 2", "url": "/posts/13460/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:38:44 +0900", "snippet": "13460 Gold 2코드#include &lt;iostream&gt;#include &lt;algorithm&gt;#include &lt;cstring&gt;#define MAX_WIDTH 10#define MAX_DEEP 10using namespace std;char map[MAX_WIDTH][MAX_WIDTH];int width;int height;int R[2];int B[2];int Hole[2];int minDeep;void getProperties();void findM..." }, { "title": "12865 평범한 배낭", "url": "/posts/12865/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:38:44 +0900", "snippet": "12865 평범한 배낭코드#include &lt;iostream&gt;using namespace std;int numOfObject, weight, DP[101][100001];int main() { cin &gt;&gt; numOfObject &gt;&gt; weight; for (int i = 1; i &lt;= numOfObject; i++) { int w, v; cin &gt;&gt; w &gt;&gt; v; for (int j = 1..." }, { "title": "9527 1의 개수세기", "url": "/posts/9527/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "9527 1의 개수세기알고리즘(수학)1. 2^n 마다 규칙이 있음2. f(n)을 2^n ~ 2^(n+1)-1 에서의 1의 개수라고 한다면, f(n) = 2^n + f(i) ( 0 &lt;= i &lt;= n-1 ) 이 성립한다.3. 그리고 들어오는 수 A,B에 대해 (1~B 까지의 1의 개수) - (1~A-1 까지의 1의 개수) 를 하면 값이 나온다.4. 2에서의 규칙에따라 end가 주어지면 0에서 end까지의 1의 개수를 구하는 함수를 선언한다. -&gt; 이때 end 미만의 최대 2^n의 n를 구한다...." }, { "title": "9466 Term project", "url": "/posts/9466/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "9466 Term project알고리즘 (DFS, 그래프)1. 각 학생들이 가리키는 학생을 저장한 student 배열, cycle에 몇명이 있는지 체크하는 cycleCcount 변수, 사이클이 형성됐는지 확인하는 cycleCheck bool형 배열. 검사중인 학생들을 저장하는 q배열2. 첫번째 학생부터 검사 -student가 -1 이면 검사안함. (이미 검사된 것) -검사가 시작되면, q에 넣고, cycleCheck 배열에 표시하고, start엔 자기, end엔 다음걸 가리키며, 그래프따라 dfs 시작 -그래프 따라가면서 ..." }, { "title": "9328 key", "url": "/posts/9328/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "9328 key알고리즘(BFS, 구현)1. 방문한 곳을 체크하는 check 이차원 배열, 키를 저장하는 key 배열, 닫힌문을 저장하는 closedDoor 벡터가 필요2. 먼저 키 배열은 26개의 원소로 이루어진 bool 자료형 배열로 선언. 각 알파벳에 해당하는 키가 들어오면 1을 저장한다.3. 방문가능한지 체크하는 checkCell 함수 선언 -&gt; 빈공간이면 true반환, 문서면 빈공간으로 전환 후 document 1증가하고 true반환 소문자면 열쇠 추가하고, 빈공간으로 전환 후 truw반환 대문자..." }, { "title": "9252 LCS2", "url": "/posts/9252/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "9252 LCS2알고리즘(최장 공통 부분 수열)1. LCS 알고리즘 사용하면 됨구현법1. 문자열 입력 받을 땐. cin.getline(char *c, size) 넣으면 된다.코드#include &lt;iostream&gt;#include &lt;cstring&gt;#define MAX_LENGTH 1000using namespace std;char a[MAX_LENGTH], b[MAX_LENGTH], lcs[MAX_LENGTH];int map[MAX_LENGTH + 1][MAX_LENGTH + 1]..." }, { "title": "7579 app", "url": "/posts/7579/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "7579 app알고리즘(배낭정리, DP)1. 배낭정리 응용. 최대 비용을 가방에 담을 수 있는 최대 무게로 보고, 메모리를 가치로 본다2. 이렇게 2차원 배열을 완성하고, 필요한 메모리 이상이며 비용이 최소인 칸을 판별하고 출력하면 된다.구현법1. 최대 앱 개수는 100개이므로, 101행, 앱 당 최대 비용은 100 이므로 10001열을 선언하여 구현하면 됨.2. 2차원 배열 완성하고나서는 첫번째 원소부터 검사하여, 메모리가 필요이상으로 있는데, 비용이 최소인 것을 찾으면 됨.코드#include &lt;iostream&..." }, { "title": "7453 four Integer's sum should be zero", "url": "/posts/7453/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "7453 four Integer’s sum should be zero알고리즘(정렬, 이분탐색)1. 앞 두개 정수의 합 배열, 뒤 두개 정수의 합 배열을 만듬.(배열 개수^2 가 합 배열의 길이)2. 두 배열을 정렬3. 앞 합 배열의 원소를 하나씩 빼며 검사 -&gt; 뒤 합 배열에서, upper_bound - lower_bound 값이, 현재 원소에서의 count 값.4. count 를 더하고 다음 원소 검사5. count 값을 long long 으로 선언구현1. lower_bound, upper_bound 로 몇개있는..." }, { "title": "6087 레이저통신", "url": "/posts/6087/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "6087 레이저통신알고리즘(BFS)1. visit 배열을 int로 선언. 이제까지 온 것들의 거울 개수를 저장2. 갈 수 잇고, visit에 저장된 거울 개수와 같거나 작으면 이동 시킴주의점같은 레이저에 도달하지 않기위해 설정한 row != start.row &amp;&amp; col != start.col 은 작동하지 않음-&gt; 같은 줄에만 있어도 애들이 안가기 때문-&gt; 따라서 다음처럼 조건문을 짜야함. row != start.row || col != start.col코드#include ..." }, { "title": "6064 카잉달력", "url": "/posts/6064/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "6064 카잉달력알고리즘(수학)1. n 을 M과 N으로 나누었을때 나온 수가 x와 y 이면 된다. 단, 나누었을때 나오는 수는 0~M-1 or N-1인데 문제에선 1~M, 1~N 까지이므로 이걸 고려해야한다.2. 하나씩 증가시켜가면서하면 너무 오래걸리니, M과 N 중 더 큰수를 더하는 수로 삼고, 그것과 연결된 수부터 시작한다(M-x, N-y) ex) 10 12 3 4 이면 4부터 시작하여 12씩 더함 각 단계의 수를 10과 12로 나누어서 3과 4가 나오면 가능한 것.3. 이때 M과 N이 같을 경우 x, y이 다르게 나오면..." }, { "title": "5014 스타트링크", "url": "/posts/5014/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "5014 스타트링크알고리즘 (BFS)- 직선으로의 BFS 를 사용하면 됨- DP를 사용하면 시간초과가 발생함. - 도달시 다 종료한다고 해도, 그게 최소로 간건지 몰라서 결국 더 검사해야함.코드#include &lt;iostream&gt;#include &lt;list&gt;using namespace std;list&lt;pair&lt;int, int&gt;&gt; q;int F, G, S, U, D;bool check[1000001];int main() { cin &..." }, { "title": "4991 로봇청소기", "url": "/posts/4991/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "4991 로봇청소기알고리즘(BFS, 브루트포스)1. 구현이 더 중요한 문제.2. 그냥 시작 + 각 더러운 곳(DT) 에서 다른 DT로의 최소 거리를 저장한 2차원 배열을 BFS로 만들고3. 시작부터 모든 경로를 탐색하여 최소 거리를 뽑아내면 됨.4. 방문 할 수 없는지 판별은 시작에서 갈 수 없는 DT가 있는지 확인하면 됨구현1. 거리 저장한 2차원 배열은, 최대 DT가 10개이므로 [11][11] 로 선언함 이때, 0은 시작장소를 뜻함 따라서 각 줄의 0번째 칸과 자기자신은 0이어야함2. 시작하는 곳과 DT의 위치를 배열에..." }, { "title": "4811 알약", "url": "/posts/4811/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "4811 알약알고리즘 (dp)1. 하나의 완전한 알약을 one, 반쪽 알약을 half 라고 하면, dp[1001][1001] 를 선언하고, one, half를 기준으로 dp를 저장함2. dp를 진행하면서 밑으로 내려가면서 one이 1개 이상이면 1 줄이고 half 1 증가하고 밑으로 내려갈 수 있고, half가 1개 이상이면 마찬가지로.코드#include &lt;iostream&gt;using namespace std;long long dp[1001][1001];long long DP(int one, int ha..." }, { "title": "4386 make constellation", "url": "/posts/4386/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "4386 make constellation알고리즘(MST)1. 모든 별들 사이의 거리를 구한 다음에, 두 별의 위치, 거리를 저장한 구조체 배열을 거리순으로 오름차순 정렬2. 구조체 배열에서 하나씩 빼가며 크루스칼 시행 -&gt; edge == numOfStar -1 이면 중지 후 출력코드#include &lt;iostream&gt;#include &lt;vector&gt;#include &lt;cmath&gt;#include &lt;cstdio&gt;#inclu..." }, { "title": "3197 백조의 호수", "url": "/posts/3197/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "3197 백조의 호수알고리즘(구현)처음 방법 1. 한 백조에서 BFS 실시하여 다른 백조를 만날 수 있는지 검사 2. 전체 맵을 검사해서 녹일 수 있는 걸 녹임 3. day 추가하고 다시 1부터 -&gt; 시간이 너무 걸림. 특히 맵 초기화하는 부분이 많이 걸림두번째 방법 1. 1번은 유지 2. 녹을 수 있는 얼음만 저장한 q에서 얼음을 하나씩 꺼내서 녹이고, 주변에 검사되지않은 얼음이 있으면 따로 저장 -&gt; q가 끝나면 따로 저장한 얼음을 q에 복사 3. day추가하고 다시 1부터 -&gt; 또 ..." }, { "title": "3190 Samsung sw test", "url": "/posts/3190/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "3190 Samsung sw test알고리즘1. 그냥 대가리 옮기고 꼬리 잡히는지, 칸 밖인지 확인하고, 또 사과있는지 확인하고, 또 시간이 바꿀때가 됐는지 확인2. 뱀 몸은 vector로 저장 / 사과 위치는 2차원 bool 배열로 저장하여 메모리랑 참조시간 아낌. / 시간은 Direction 구조체에 저장하고, directionIndex를 선언하여 directionIndex에 있는 시간에 맞으면 방향 바꾸고 index++.3. 구현문제이므로 내가 생각하는 게임이랑 실제 문제 조건은 다를 수 있다. 따라서 예시를 통해 확인..." }, { "title": "2933 미네랄", "url": "/posts/2933/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "2933 미네랄알고리즘(구현, 시뮬레이션)1. 들어온 위치의 미네랄을 지움2. 지운 위치에서 상하좌우에 인접한 칸들을 root로 BFS 시행 -&gt; 바닥에 닿는 칸이 있으면 공중에 뜬 클러스터가 아님3. 공중에 뜬 클러스터이면 BFS 시 넣은 칸들을 열순, 열이 같으면 행이 큰 순으로 정렬4. 각 열별로 가장 밑에 있는 칸만 검사하여 밑 클러스트 또는 바닥과 떨어진 최소 거리를 구함5. 정렬한 순으로 최소거리만큼 밑으로 내림6. 끝날때까지 1~5 반복코드#include &lt;iostream&gt;#i..." }, { "title": "2931 가스관", "url": "/posts/2931/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "2931 가스관알고리즘(구현)1. M에서 시작해 갈 수 있는 방향으로 가다가 빈칸을 마주하면 스탑. 만약 빈칸이 없으면 아예 처음부터 연결이 안된 것. 이때는 Z 부터 시작해 빈칸을 찾는다.2. 빈칸을 찾으면 빈칸 주변을 검색. 인접한 칸에 바로 연결되는 파이프가 있으면 그 칸을 true로 표시. 표시된 것에 따라 빈칸에 넣을 수 있는 가스관을 배정. ex) 빈칸 | 이면 연결이 안되므로, 오른쪽칸은 false 빈칸 - 이면 연결이 되므로 오른쪽 칸은 true코드#include &lt;bits/stdc++.h&..." }, { "title": "2887 planet tunnel", "url": "/posts/2887/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "2887 planet tunnel알고리즘(정렬, MST)1. 모든 행성들의 x좌표, y좌표, z좌표를 꺼내 따로 저장하고, 오름차순으로 정렬2. 정렬한 것에서 인접한 것끼리 비용을 계산, (인접한 점 두개 + 비용)을 구조체로 n-1개의 원소를 가지는 배열 3개를 선언하고 집어넣음3. 그 구조체배열 3개는 비용을 기준으로 오름차순으로 정렬4. 구조체 배열 3개를 가리키는 3개의 인덱스 변수 xi, yi, zi를 선언5. xi, yi, zi 중 최소 비용을 unionFind 알고리즘으로 사이클이 형성되는지 확인하면서 집어넣음. ..." }, { "title": "2568 전기줄", "url": "/posts/2568/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "2568 전기줄알고리즘(LIS)1. LIS를 구하면 그것들이 겹치지 않고 최대로 연결가능한 전깃줄들이다.2. 따라서 LIS를 구하고 전체 전깃줄에서 LIS를 빼면 빼야하는 전깃줄의 수와 전깃줄의 종류가 나오게 된다.코드#include &lt;bits/stdc++.h&gt;#define MAX_WIRE 100001using namespace std;struct Wire { int a; int b; bool check; bool operator &lt;(Wire s) { return a &lt; s...." }, { "title": "2529 부등호", "url": "/posts/2529/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "2529 부등호알고리즘(브루트포스, 백트래킹)1. 모든 경우를 탐색하되, 앞에서부터 하나씩 채워가면서 재귀형태로 하면 백트래킹도 적용하기 쉽다.2. 조건에 맞는 경우만 탐색하면 됨.3. 낮은 거 먼저 탐색하도록하면 굳이 뭘 저장할 필요없이 맨처음것만 minimum에 저장하고, 제일 나중 것을 maxnimum에 저장하면 된다.코드#include &lt;iostream&gt;using namespace std;int N, arr[10];char str[9], mini[10], maxi[10];bool check[10..." }, { "title": "2342 Dance Dance Revolution", "url": "/posts/2342/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "2342 Dance Dance Revolution알고리즘(DP)1. 왼발 위치, 오른발 위치, 명령 번호를 기준으로 dp 자료를 만듬 dp[5][5][MAX_COMMAND]2. 명령과 같은 칸에 있을 경우 그대로, 다른 위치에 있을 경우 가능한 위치엔 전부 옮기는 식으로 DP 시행.코드#include &lt;iostream&gt;#include &lt;cstring&gt;#define MAX_COMMAND 1000000using namespace std;int command[MAX_COMMAND];..." }, { "title": "2252 줄세우기", "url": "/posts/2252/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "2252 줄세우기알고리즘(위상정렬)1. 그냥 위상정렬하면된다.구현1. Student 구조체 : beforeCount : 이전에 몇개있는지 셈, next : 큰 학생들 나열, isChecked : 라인에 넣어졌는지 확인2. 처음에 beforeCount가 0인 학생들을 큐에 넣고, isCheked에 체크함3. 하나씩 큐에서 빼면서 아래 시행 -라인에 넣고 -next에 있는 학생들의 beforeCount를 1개씩 줄임 -이때, 그 학생의 beforeCount가 0이 되면, 임시 큐에 넣고 isChecked에 TRUE 표시4. 큐에서..." }, { "title": "2239 sudoku", "url": "/posts/2239/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "2239 sudoku알고리즘(백트래킹)1. 칸을 하나씩 탐방하면서, 0으로 기록돼있다면 수직, 수평, 3x3사각형을 검사하여 가능한 숫자를 하나씩 뽑아서 넣고 다음 칸 검사2. 만약 0으로 기록되지않은 칸으로 갔다면 다음칸으로 넘겨주되, 마지막 칸일 경우 스도쿠가 완성된 것이므로 true를 반환한다.3. 마찬가지로, 넣고 다음 칸으로 이동하는데, 마지막칸일 경우 스도쿠가 완성된 것이므로 true를 반환한다.4. 최소일때를 반환하는 것이므로 먼저 검사하는 칸을 왼쪽 위로하고, 1부터 검사하여 가능한 걸 넣으면 된다.구현1. 내가..." }, { "title": "2186 문자판", "url": "/posts/2186/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "2186 문자판알고리즘(DFS, DP)1. 문자판을 주어진 조건대로 순회하되, 결과값이 int 안이라는 걸보고 엄청 클 수도 잇다는 걸 예측 or 빙글빙글 계속 돌 수 있으니 DP가 잇어야겠다고 예측2. DP를 DP[r][c][d] 로 구현하되, -1로 초기화를 시키고, 0인것도 기록 -&gt; 문자열이 아예 없을 수도 잇으니 0도 기록하여 더 빨리 DP가 가능해짐.코드#include &lt;bits/stdc++.h&gt;using namespace std;int N, M, K, length, DP[101..." }, { "title": "2166 areaOfPolygon", "url": "/posts/2166/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "2166 areaOfPolygon알고리즘(기하학)1. 사선정리 사용구현법1. 최대 4000000000000이 나올 수 있으므로, int대신 long long, float은 쓰지말고 double로 통일 시킨다.2. double은 최대 15자리수를 표현가능하므로, 위 값도 충분히 들어갈 수 있다.코드#include &lt;iostream&gt;#include &lt;cstdio&gt;#include &lt;cmath&gt;#define MAX_POINT 10000using namespac..." }, { "title": "2151 거울설치", "url": "/posts/2151/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "2151 거울설치알고리즘 (BFS)1. 자기 방향으로만 쭉 가다가 ! 를 만나면 자기방향 + 양옆으로 이동. 양옆으로 이동할 땐 거울을 설치하는 것이므로 거울 + 1.2. 이때 visit으로 이미왔던곳을 안가면, 다른 루트로 가는 빛은 중간에 끊기기에, visit에 빛이 그곳에 도착했을 때의 거울의 개수를 저장함. 그리고 다음 빛이 그곳에 도착했을 때, 거울 개수가 적을 때만 이동 아니면 이동시키지 않는다. ex) if( .... &amp;&amp; n + 1 &lt; visit[r][c]) ..." }, { "title": "2143 sumOfTwoArray", "url": "/posts/2143/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "2143 sumOfTwoArray알고리즘(이분탐색, 누적합)1. A배열은 그대로 두고, B 배열은 가능한 모든 누적합의 집합을 만들고, 그것을 정렬함 -&gt; 이때 그냥 배열에 집어넣으면 최대 500500개의 원소가 있어서 메모리에서 박살나고, 찾는데 시간도 박살남 -&gt; 난 set을 택했음 -&gt; Pair 구조체를 선언해서, 누적합과 그 누적합이 나온 횟수를 저장함. 그리고 정렬과 비교연산자를 오버로딩하여 set에서 활용할 수 있게함 -&gt; B의 누적합을 하나씩 만들어가며, s..." }, { "title": "2098 TSP", "url": "/posts/2098/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "2098 TSP알고리즘(외판원 순회, DP, 비트마스크)1. 비트마스크로 각 비트마다 하나의 도시라고 치고, 검사를 함.2. 방문하지 않았고, 갈수 있는 도시를 방문하는 식으로 끝까지감. -&gt; 끝까지 갔을 때 다시 돌아갈 수 있는 길이 있으면 그 값 반환, 없으면 IMPOSSIBLE 반환 -&gt; 그 전 재귀로 돌아와서 가장 최솟값을 dp[current][bitMask] 에 저장함.3. 각 재귀 처음엔 돌아갈 수 마지막인지 확인하고, 이미 방문됐는지 확인한다.새겨둘 점.1. 모든 출발점에 대해 실행할 필요..." }, { "title": "2056 task", "url": "/posts/2056/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "2056 task알고리즘(위상정렬)1. Task 구조체를 선언하여, time : 현재 task가 걸리는 시간을 저장 / timeTaken : 현재까지 오는데 선행으로 필요한 작업을 수행하면서 걸리는 시간 중 가장 긴 시간 저장 next : 다음으로 이어지는 작업들을 저장2. Task 구조체 배열을 앞에서부터 검사 -&gt; 현재 task의 time과 timeTaken을 더해 temp에 저장. -&gt; 이어지는 작업들을 하나씩 검사 -&gt; 이어지는 작업의 timeTaken보다 현재의 temp가 더 ..." }, { "title": "20055 Samsung sw test", "url": "/posts/20055/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "20055 Samsung sw test알고리즘 (구현)1. durability와 isThereRobot 변수로 구성된 Cell 구조체로 컨베이어 벨트를 만듦2. 무한 루프 안에서, 이동시키고 -&gt; 내리는 위치 로봇빼고 -&gt; 로봇이동 시키고(이동시 내구도 1감소) -&gt; 내리는 위치 로봇빼고 -&gt; 올리는 위치 로봇 올리고(내구도 1감소) -&gt; 내구도 0인 칸이 한계점에 닿으면 종료기타1. 매번 내구도 0인 칸이 몇개인지 세기보단, 내구도를 깎는 칸에 깎고나서 내구도 0..." }, { "title": "1987 Alphabet", "url": "/posts/1987/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "1987 Alphabet알고리즘(백트래킹)1. 어떤 알파벳을 검사했는지 기록하는 건 check[26] 배열을 선언하고, 각 알파벳에서 65 값을 빼서 각 자리로 접근한다.('A' - 65 == 0)2. 인접한 칸을 검사하고, 가능하면 check 표시하고 인접한 칸으로 이동. 안되면 돌아와서(Back Tracking) 다른 인접한 칸 검사3. 갈 수 있는 인접한 칸이 없으면 그 칸이 마지막 칸이므로 maxCount 보다 크면 기록 -&gt; 종료4. 만약 deep이 27에 다다르면, 알파벳 26개를 전부 검..." }, { "title": "1956 운동", "url": "/posts/1956/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "1956 운동알고리즘(플로이드 워셜)1. 문제 파악 : 그래프 상의 어떤 점들 집합이건 최소로 사이클을 이루는 집합이면 됨2. 그래프 관련 알고리즘 1. 다익스트라 : 출발점에서 모든 정점까지의 거리 -&gt; 해당 x 2. 밸만포드 : 다익스트라와 마찬가지 3. MST : 최소신장트리 -&gt; 사이클은 감지할 수 있으나, 그것이 최소 사이클인지 모름 and 사이클만을 추출해내기 어려움 4. 플로이드 워셜 : 모든 정점 사이의 거리 -&gt; 자기 자신은 원래 0으로 표시하지만, 이 문제에선 INF로 ..." }, { "title": "19235 모노미노도미노", "url": "/posts/19235/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "19235 모노미노도미노알고리즘(구현)1. 파란 맵과 초록 맵을 따로 만듬.(bool 형으로 해서 true면 블록이 있고, false면 없는 거로 취급)2. 매 실행마다 블록을 잘 놓고 -&gt; 업데이트 하면서 점수 얻고 -&gt; 연한부분 있으면 당긴 다음에 -&gt; 다음 실행3. 실행마다 얻은 점수를 잘 기록해서 리턴함. 그리고 남은 블록 수를 세서 출력.4. 단, 모노미노도미노 1인 경우 - bool형이 아닌 int형으로 하고 0이면 블록 없음, 1 이상이면 각 블록을 나타내서, 옮길 때 어떤 블..." }, { "title": "1865 웜홀", "url": "/posts/1865/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "1865 웜홀코드#include &lt;bits/stdc++.h&gt;using namespace std;int tc, N, M, W, road[501][501];long long d[501];bool check[501][501];vector&lt;int&gt; connect[501];bool belmanford();int main() { ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); cin &gt;&gt; tc; while (tc--) { ..." }, { "title": "1806 partial sum", "url": "/posts/1806/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "1806 partial sum알고리즘 (브루트포스)1. 길이를 1부터 N까지 검사2. 한 길이를 검사할 때 다음을 실행 1. 수열의 처음부터 길이만큼 더하고 sum에 저장, 첫번째를 따로 first에 저장. -&gt; S를 넘는지 확인하고 안넘으면 2 실행 2. 수열의 (길이)번째부터 검사. 위에서 구한 sum에서 first를 빼고, 현재의 수열원소를 집어넣음. -&gt; 검사 -&gt; 아니면 first에 두번째 저장3. 다 했는데도 안되면 0 반환-&gt; 통과는 되지만, 아주 느림알고리즘(두 ..." }, { "title": "17837 새로운게임2 Samsung sw test", "url": "/posts/17837/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "17837 새로운게임2 Samsung sw test알고리즘(구현)1. 색깔을 저장한 map, 말을 저장하는 3차원 배열 맵(각 칸마다 4칸만 있으면 됨), 각 칸에 말이 몇개있는지 저장하는 인덱스맵2. 턴을 계속 진행함. 각 말을 순차적으로 검사함. 이때 각 말들을 몇 번 검사했는지 저장하는 temp 배열을 생성. 방향과 색깔에 맞춰 이동 시킴. -두번째면 그냥 넘기고 다음 거 검사 -빨강이면 위에서부터 옆으로 이동시킴 -흰색이면 현재 말부터 옆으로 이동시킴 -파랑이나 맵 밖이면 방향을 바꾸고 현재말을 다시 검..." }, { "title": "1753 최단경로", "url": "/posts/1753/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "1753 최단경로알고리즘1. 다익스트라코드#include &lt;iostream&gt;#include &lt;queue&gt;#include &lt;vector&gt;#define INF 2000000000;using namespace std;int V, E, s, length[20001];vector&lt;pair&lt;int, int&gt;&gt; node[20001];priority_queue&lt;pair&lt;int, int&..." }, { "title": "17404 RGB Distance 2", "url": "/posts/17404/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "17404 RGB Distance 2알고리즘 : DP1. bottom-top 방식으로 품2. DP 구성 : (현재 깊이, 이전에 고른 색깔)칸에 현재 깊이에서의 최솟값 저장.3. DP 작동 a. 첫번째 집은 for문으로 하나씩 들어감. b. 두번째 집부터 이전에 고른 색이 아닌 색으로 이동 c. 마지막집 + 1에 도착하면 0을 리턴 d. 마지막집은 0리턴 받은 것에 고른 색의 값 더함. 총 가능한 모든 색 값 중의 최솟값을 DP[deep][before]에 저장함 e. 하나씩 올라가면서 DP에 저장 f. DP에 저장돼있는게 있으..." }, { "title": "17144 Samsung sw test", "url": "/posts/17144/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "17144 Samsung sw test알고리즘1. 기존 map에서 미세먼지가 있는 부분만, 기존에 있던 미세먼지만 확산시키는 게 관건2. 따라서 tempMap을 만들어서 map을 복사한 다음에, map에 있는 미세먼지 양을 받아서, tempMap에 확산시키고, 다 확산하면 tempMap을 map에 복사하고 순환하여야 한다.3. 자꾸 갱신되는 상황을 잊지 말자코드#include &lt;iostream&gt;#include &lt;cstring&gt;#define MAX_WIDTH 50using nam..." }, { "title": "16954 움직이는 미로탈출", "url": "/posts/16954/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "16954 움직이는 미로탈출알고리즘(BFS)1. q와 nextq를 선언2. q엔 현재 노드를 저장. 하나식 꺼내서 이동가능한 인접 칸을 nextq에 추가.3. 다 추가하면, 벽을 한칸 내림4. 이후 q에 nextq를 넣고, nextq는 비운채 다음 루프 시작.5. 루프 중에 q에서 꺼낸 노드가 벽이 있는 칸이라면 추가안하고 다음 노드로, 최우측상단이라면 종료코드#include &lt;bits/stdc++.h&gt;using namespace std;char miro[8][8];list&lt;pair&..." }, { "title": "16946 벽부수고 이동하기 4", "url": "/posts/16946/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "16946 벽부수고 이동하기 4알고리즘 (BFS)1. 각 맵의 빈공간마다 각자의 크기를 BFS로 구함2. 이때, 각자의 id를 지정하여 같은 집합에 속하면 같은 id를 갖도록 하자3. BFS가 끝나면 각 벽을 검사 -&gt; 주변에 인접한 모든 빈공간의 아까 구한 크기를 모두 더하고 자기자신 + 1해서 리턴 -&gt; 이때, 주변에 인접한 빈공간들의 id 중 겹치는 것이 있는지 확인하면서 넣자. id가 같은 것이 이미 들어가있으면 그것은 더하지 않는다.주의점1. memset으로 visit을 매 BFS마다 초기화시..." }, { "title": "16724 피리부는 사나이", "url": "/posts/16724/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "16724 피리부는 사나이알고리즘 (DFS)1. 맵에 있는 서로 이어져있는 그래프의 개수를 세면 됨.2. visit을 만들어, 이미 검사한 그래프의 원소엔 true를 표시한다. 그리고 tempVisit을 만들어, 현재 검사하고 있는 그래프의 원소에 true를 표시한다.3. 먼저, 첫번째 셀부터 검사하지 않았으면 DFS를 실시한다. -&gt; 갈수있는데까지 가면서 tempVisit에 true를 표시 -&gt; tempVsit이든 visit 배열이든 true로 표시돼있는 칸에 오면 중지 -&gt; 이때 상태..." }, { "title": "16566 카드게임", "url": "/posts/16566/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "16566 카드게임알고리즘(이분탐색, 정렬, 분리집합, 세그먼트트리?)1. 고른 카드를 버킷정렬함(400만개이므로 버킷정렬이 훨씬 빠르다. sort는 NlogN)2. 범위를 저장하는 set&lt;pair&lt;int,int&gt;&gt;을 선언하고 첫 범위를 넣음(0-m)3. 철수가 뽑은 카드의 수가 저장된 범위의 끝의 수보다 작을 때의 범위를 찾고, 그 범위안에서 uppper_bound로 큰수를 찾고, 그 찾은 공간을 제외한 양쪽의 두 범위를 다시 범위set에 추가하고, 현재 범위는 삭제한다. e..." }, { "title": "16562 친구비", "url": "/posts/16562/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "16562 친구비알고리즘 (union find)1. unionfind 를 구현하되, 루트에 친구비가 적은 것이 오도록함2. 루트만 골라 친구비를 정산한다.코드#include &lt;iostream&gt;#include &lt;vector&gt;#include &lt;cstring&gt;#include &lt;algorithm&gt;#define MAX_STUDENT 10001using namespace std;int numOfStudent, numOfFriendship,..." }, { "title": "1655 가운데를 말해요", "url": "/posts/1655/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "1655 가운데를 말해요내 방식 (multiset)1. 멀티셋으로 가운데를 트래킹해가면서 구함2. 첫번째 원소를 넣고, s.begin()으로 첫번째 iter를 middle로 받고 그대로 출력3. 두번째부턴 middle과 같은 원소가 들어오면, s.insert(s.upperbound(*middle), input) 으로 가장 앞에 넣음 아니면 그냥 s.insert 로 넣음4. 현재 넣는 것이 짝수번째 숫자이고, 넣은 원소가 현재 middle보다 작을 경우 middle-- (작은 원소가 들어왔을 때, middle은 반쪽 바로 앞..." }, { "title": "1647 split town", "url": "/posts/1647/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "1647 split town알고리즘(MST)1. MST를 만들고, 가장 거리가 긴 길을 빼면 된다.2. n개의 노드에 대해 n-1개의 간선만 연결되어있으므로, 1개만 빼면 딱 두개로 나눌 수 있는데, 뺀다면 가장 거리가 긴 길이기 때문이다.(2개 이상 빼면 3개이상으로 나눠짐)코드#include &lt;iostream&gt;#include &lt;vector&gt;#include &lt;cstring&gt;#include &lt;algorithm&gt;#define MA..." }, { "title": "1644 소수의 연속합", "url": "/posts/1644/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "1644 소수의 연속합알고리즘(에라토스테네스의 체, 두 포인터)1. 에라토스테네스의 체로, N이하의 소수를 모두 구함2. 두 포인터로 일치하는 해당하는 연속합을 구함 초기 : start = 0, end = 1, sum = prime[0] while start &lt; end if sum == N count++ sum -= prime[start++] //밑에서 end를 기준으로 끝을 정했으니, start를 조작한다. else if sum &lt; N if end == numOfPrime..." }, { "title": "1562 계단수", "url": "/posts/1562/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "1562 계단수알고리즘(DP, 비트마스킹)1. 숫자를 기록하는 num[101] 배열과, DP[101][10][1 &lt;&lt; 10] 배열을 이용하여 DP로 품2. 앞에서부터 길이에 맞게 숫자를 하나하나 기록해감. 기록하면서 비트마스킹으로 0~9가 있는지 표시 -&gt; 이때 이미 표시된 비트에 또 표시하면 기록이 망가짐. 따라서 &amp; 연산자로 검사하고 넣음 ex) int b = bit &amp; (1 &lt;&lt; num[deep]) ? bit : bit + (1 ..." }, { "title": "1516 Developing game", "url": "/posts/1516/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "1516 Developing game알고리즘(위상정렬, 그래프. DP)1. 어떤 건물을 짓기 위해 필요한 건물을 짓는 시간의 최대 + 이 건물을 짓는데 걸리는 시간을 출력하면 됨.2. 예를 들어 A -&gt; -&gt; D C B -&gt; -&gt; E 로 그래프가 이루어져 있을 때, A : A를 짓는 시간 B : B를 짓는 시간 C : max(A, B) + C를 짓는데 걸리는 시간 D : C + D를 짓는데 걸리는 시간 E : C + E를 짓는데 걸리는 시간 이기 때문이다. 물론..." }, { "title": "1509 팰린드롬 분할", "url": "/posts/1509/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "1509 팰린드롬 분할알고리즘(Manacher’s Algorithm, DP)1. 2차원 bool 형 배열 palindrome에 모든 팰린드롬을 저장. -&gt; palindrome[i][j] 엔 시작이 i이고 끝이 j인 문자열이 팰린드롬인지 아닌지 저장돼있음 -&gt; 구하는 법은 처음부터 i = 0~length, j = i~length; 로 가면서 팰린드롬인지 확인하고, 팰린드롬이면 Manacher’s Algorithm 으로 그 사이 팰린드롬을 전부 표시 아니면 그냥 넘어간다 -&gt; 팰린드롬일경우..." }, { "title": "14939 불끄기", "url": "/posts/14939/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "14939 불끄기알고리즘(브루트, 그리디, 비트마스킹)1. 최소한으로 눌러야하는 경우 : 한 칸은 한번만 눌러야함. 두번 이상누르면 최소가 아니고, 무한 루프가 될 수 잇음2. 모든 방법을 다 검사해봐야 정답을 얻을 수 있음 -&gt; 브루트 포스3. 한번씩만 누르면 누르는 순서는 중요하지않음 -&gt; 첫줄부터 차례대로 누름4. 이때 첫번째줄은 모든 경우로 누른다고 치고, 두번째 줄부터는 위에 전구가 켜진 경우만 눌러도 해결이 가능하다. -&gt; 그리디5. 모든 경우는 0~1023까지 비트마스킹 방식으..." }, { "title": "14938 서강그라운드", "url": "/posts/14938/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "14938 서강그라운드알고리즘(플로이드워셜)1. 접근 방법 - 처음엔 DFS 또는 BFS로 접근 - 근데 해보니 이전에 왔던 것보다 적은 거리를 써서온건 보내고, 보낸 다음부터 이어지는 건 겹치면 빼야함. - 결국 플로이드워셜로 최소거리를 모두 구한 후, 탐색범위 안에 들어오는 지역의 아이템을 모두 더하는 방식과 동일함.코드#include &lt;iostream&gt;#include &lt;list&gt;#include &lt;cstring&gt;using namespace std;i..." }, { "title": "14500 Samsung sw test", "url": "/posts/14500/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "14500 Samsung sw test알고리즘1. 가능한 모든 모양으로 검사.코드#include &lt;iostream&gt;#define MAX_WIDTH 501using namespace std;int width, height;int map[MAX_WIDTH][MAX_WIDTH];void getProperties();int putTetromino();int main() { while (true) { getProperties(); cout &lt;&lt; putTetromino(); }}voi..." }, { "title": "14499 Samsung sw test", "url": "/posts/14499/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "14499 Samsung sw test알고리즘1. 각 명령별로 검사. 초기 주사위 상태 (top : 1, north : 2, east : 3, south : 5, western : 4, bottom : 6)2. 명령대로 주사위를 굴리고 -&gt; 그 위치가 맵 밖이면 다음 명령 실행 맵 안이면 이동시키고, 주사위 상태 변경 -&gt; 이후 이동한 칸에 숫자 적용하고 -&gt; 상단 프린트 -&gt; 다음 명령 실행코드#include &lt;iostream&gt;#include ..." }, { "title": "14003 LIS5", "url": "/posts/14003/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "14003 LIS5알고리즘 (LIS)1. LIS 로 구함2. 수열 출력 1. LIS구하면서, 현재 LIS의 인덱스를 항상 저장함 2. LIS를 구하면, 가장 뒤에있는 LIS의 가장 뒤 원소의 인덱스가 저장됨 3. 그 인덱스로부터 길이를 하나씩줄여가며 앞으로 가면서 길이가 맞는걸 뒤에서부터 저장 4. 길이가 0이되면 종료 5. 이렇게하면 가장 뒤에있는 LIS가 저장된다.코드#include &lt;iostream&gt;#include &lt;algorithm&gt;#define MAX_LENGTH 10..." }, { "title": "13549 숨바꼭질 3", "url": "/posts/13549/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "13549 숨바꼭질 3알고리즘(BFS)1. BFS로 가능한 곳은 가되, check엔 이전에 온 것보다 적은 시간을 걸리며 온 것을 받아주기 위해 시간을 저장하자.코드#include &lt;iostream&gt;#include &lt;cstring&gt;#include &lt;list&gt;using namespace std;int N, K, result = 2000000000;int check[200020];list&lt;pair&lt;int,int&gt;&..." }, { "title": "13458 Samsung sw test", "url": "/posts/13458/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "13458 Samsung sw test알고리즘1. 오직 1명은 무조건 1명 있어야한다는 말2. 총감독관이 감시할 수 있는 응시자 수만큼 빼고, 감독관 수 1명 늘린다음에, 넣어야하는 부감독관 수를 더함. -&gt; 이를 모든 시험장에 적용3. 이때 총 필요한 감독관을 저장하는 변수는 long long으로 해야함(응시자 수 100만명, 시험장 수 100만개 이므로)코드#include &lt;iostream&gt;#define MAX_CLASS 1000000using namespace std;int testC..." }, { "title": "1339 단어 수학", "url": "/posts/1339/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "1339 단어 수학알고리즘(브루트포스, 그리디)- 브루트포스 1. 어떤 알파벳이 나왔는지 기록하고, 거기에 맞춰 알파벳의 종류의 개수만큼 모든 가능한 수를 뽑아 검사한다.- 그리디 1. 들어오는 단어를 알파벳 단위로 분류하고 다음처럼 자릿수를 곱해준다. ABC -&gt; 100A + 10B + C BCA -&gt; 100B + 10C + A 2. 그리고 모든 단어를 더해준다. 101A + 110B + 11C 3. 이를 앞에 자릿수대로 내림차순 정렬하면, 가장 커야할 수가 나오고, 그에따라 수를 부여하고 계산..." }, { "title": "12969 ABC", "url": "/posts/12969/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "12969 ABC알고리즘(dp)1. 현재 깊이 d, 이전에 나왔던 a의 개수, 이전에 나왔던 b의 개수, 이제까지의 k 수를 기준으로 dp를 만들어야함. k도 결과에 영향을 주기 때문.2. 즉, dp[d][a][b][k] 를 선언하고, 이 상태가 검사되면 true로 지정하여 다시 검사하지않음.3. 정답을 찾았을 때는 전역변수 got에 true값을 지정하여 바로 다른 모든걸 종료시키고 출력코드#include &lt;iostream&gt;#include &lt;cstring&gt;using namesp..." }, { "title": "12849 본대산책", "url": "/posts/12849/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "12849 본대산책알고리즘(DP)1. a라는 건물에 x분을 남기고 들어가는 것은, a에 연결된 모든 건물 b에 x+1분을 남기고 들어온 것과 같음2. 즉, 정보과학관에 0분을 남기고 들어오는 것을 시작으로 모든 경우를 탐색하면 된다.3. 쭉쭉 내려가다가 D분에 도달했을때, 장소가 정보과학관이면 1, 아니면 0을 리턴하여 바텀업으로 함4. DP에는 현재 위치와 시간으로 배열을 만들고, 그 위치에서 그 시간이 걸렸을 때의 결과를 1000000007로 나눈 값을 저장함코드#include &lt;iostream&gt;#..." }, { "title": "1248 맞춰봐", "url": "/posts/1248/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "1248 맞춰봐알고리즘 (백트래킹)1. 그냥 앞에서부터 가능한 수를 채워나가는 백트래킹 방식을 사용하면 된다.2. 이때, 가능한 수는 ans를 채워간다는 얘기다. 즉, 가능한 경우만을 탐색하여 가서 정답만을 하나하나 채워가는 식으로 모든 경우를 탐색하면 됨.코드#include &lt;bits/stdc++.h&gt;using namespace std;int N, ans[10];char S[10][10];bool gotAns;void makeArr(int deep, int beforeSum);bool check(in..." }, { "title": "12100 Samsung sw test", "url": "/posts/12100/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "12100 Samsung sw test알고리즘(DFS 재귀)1. 위, 오른쪽, 아래, 왼쪽으로 차례로 보냄. 보낼때, 보내는 쪽에 가까운 곳부터 하나씩 검사.2. 주의 사항 : 2 4 8 2 8 16 4 2 32 에서 위로 한번 보내면 맨 왼쪽 맨 위는 4가 되야하고 그 밑에 4가 와야함. 하지만 내가 처음 짠 코드에서는 한번 업데이트 된곳을 저장안하고 그때그때 업데이트된 맵에 하나하나씩 적용했기에 맨왼쪽맨위에 8이 왔음 이점에 주의코드#include &lt;iostream&gt;#include &lt;c..." }, { "title": "1202 Jewelry thief", "url": "/posts/1202/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "1202 Jewelry thief알고리즘1. 보석을 가치순으로 내림차순으로 정렬2. 가방을 multiset에 저장함3. 가치가 큰 보석부터 차례대로 꺼내면서 적절한 가방을 선택하고, 집어넣음 -적절한 가방 : 무게가 같으면 가장 적절. 같지 않으면 남는 무게가 가장 적은 가방 -찾는 법 1. 같은 게 있는지 확인 2. 없으면, 현재 검사하는 보석의 무게를 넣고 그대로 빼면 넣었던 자리에 있는 옆 가방의 반복자가 반환되는데 이 반복자가 multiset.end()와 같으면 보석 무게가 너무 무거워서 넣을 수 있는 가방이 없..." }, { "title": "1197 MST", "url": "/posts/1197/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "1197 MST알고리즘(크루스칼 알고리즘 사용)1. 가장 가중차가 작은 간선부터 검사2. 사이클 검사 1. 간선의 양단이 전부 이미 안들어갔으면 양단을 같은 벡터에 집어넣고, set 벡터에 집어넣음 2. 한쪽만 들어갔다면, 들어가 있는 한쪽의 set벡터에 안들어간 쪽을 집어넣음 3. 두개 다 들어가 있고, 들어간 집합이 같다면, 사이클 형성 -&gt; false 반환 4. 두개 다 들어가 있고, 들어간 집합이 다르다면, 두개를 합침.자료구조간선을 저장하는 Edge 배열노드를 저장하는 Node 배열. - 배열의 인덱스가 ..." }, { "title": "1167 트리의 지름", "url": "/posts/1167/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "1167 트리의 지름알고리즘 (dfs, dp)1. 1번 노드를 최상위 노드라고 가정하고 품2. 1번 노드부터 시작하여 트리를 밑으로 탐색함.3. 끝까지 닿으면, 거리를 올리면서(bottom-top) 다시 위로 올라감(재귀로 탐색하면 됨)4. 한 노드에서 재귀로 반환하는 건, 자신의 자식 노드에서 올라오는 것중 가장 큰 값(끝까지 갔을 때의 값들 중 가장 큰 값)5. 또 한 노드에선, 자식 노드에서 올라오는 것 중 가장 큰 값 2개를 저장하여, 탐색이 끝나면, 그 두개의 합이 전역으로 저장되어있는 length값보다 크면 저장.6..." }, { "title": "11657 타임머신", "url": "/posts/11657/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "11657 타임머신알고리즘(밸만포드)1. 밸만포드로 푼다.2. 길이는 long long으로 저장한다.코드#include &lt;bits/stdc++.h&gt;using namespace std;struct Road { int to; int time;};struct City { vector&lt;Road&gt; next; long long minTime; int before;};City city[501];list&lt;Road&gt; q;int N, M;bool generateMinTi..." }, { "title": "1149 RGB 거리", "url": "/posts/1149/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "1149 RGB 거리알고리즘(dp)1. 그냥 현재 노드에서 이전의 상태가 X였을때의 최솟값을 저장하는 dp 배열 DP[1000][3] 을 선언하고 DP를 해주면 된다.2. 첫번째와 마지막이 상관이없으므로 매번 값을 초기화 시켜줄 필요는 없다.코드#include &lt;iostream&gt;#define INF 2000000000using namespace std;int N, cost[1000][3], DP[1000][3];int getMin(int deep, int bf);int min(int a, int b) ..." }, { "title": "11437 LCA", "url": "/posts/11437/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "11437 LCA알고리즘 (BFS, LCA)1. 트리만들기 1. check 배열을 만들고, 1번 노드만 true 표시함 2. 정점 쌍이 들어왔을 때, 둘중에 check 표시된 것이 A, 안된 것이 B라고 할때, B의 부모엔 A를, 깊이엔 A+1을 넣고, check[B] 에 true 표시함 3. true 표시가 끝나고, B에 인접한 정점들을 BFS로 끝까지 방문하면서 깊이를 부여하고 check 표시함. 4. 만약 둘 다 check에 표시가 안돼있으면, 두 정점의 인접한 정점 벡터에 둘다 추가한다.2. LCA 찾기 1. log2의..." }, { "title": "11401 이항계수 3", "url": "/posts/11401/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "11401 이항계수 3알고리즘(페르마의 소정리, 거듭제곱)1. 페르마의 소정리 적용 nCk = n! / k!(n-k)! % 1000000007 에서 A = n!, B = k!(n-k)!, p = 1000000007 이라고 할 때 위 식은 (A * B^(-1)) % p 를 성립 페르마의 소정리는 p가 소수일때, p와 서로소인 a가 있으면, a^p mod p = a mod p 가 성립. -&gt; a^p-1 mod p = 1 mod p 이므로 a^p-1 mod p = 1 이 성립함을 알려주는 공식 여기서 양변에 a를 한번 ..." }, { "title": "1107 리모컨", "url": "/posts/1107/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "1107 리모컨알고리즘(브루트 포스)1. 0~100만까지 하나씩 올라가며, 되는지 검사하고, 가장 적게 누르는 건지 저장2. 되는지 검사할 때는, 고장난 버튼을 bool 배열에 저장하여 바로바로 참조가능하게 하고, 뒤에서부터 하나씩 검사하자헛발질 기록1. 처음엔 들어온 버튼의 자릿수에 맞게, 모든 숫자를 검사하되 재귀로 수를 하나씩 채워하는 식으로 하여 안되는 건 미리 제거하는 식으로 함 -&gt; ex) 2528이면 모든 4자리수를 검사하되, 앞에서부터 안되는건 배제하며 채워감2. 그러나 자릿수를 더 크게 해도 최소가..." }, { "title": "11066 파일합치기", "url": "/posts/11066/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "11066 파일합치기알고리즘(DP)1. 긴 한 줄의 파일들을 두조각으로 분리해서 찾는 방식으로 DP를 함2. 먼저 0, k-1를 넣음3. (0, k-1)의 파일 크기의 합을 구한 후, (0,0)-(1,k-1), (0, 1)-(2,k-1) ... 로 모든 두 조각을 만든 후, 그 조각의 비용을 구함 -&gt; start == end 일 경우 합치지 않으므로 비용은 0 ex) (0,1) 일 경우, 0 + 0 + file[0] + file[1]4. 각 조각의 비용은 dp[start][end]에 저장함.주의점이전 tc의 결과가..." }, { "title": "11054 가장 긴 바이토닉 부분수열", "url": "/posts/11054/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "11054 가장 긴 바이토닉 부분수열알고리즘(LIS)1. 왼쪽에서 오른쪽으로 LIS, 오른쪽에서 왼쪽으로 LIS 후, 모든 점에 대하여 l[i] + r[i] - 1 (길이가 1이면 1일때, 0일경우는 + 1 해줌) 가 최대가 되는 점을 찾고 그점에서의 l[i] + r[i] -1 를 출력하면 됨.2. 길이가 최대1000이므로 dp로 안해도 되는데 해도 무방코드#include &lt;iostream&gt;#include &lt;algorithm&gt;#include &lt;cstring&..." }, { "title": "11049 행렬곱셈순서", "url": "/posts/11049/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "11049 행렬곱셈순서알고리즘(DP)1. 연속된 원소를 크게 이등분하여 계산할 수 있음 ex) (start ~ i, i+1~end)2. dp(start, end)는 start에서 end까지의 범위를 계산할 때 최소비용.3. 현재 비용은, matrixt[start][0]*matrix[i][1]*matrix[end][1] 로 계산할 수 있음.4. start == end 일때 비용은 0코드#include &lt;iostream&gt;#define MAX_MATRIX 501using namespace std;int nu..." }, { "title": "10942 Palindrome", "url": "/posts/10942/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "10942 Palindrome알고리즘(DP)1. 입력으로 받은 것이 팰린드롬인지 확인2. 팰린드롬이면 from-to 사이에 있는 모든 건 다 팰린드롬이다. -&gt; DP에 팰린드롬임을 표시 아니면, from-to에서 팰린드롬이 아님을 판별한 곳까지 팰린드롬이 아니다 -&gt; DP에 팰린드롬이 아님을 표시주의점아무리 잘 짜도ios::sync_with_stdio(false);cin.tie(0);를 써줘야 통과가 됨.코드#include &lt;iostream&gt;#include &lt;cs..." }, { "title": "10830 행렬제곱", "url": "/posts/10830/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "10830 행렬제곱알고리즘 (거듭제곱, 행렬곱)1. 그냥 행렬 거듭제곱으로 풀면됨주의점1. 입력의 B를 long long으로 받아야한다2. AB = A 가 나오도록 하는 B행렬은 대각선이 모두 1이고 나머지는 0인 행렬이다코드#include &lt;iostream&gt;#include &lt;cstring&gt;using namespace std;long long N, B;long long map[5][5], result[5][5] = { {1,0,0,0,0}, {0,1,0,0,0}, {0,0,1,..." }, { "title": "10775 airport", "url": "/posts/10775/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "10775 airport알고리즘(자료구조, 트리를 이용한 이진탐색)1. 게이트의 개수에 맞게 1~G를 set에 넣음2. bool형 check 배열에서, gi에 아무것도 없으면 gi에 넣고 set에서 gi 삭제3. 이미 gi에 들어가 있으면, set에서 upperbound로 gi초과하는 첫번째를 찾음 -&gt; 만약 그 첫번째가 begin()이면 없는 것 -&gt; false 반환 후 입력 종료 -&gt; begin()이 아니면 그 곳에서 한칸 아래에 도킹하고, true반환주의점1. set의 erase는..." }, { "title": "1043 거짓말", "url": "/posts/1043/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "1043 거짓말알고리즘(DFS)1. 처음 진실을 알고있는 사람과 연결돼있는 파티 그룹과, 절대 연결되지 않은 그룹을 나눠야함.2. 나는 단순히 변화가 발생하지 않을 때까지 반복하였음. 그리고 그런 사람이 없는 그룹의 개수만 세서 출력함.다른 풀이1. 내꺼에서 좀더 최적화된 풀이2. 처음에 입력을 받으면서, 각 사람들이 어디 파티와 연결되어있는지 벡터에 저장.3. 처음부터 진실을 알고 있는 사람들이 있는 그룹을 큐에 넣고, 거짓말을 할 수 있는 그룹인지 체크하는 배열에 표시4. 큐에 넣어진 그룹들을 하나씩 빼가며, 그 그룹에 있..." }, { "title": "10217 KCM Travel", "url": "/posts/10217/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "10217 KCM Travel알고리즘(다익스트라, DP)1. 기본적으로 다익스트라를 사용하여 최소경로를 찾아야하는 것을 맞으나, 비용이 넘어버릴경우 이전의 최소경로로 간 것이 의미가 없어진다. 따라서 현재 큐에서 꺼낸 노드가, 이전에 지나간 노드보다 같은 비용일때 더 시간이 많이 걸렸을 경우만 패스. DP[101][10001] 선언2. 첫번째를 제외하고, 모든 노드를 INF 로 초기화3. 다익스트라를 진행하면서, 현재 큐에서 꺼낸 노드의 시간이 DP[현재노드][현재노드의 비용] 보다 클 때 패스 (같은 거는 현재 일 수 ..." }, { "title": "10165 KOI 2014_Elementary", "url": "/posts/10165/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "10165 KOI 2014_Elementary알고리즘1. 각 버스노선의 출발점, 도착점, 번호를 기록한 구조체를 만들고, 출발점을 기준으로 정렬. 출발점이 같으면 도착점이 작은 것이 앞에 위치하도록함.2. 버스노선인덱스를 저장하는 배열을 만들어, 버스노선을 하나씩 검사함. -초기 : 첫번째 버스노선을 넣음3. 검사내용 -출발점이 이전 버스노선과 같은가 -이전 버스노선이 0을 지나치고, 현재 버스노선도 0을 지나치는가 (정렬로 인해 자동으로 현재버스노선이 이전버스노선을 감싸는 형태가 됨.) -현재버스 노선을 넣었을 때 지워..." }, { "title": "1007 vector matching", "url": "/posts/1007/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "1007 vector matching알고리즘1. 각 테스트 케이스 별로 모든 x좌표와 y좌표를 더함2. n/2개의 점을 고르고, 그 고른 점들의 모든 x좌표와 y좌표를 더함3. 1에서 구한 x,y 에 2에서 구한 x,y를 2곱하고 뺌. (sumOfEveryX - 2*sumOfChoosedX)4. 3.에서 구한 벡터의 길이를 구한 후 반환. 반환 받은 곳은 최솟값을 다시 반환설명들어오는 점들이 짝수이고, 두개씩 짝 지어준다면 벡터를 구했을 때 반은 더하는 형태이고, 반은 빼는 형태이다.따라서 빼줄 반을 고르고, 전체 X의 합에서..." }, { "title": "1005 ACM Craft", "url": "/posts/1005/", "categories": "알고리즘, beackjoon", "tags": "", "date": "2021-04-26 18:19:26 +0900", "snippet": "1005 ACM Craft알고리즘(위상정렬, DP)1. 1516 게임개발과 비슷한 유형이다2. 위상정렬하여 푼다. 단, 1516과는 다르게 목표건물을 지었다는게 확인되면 while문을 종료하고 출력해도 됨.3. 단, 진짜로 위상정렬한 배열을 만들 필요는 없고, 위상정렬식으로 풀어도 시간은 비슷하다. 오히려 위상정렬을 굳이 안만드는 편이 더 빠를 수있다.4. 더 빠르게 하는 방법은, 목표건물이 속한 그래프만을 검사하는 것.기타1. DP : 왜 알고리즘 분류에 DP가 들어갔나 했더만, 이미 검사한 거는 check표시하여 검사하지..." } ] diff --git a/assets/js/data/swcache.js b/assets/js/data/swcache.js new file mode 100644 index 000000000..9f3dbf398 --- /dev/null +++ b/assets/js/data/swcache.js @@ -0,0 +1 @@ +const resource = [ /* --- CSS --- */ '/assets/css/style.css', /* --- JavaScripts --- */ '/assets/js/dist/home.min.js', '/assets/js/dist/page.min.js', '/assets/js/dist/post.min.js', '/assets/js/dist/categories.min.js', '/assets/js/data/search.json', '/app.js', '/sw.js', /* --- HTML --- */ '/index.html', '/404.html', '/categories/', '/archives/', '/tags/', /* --- Icons --- */ '/assets/img/favicons/favicon.ico', '/assets/img/favicons/apple-icon.png', '/assets/img/favicons/apple-icon-precomposed.png', '/assets/img/favicons/apple-icon-57x57.png', '/assets/img/favicons/apple-icon-60x60.png', '/assets/img/favicons/apple-icon-72x72.png', '/assets/img/favicons/apple-icon-76x76.png', '/assets/img/favicons/apple-icon-114x114.png', '/assets/img/favicons/apple-icon-120x120.png', '/assets/img/favicons/apple-icon-144x144.png', '/assets/img/favicons/apple-icon-152x152.png', '/assets/img/favicons/apple-icon-180x180.png', '/assets/img/favicons/android-icon-192x192.png', '/assets/img/favicons/favicon-32x32.png', '/assets/img/favicons/favicon-96x96.png', '/assets/img/favicons/favicon-16x16.png', '/assets/img/favicons/ms-icon-144x144.png', '/assets/img/favicons/manifest.json', '/assets/img/favicons/browserconfig.xml' ]; /* The request url with below domain will be cached */ const allowedDomains = [ 'www.googletagmanager.com', 'www.google-analytics.com', 'seongil-shin.github.io', 'fonts.gstatic.com', 'fonts.googleapis.com', 'cdn.jsdelivr.net', 'polyfill.io' ]; /* Requests that include the following path will be banned */ const denyUrls = [ ]; diff --git a/assets/js/dist/categories.min.js b/assets/js/dist/categories.min.js new file mode 100644 index 000000000..5ebbbc12a --- /dev/null +++ b/assets/js/dist/categories.min.js @@ -0,0 +1,6 @@ +/*! + * Chirpy v3.3.2 (https://github.com/cotes2020/jekyll-theme-chirpy/) + * © 2019 Cotes Chung + * MIT Licensed + */ +function copyLink(e){e&&0!==e.length||(e=window.location.href);const o=$("");$("body").append(o),o.val(e).select(),document.execCommand("copy"),o.remove(),alert("Link copied successfully!")}$(function(){$(window).scroll(()=>{50<$(this).scrollTop()&&"none"===$("#sidebar-trigger").css("display")?$("#back-to-top").fadeIn():$("#back-to-top").fadeOut()}),$("#back-to-top").click(()=>($("body,html").animate({scrollTop:0},800),!1))}),$(function(){$("input[type=checkbox]").addClass("unloaded"),$("input[type=checkbox][checked]").before(''),$("input[type=checkbox]:not([checked])").before('')}),$(function(){$("#main a").has("img").addClass("img-link")}),$(function(){const e=$("#sidebar-trigger"),o=$("#search-trigger"),a=$("#search-cancel"),s=$("#search-cleaner"),t=$("#main"),l=$("#topbar-title"),n=$("#search-wrapper"),r=$("#search-result-wrapper"),d=$("#search-results"),c=$("#search-input"),i=$("#search-hints"),u=function(){let e=0;return{block(){e=window.scrollY,$("html,body").scrollTop(0)},release(){$("html,body").scrollTop(e)},getOffset(){return e}}}(),f={on(){e.addClass("unloaded"),l.addClass("unloaded"),o.addClass("unloaded"),n.addClass("d-flex"),a.addClass("loaded")},off(){a.removeClass("loaded"),n.removeClass("d-flex"),e.removeClass("unloaded"),l.removeClass("unloaded"),o.removeClass("unloaded")}},p=function(){let e=!1;return{on(){e||(u.block(),r.removeClass("unloaded"),t.addClass("unloaded"),e=!0)},off(){e&&(d.empty(),i.hasClass("unloaded")&&i.removeClass("unloaded"),r.addClass("unloaded"),s.removeClass("visible"),t.removeClass("unloaded"),u.release(),c.val(""),e=!1)},isVisible(){return e}}}();function h(){return a.hasClass("loaded")}o.click(function(){f.on(),p.on(),c.focus()}),a.click(function(){f.off(),p.off()}),c.focus(function(){n.addClass("input-focus")}),c.focusout(function(){n.removeClass("input-focus")}),c.on("keyup",function(e){8===e.keyCode&&""===c.val()?h()?i.removeClass("unloaded"):p.off():""!==c.val()&&(p.on(),s.hasClass("visible")||s.addClass("visible"),h()&&i.addClass("unloaded"))}),s.on("click",function(){c.val(""),h()?(i.removeClass("unloaded"),d.empty()):p.off(),c.focus(),s.removeClass("visible")})}),$(function(){var e=function(){const e="sidebar-display";let o=!1;const a=$("body");return{toggle(){!1===o?a.attr(e,""):a.removeAttr(e),o=!o}}}();$("#sidebar-trigger").click(e.toggle),$("#mask").click(e.toggle)}),$(function(){$('[data-toggle="tooltip"]').tooltip()}),$(function(){const o=$("#topbar-wrapper"),a=$("#toc-wrapper"),s=$(".access"),t=$("#search-input");let l,n=0;const r=o.outerHeight();$(window).scroll(function(e){$("#topbar-title").is(":hidden")&&(l=!0)}),setInterval(function(){l&&(function(){var e=$(this).scrollTop();Math.abs(n-e)<=5||(e>n&&e>r?(o.removeClass("topbar-down").addClass("topbar-up"),0h1"),a=e.text().trim();let s=(0");$("body").append(o),o.val(e).select(),document.execCommand("copy"),o.remove(),alert("Link copied successfully!")}$(function(){$(window).scroll(()=>{50<$(this).scrollTop()&&"none"===$("#sidebar-trigger").css("display")?$("#back-to-top").fadeIn():$("#back-to-top").fadeOut()}),$("#back-to-top").click(()=>($("body,html").animate({scrollTop:0},800),!1))}),$(function(){$("input[type=checkbox]").addClass("unloaded"),$("input[type=checkbox][checked]").before(''),$("input[type=checkbox]:not([checked])").before('')}),$(function(){$("#main a").has("img").addClass("img-link")}),$(function(){const e=$("#sidebar-trigger"),o=$("#search-trigger"),t=$("#search-cancel"),a=$("#search-cleaner"),n=$("#main"),s=$("#topbar-title"),l=$("#search-wrapper"),r=$("#search-result-wrapper"),i=$("#search-results"),c=$("#search-input"),d=$("#search-hints"),u=function(){let e=0;return{block(){e=window.scrollY,$("html,body").scrollTop(0)},release(){$("html,body").scrollTop(e)},getOffset(){return e}}}(),h={on(){e.addClass("unloaded"),s.addClass("unloaded"),o.addClass("unloaded"),l.addClass("d-flex"),t.addClass("loaded")},off(){t.removeClass("loaded"),l.removeClass("d-flex"),e.removeClass("unloaded"),s.removeClass("unloaded"),o.removeClass("unloaded")}},f=function(){let e=!1;return{on(){e||(u.block(),r.removeClass("unloaded"),n.addClass("unloaded"),e=!0)},off(){e&&(i.empty(),d.hasClass("unloaded")&&d.removeClass("unloaded"),r.addClass("unloaded"),a.removeClass("visible"),n.removeClass("unloaded"),u.release(),c.val(""),e=!1)},isVisible(){return e}}}();function p(){return t.hasClass("loaded")}o.click(function(){h.on(),f.on(),c.focus()}),t.click(function(){h.off(),f.off()}),c.focus(function(){l.addClass("input-focus")}),c.focusout(function(){l.removeClass("input-focus")}),c.on("keyup",function(e){8===e.keyCode&&""===c.val()?p()?d.removeClass("unloaded"):f.off():""!==c.val()&&(f.on(),a.hasClass("visible")||a.addClass("visible"),p()&&d.addClass("unloaded"))}),a.on("click",function(){c.val(""),p()?(d.removeClass("unloaded"),i.empty()):f.off(),c.focus(),a.removeClass("visible")})}),$(function(){var e=function(){const e="sidebar-display";let o=!1;const t=$("body");return{toggle(){!1===o?t.attr(e,""):t.removeAttr(e),o=!o}}}();$("#sidebar-trigger").click(e.toggle),$("#mask").click(e.toggle)}),$(function(){$('[data-toggle="tooltip"]').tooltip()}),$(function(){const o=$("#topbar-wrapper"),t=$("#toc-wrapper"),a=$(".access"),n=$("#search-input");let s,l=0;const r=o.outerHeight();$(window).scroll(function(e){$("#topbar-title").is(":hidden")&&(s=!0)}),setInterval(function(){s&&(function(){var e=$(this).scrollTop();Math.abs(l-e)<=5||(e>l&&e>r?(o.removeClass("topbar-down").addClass("topbar-up"),0h1"),t=e.text().trim();let a=(0");$("body").append(t),t.val(e).select(),document.execCommand("copy"),t.remove(),alert("Link copied successfully!")}$(function(){$(window).scroll(()=>{50<$(this).scrollTop()&&"none"===$("#sidebar-trigger").css("display")?$("#back-to-top").fadeIn():$("#back-to-top").fadeOut()}),$("#back-to-top").click(()=>($("body,html").animate({scrollTop:0},800),!1))}),$(function(){$("input[type=checkbox]").addClass("unloaded"),$("input[type=checkbox][checked]").before(''),$("input[type=checkbox]:not([checked])").before('')}),$(function(){$("#main a").has("img").addClass("img-link")}),$(function(){const e=$("#sidebar-trigger"),t=$("#search-trigger"),o=$("#search-cancel"),a=$("#search-cleaner"),s=$("#main"),l=$("#topbar-title"),n=$("#search-wrapper"),r=$("#search-result-wrapper"),c=$("#search-results"),i=$("#search-input"),d=$("#search-hints"),u=function(){let e=0;return{block(){e=window.scrollY,$("html,body").scrollTop(0)},release(){$("html,body").scrollTop(e)},getOffset(){return e}}}(),f={on(){e.addClass("unloaded"),l.addClass("unloaded"),t.addClass("unloaded"),n.addClass("d-flex"),o.addClass("loaded")},off(){o.removeClass("loaded"),n.removeClass("d-flex"),e.removeClass("unloaded"),l.removeClass("unloaded"),t.removeClass("unloaded")}},p=function(){let e=!1;return{on(){e||(u.block(),r.removeClass("unloaded"),s.addClass("unloaded"),e=!0)},off(){e&&(c.empty(),d.hasClass("unloaded")&&d.removeClass("unloaded"),r.addClass("unloaded"),a.removeClass("visible"),s.removeClass("unloaded"),u.release(),i.val(""),e=!1)},isVisible(){return e}}}();function h(){return o.hasClass("loaded")}t.click(function(){f.on(),p.on(),i.focus()}),o.click(function(){f.off(),p.off()}),i.focus(function(){n.addClass("input-focus")}),i.focusout(function(){n.removeClass("input-focus")}),i.on("keyup",function(e){8===e.keyCode&&""===i.val()?h()?d.removeClass("unloaded"):p.off():""!==i.val()&&(p.on(),a.hasClass("visible")||a.addClass("visible"),h()&&d.addClass("unloaded"))}),a.on("click",function(){i.val(""),h()?(d.removeClass("unloaded"),c.empty()):p.off(),i.focus(),a.removeClass("visible")})}),$(function(){var e=function(){const e="sidebar-display";let t=!1;const o=$("body");return{toggle(){!1===t?o.attr(e,""):o.removeAttr(e),t=!t}}}();$("#sidebar-trigger").click(e.toggle),$("#mask").click(e.toggle)}),$(function(){$('[data-toggle="tooltip"]').tooltip()}),$(function(){const t=$("#topbar-wrapper"),o=$("#toc-wrapper"),a=$(".access"),s=$("#search-input");let l,n=0;const r=t.outerHeight();$(window).scroll(function(e){$("#topbar-title").is(":hidden")&&(l=!0)}),setInterval(function(){l&&(function(){var e=$(this).scrollTop();Math.abs(n-e)<=5||(e>n&&e>r?(t.removeClass("topbar-down").addClass("topbar-up"),0h1"),o=e.text().trim();let a=(0{const e=$(s);e.focus();var t="scroll-focus";if($(`[${t}=true]`).length&&$(`[${t}=true]`).attr(t,!1),$(":target").length&&$(":target").attr(t,!1),(a||o)&&e.attr(t,!0),e.is(":focus"))return!1;e.attr("tabindex","-1"),e.focus()})}}})}); \ No newline at end of file diff --git a/assets/js/dist/post.min.js b/assets/js/dist/post.min.js new file mode 100644 index 000000000..54a1efedf --- /dev/null +++ b/assets/js/dist/post.min.js @@ -0,0 +1,6 @@ +/*! + * Chirpy v3.3.2 (https://github.com/cotes2020/jekyll-theme-chirpy/) + * © 2019 Cotes Chung + * MIT Licensed + */ +function copyLink(e){e&&0!==e.length||(e=window.location.href);const t=$("");$("body").append(t),t.val(e).select(),document.execCommand("copy"),t.remove(),alert("Link copied successfully!")}$(function(){$(window).scroll(()=>{50<$(this).scrollTop()&&"none"===$("#sidebar-trigger").css("display")?$("#back-to-top").fadeIn():$("#back-to-top").fadeOut()}),$("#back-to-top").click(()=>($("body,html").animate({scrollTop:0},800),!1))}),$(function(){$("input[type=checkbox]").addClass("unloaded"),$("input[type=checkbox][checked]").before(''),$("input[type=checkbox]:not([checked])").before('')}),$(function(){$("#main a").has("img").addClass("img-link")}),$(function(){const e=$("#sidebar-trigger"),t=$("#search-trigger"),o=$("#search-cancel"),a=$("#search-cleaner"),n=$("#main"),s=$("#topbar-title"),l=$("#search-wrapper"),r=$("#search-result-wrapper"),i=$("#search-results"),c=$("#search-input"),d=$("#search-hints"),u=function(){let e=0;return{block(){e=window.scrollY,$("html,body").scrollTop(0)},release(){$("html,body").scrollTop(e)},getOffset(){return e}}}(),h={on(){e.addClass("unloaded"),s.addClass("unloaded"),t.addClass("unloaded"),l.addClass("d-flex"),o.addClass("loaded")},off(){o.removeClass("loaded"),l.removeClass("d-flex"),e.removeClass("unloaded"),s.removeClass("unloaded"),t.removeClass("unloaded")}},f=function(){let e=!1;return{on(){e||(u.block(),r.removeClass("unloaded"),n.addClass("unloaded"),e=!0)},off(){e&&(i.empty(),d.hasClass("unloaded")&&d.removeClass("unloaded"),r.addClass("unloaded"),a.removeClass("visible"),n.removeClass("unloaded"),u.release(),c.val(""),e=!1)},isVisible(){return e}}}();function p(){return o.hasClass("loaded")}t.click(function(){h.on(),f.on(),c.focus()}),o.click(function(){h.off(),f.off()}),c.focus(function(){l.addClass("input-focus")}),c.focusout(function(){l.removeClass("input-focus")}),c.on("keyup",function(e){8===e.keyCode&&""===c.val()?p()?d.removeClass("unloaded"):f.off():""!==c.val()&&(f.on(),a.hasClass("visible")||a.addClass("visible"),p()&&d.addClass("unloaded"))}),a.on("click",function(){c.val(""),p()?(d.removeClass("unloaded"),i.empty()):f.off(),c.focus(),a.removeClass("visible")})}),$(function(){var e=function(){const e="sidebar-display";let t=!1;const o=$("body");return{toggle(){!1===t?o.attr(e,""):o.removeAttr(e),t=!t}}}();$("#sidebar-trigger").click(e.toggle),$("#mask").click(e.toggle)}),$(function(){$('[data-toggle="tooltip"]').tooltip()}),$(function(){const t=$("#topbar-wrapper"),o=$("#toc-wrapper"),a=$(".access"),n=$("#search-input");let s,l=0;const r=t.outerHeight();$(window).scroll(function(e){$("#topbar-title").is(":hidden")&&(s=!0)}),setInterval(function(){s&&(function(){var e=$(this).scrollTop();Math.abs(l-e)<=5||(e>l&&e>r?(t.removeClass("topbar-down").addClass("topbar-up"),0h1"),o=e.text().trim();let a=(0{o.test(e)&&(e=e.substring(t.length),$(this).attr("lang",`${e}`))})})}),$(function(){$("a[href*='#']").not("[href='#']").not("[href='#0']").click(function(t){if(this.pathname.replace(/^\//,"")===location.pathname.replace(/^\//,"")&&location.hostname===this.hostname){const l=decodeURI(this.hash);let o=RegExp(/^#fnref:/).test(l),a=RegExp(/^#fn:/).test(l);var s=l.includes(":")?l.replace(/\:/,"\\:"):l;let n=$(s);if(n.length){t.preventDefault(),history.pushState&&history.pushState(null,null,l);s=$(this).offset().top;let e=n.offset().top;t=e{const e=$(n);e.focus();var t="scroll-focus";if($(`[${t}=true]`).length&&$(`[${t}=true]`).attr(t,!1),$(":target").length&&$(":target").attr(t,!1),(a||o)&&e.attr(t,!0),e.is(":focus"))return!1;e.attr("tabindex","-1"),e.focus()})}}})}); \ No newline at end of file diff --git a/assets/js/dist/pvreport.min.js b/assets/js/dist/pvreport.min.js new file mode 100644 index 000000000..cde699cd3 --- /dev/null +++ b/assets/js/dist/pvreport.min.js @@ -0,0 +1,6 @@ +/*! + * Chirpy v3.3.2 (https://github.com/cotes2020/jekyll-theme-chirpy/) + * © 2019 Cotes Chung + * MIT Licensed + */ +const getInitStatus=function(){let t=!1;return()=>{var e=t;return t=t||!0,e}}(),PvOpts=function(){function t(e){return $(e).attr("content")}function e(e){e=t(e);return void 0!==e&&!1!==e}return{getProxyMeta(){return t("meta[name=pv-proxy-endpoint]")},getLocalMeta(){return t("meta[name=pv-cache-path]")},hasProxyMeta(){return e("meta[name=pv-proxy-endpoint]")},hasLocalMeta(){return e("meta[name=pv-cache-path]")}}}(),PvStorage=function(){const a={KEY_PV:"pv",KEY_PV_SRC:"pv_src",KEY_CREATION:"pv_created_date"},t={LOCAL:"same-origin",PROXY:"cors"};function r(e){return localStorage.getItem(e)}function o(e,t){localStorage.setItem(e,t)}function n(e,t){o(a.KEY_PV,e),o(a.KEY_PV_SRC,t),o(a.KEY_CREATION,(new Date).toJSON())}return{keysCount(){return Object.keys(a).length},hasCache(){return null!==localStorage.getItem(a.KEY_PV)},getCache(){return JSON.parse(localStorage.getItem(a.KEY_PV))},saveLocalCache(e){n(e,t.LOCAL)},saveProxyCache(e){n(e,t.PROXY)},isExpired(){let e=new Date(r(a.KEY_CREATION));return e.setHours(e.getHours()+1),Date.now()>=e.getTime()},isFromLocal(){return r(a.KEY_PV_SRC)===t.LOCAL},isFromProxy(){return r(a.KEY_PV_SRC)===t.PROXY},newerThan(e){return PvStorage.getCache().totalsForAllResults["ga:pageviews"]>e.totalsForAllResults["ga:pageviews"]},inspectKeys(){if(localStorage.length===PvStorage.keysCount())for(let e=0;er&&countUp(r,o,a.attr("id"))):a.text((new Intl.NumberFormat).format(o))}function displayPageviews(e){if(void 0!==e){let t=getInitStatus();const a=e.rows;0<$("#post-list").length?$(".post-preview").each(function(){var e=$(this).find("a").attr("href");tacklePV(a,e,$(this).find(".pageviews"),t)}):0<$(".post").length&&(e=window.location.pathname,tacklePV(a,e,$("#pv"),t))}}function fetchProxyPageviews(){PvOpts.hasProxyMeta()&&$.ajax({type:"GET",url:PvOpts.getProxyMeta(),dataType:"jsonp",jsonpCallback:"displayPageviews",success:e=>{PvStorage.saveProxyCache(JSON.stringify(e))},error:(e,t,a)=>{console.log("Failed to load pageviews from proxy server: "+a)}})}function fetchLocalPageviews(t=!1){return fetch(PvOpts.getLocalMeta()).then(e=>e.json()).then(e=>{t&&PvStorage.isFromProxy()&&PvStorage.newerThan(e)||(displayPageviews(e),PvStorage.saveLocalCache(JSON.stringify(e)))})}$(function(){$(".pageviews").length<=0||(PvStorage.inspectKeys(),PvStorage.hasCache()?(displayPageviews(PvStorage.getCache()),PvStorage.isExpired()?PvOpts.hasLocalMeta()?fetchLocalPageviews(!0).then(fetchProxyPageviews):fetchProxyPageviews():PvStorage.isFromLocal()&&fetchProxyPageviews()):PvOpts.hasLocalMeta()?fetchLocalPageviews().then(fetchProxyPageviews):fetchProxyPageviews())}); \ No newline at end of file diff --git a/assets/js/lib/jquery.disqusloader.min.js b/assets/js/lib/jquery.disqusloader.min.js new file mode 100644 index 000000000..b2cb0bec6 --- /dev/null +++ b/assets/js/lib/jquery.disqusloader.min.js @@ -0,0 +1,8 @@ +/*! + disqusLoader.js v1.0 + A JavaScript plugin for lazy-loading Disqus comments widget. + - + By Osvaldas Valutis, www.osvaldas.info + Available for use under the MIT License +*/ +(function(e,g,h,d){var a=e(g),k=function(o,n){var q,p;return function(){var t=this,s=arguments,r=+new Date;q&&ra.height()*j||n-b.offset().top-b.outerHeight()-(a.height()*j)>0){return true}e("#disqus_thread").removeAttr("id");b.attr("id","disqus_thread").data("disqusLoaderStatus","loaded");if(f=="loaded"){DISQUS.reset({reload:true,config:i})}else{g.disqus_config=i;if(f=="unloaded"){f="loading";e.ajax({url:c,async:true,cache:true,dataType:"script",success:function(){f="loaded"}})}}};a.on("scroll resize",k(m,l));e.disqusLoader=function(o,n){n=e.extend({},{laziness:1,throttle:250,scriptUrl:false,disqusConfig:false},n);j=n.laziness+1;m=n.throttle;i=n.disqusConfig;c=c===false?n.scriptUrl:c;b=(typeof o=="string"?e(o):o).eq(0);b.data("disqusLoaderStatus","unloaded");l()}})(jQuery,window,document); \ No newline at end of file diff --git a/assets/video/2023-06-18-ios-issue-spec.mov b/assets/video/2023-06-18-ios-issue-spec.mov new file mode 100644 index 000000000..29734744e Binary files /dev/null and b/assets/video/2023-06-18-ios-issue-spec.mov differ diff --git a/assets/video/2023-06-18-ios-issue.mp4 b/assets/video/2023-06-18-ios-issue.mp4 new file mode 100644 index 000000000..54677431d Binary files /dev/null and b/assets/video/2023-06-18-ios-issue.mp4 differ diff --git a/categories/algorithm/index.html b/categories/algorithm/index.html new file mode 100644 index 000000000..a098c1347 --- /dev/null +++ b/categories/algorithm/index.html @@ -0,0 +1 @@ + algorithm | 디피의 개발일지
Home Categories algorithm
Category
Cancel
diff --git a/categories/article/index.html b/categories/article/index.html new file mode 100644 index 000000000..7d9835883 --- /dev/null +++ b/categories/article/index.html @@ -0,0 +1 @@ + article | 디피의 개발일지
Home Categories article
Category
Cancel
diff --git a/categories/beackjoon/index.html b/categories/beackjoon/index.html new file mode 100644 index 000000000..415106912 --- /dev/null +++ b/categories/beackjoon/index.html @@ -0,0 +1 @@ + beackjoon | 디피의 개발일지
Home Categories beackjoon
Category
Cancel

beackjoon 157

diff --git a/categories/clean-code/index.html b/categories/clean-code/index.html new file mode 100644 index 000000000..286f834d2 --- /dev/null +++ b/categories/clean-code/index.html @@ -0,0 +1 @@ + clean code | 디피의 개발일지
Home Categories clean code
Category
Cancel
diff --git a/categories/cs/index.html b/categories/cs/index.html new file mode 100644 index 000000000..71fbc2b44 --- /dev/null +++ b/categories/cs/index.html @@ -0,0 +1 @@ + cs | 디피의 개발일지
Home Categories cs
Category
Cancel
diff --git a/categories/css/index.html b/categories/css/index.html new file mode 100644 index 000000000..87d683d9f --- /dev/null +++ b/categories/css/index.html @@ -0,0 +1 @@ + css | 디피의 개발일지
Home Categories css
Category
Cancel
diff --git a/categories/database/index.html b/categories/database/index.html new file mode 100644 index 000000000..4d9995cc4 --- /dev/null +++ b/categories/database/index.html @@ -0,0 +1 @@ + database | 디피의 개발일지
Home Categories database
Category
Cancel
diff --git a/categories/datastructure/index.html b/categories/datastructure/index.html new file mode 100644 index 000000000..735f5d4be --- /dev/null +++ b/categories/datastructure/index.html @@ -0,0 +1 @@ + datastructure | 디피의 개발일지
Home Categories datastructure
Category
Cancel
diff --git a/categories/db/index.html b/categories/db/index.html new file mode 100644 index 000000000..b07f58728 --- /dev/null +++ b/categories/db/index.html @@ -0,0 +1 @@ + db | 디피의 개발일지
Home Categories db
Category
Cancel
diff --git a/categories/design-pattern/index.html b/categories/design-pattern/index.html new file mode 100644 index 000000000..c32e258c5 --- /dev/null +++ b/categories/design-pattern/index.html @@ -0,0 +1 @@ + design pattern | 디피의 개발일지
Home Categories design pattern
Category
Cancel
diff --git a/categories/devops/index.html b/categories/devops/index.html new file mode 100644 index 000000000..8729576c8 --- /dev/null +++ b/categories/devops/index.html @@ -0,0 +1 @@ + devops | 디피의 개발일지
Home Categories devops
Category
Cancel
diff --git a/categories/firebase/index.html b/categories/firebase/index.html new file mode 100644 index 000000000..9d2106709 --- /dev/null +++ b/categories/firebase/index.html @@ -0,0 +1 @@ + firebase | 디피의 개발일지
Home Categories firebase
Category
Cancel
diff --git a/categories/flutter/index.html b/categories/flutter/index.html new file mode 100644 index 000000000..49f0f145d --- /dev/null +++ b/categories/flutter/index.html @@ -0,0 +1 @@ + flutter | 디피의 개발일지
Home Categories flutter
Category
Cancel
diff --git a/categories/forkie/index.html b/categories/forkie/index.html new file mode 100644 index 000000000..73c4edb66 --- /dev/null +++ b/categories/forkie/index.html @@ -0,0 +1 @@ + forkie | 디피의 개발일지
Home Categories forkie
Category
Cancel
diff --git a/categories/graphql/index.html b/categories/graphql/index.html new file mode 100644 index 000000000..e6b33a6c0 --- /dev/null +++ b/categories/graphql/index.html @@ -0,0 +1 @@ + graphql | 디피의 개발일지
Home Categories graphql
Category
Cancel
diff --git a/categories/index.html b/categories/index.html new file mode 100644 index 000000000..d97288740 --- /dev/null +++ b/categories/index.html @@ -0,0 +1 @@ + Categories | 디피의 개발일지
Home Categories
Categories
Cancel

Categories

cs 1 category, 1 post
projects 1 category, 6 posts
study 26 categories, 180 posts
study, 3 categories, 13 posts
알고리즘 1 category, 159 posts
diff --git a/categories/java/index.html b/categories/java/index.html new file mode 100644 index 000000000..cc6f2faab --- /dev/null +++ b/categories/java/index.html @@ -0,0 +1 @@ + java | 디피의 개발일지
Home Categories java
Category
Cancel
diff --git a/categories/javascript/index.html b/categories/javascript/index.html new file mode 100644 index 000000000..486a51d74 --- /dev/null +++ b/categories/javascript/index.html @@ -0,0 +1 @@ + javascript | 디피의 개발일지
Home Categories javascript
Category
Cancel
diff --git a/categories/js/index.html b/categories/js/index.html new file mode 100644 index 000000000..846ffdcdf --- /dev/null +++ b/categories/js/index.html @@ -0,0 +1 @@ + js | 디피의 개발일지
Home Categories js
Category
Cancel
diff --git a/categories/network/index.html b/categories/network/index.html new file mode 100644 index 000000000..c2bc345f1 --- /dev/null +++ b/categories/network/index.html @@ -0,0 +1 @@ + network | 디피의 개발일지
Home Categories network
Category
Cancel
diff --git a/categories/next-js/index.html b/categories/next-js/index.html new file mode 100644 index 000000000..f2b6155bb --- /dev/null +++ b/categories/next-js/index.html @@ -0,0 +1 @@ + next.js | 디피의 개발일지
Home Categories next.js
Category
Cancel
diff --git a/categories/node/index.html b/categories/node/index.html new file mode 100644 index 000000000..e6caeee5d --- /dev/null +++ b/categories/node/index.html @@ -0,0 +1 @@ + node | 디피의 개발일지
Home Categories node
Category
Cancel
diff --git a/categories/os/index.html b/categories/os/index.html new file mode 100644 index 000000000..207ea619d --- /dev/null +++ b/categories/os/index.html @@ -0,0 +1 @@ + os | 디피의 개발일지
Home Categories os
Category
Cancel
diff --git a/categories/php/index.html b/categories/php/index.html new file mode 100644 index 000000000..6319fbd94 --- /dev/null +++ b/categories/php/index.html @@ -0,0 +1 @@ + php | 디피의 개발일지
Home Categories php
Category
Cancel
diff --git a/categories/projects/index.html b/categories/projects/index.html new file mode 100644 index 000000000..17267cb16 --- /dev/null +++ b/categories/projects/index.html @@ -0,0 +1 @@ + projects | 디피의 개발일지
Home Categories projects
Category
Cancel
diff --git a/categories/react-native/index.html b/categories/react-native/index.html new file mode 100644 index 000000000..91182c13c --- /dev/null +++ b/categories/react-native/index.html @@ -0,0 +1 @@ + react-native | 디피의 개발일지
Home Categories react-native
Category
Cancel
diff --git a/categories/react/index.html b/categories/react/index.html new file mode 100644 index 000000000..5cfaef744 --- /dev/null +++ b/categories/react/index.html @@ -0,0 +1 @@ + react | 디피의 개발일지
Home Categories react
Category
Cancel
diff --git a/categories/software-engineering/index.html b/categories/software-engineering/index.html new file mode 100644 index 000000000..cf814a9f9 --- /dev/null +++ b/categories/software-engineering/index.html @@ -0,0 +1 @@ + software engineering | 디피의 개발일지
Home Categories software engineering
Category
Cancel
diff --git a/categories/spring/index.html b/categories/spring/index.html new file mode 100644 index 000000000..8656f7837 --- /dev/null +++ b/categories/spring/index.html @@ -0,0 +1 @@ + spring | 디피의 개발일지
Home Categories spring
Category
Cancel
diff --git a/categories/study/index.html b/categories/study/index.html new file mode 100644 index 000000000..6630bc2e2 --- /dev/null +++ b/categories/study/index.html @@ -0,0 +1 @@ + study, | 디피의 개발일지
Home Categories study,
Category
Cancel
diff --git a/categories/summary/index.html b/categories/summary/index.html new file mode 100644 index 000000000..3dfc43175 --- /dev/null +++ b/categories/summary/index.html @@ -0,0 +1 @@ + summary | 디피의 개발일지
Home Categories summary
Category
Cancel
diff --git a/categories/typescript/index.html b/categories/typescript/index.html new file mode 100644 index 000000000..cf3cd301d --- /dev/null +++ b/categories/typescript/index.html @@ -0,0 +1 @@ + typescript | 디피의 개발일지
Home Categories typescript
Category
Cancel
diff --git a/categories/web/index.html b/categories/web/index.html new file mode 100644 index 000000000..8ca528f9b --- /dev/null +++ b/categories/web/index.html @@ -0,0 +1 @@ + web | 디피의 개발일지
Home Categories web
Category
Cancel
diff --git "a/categories/\354\213\244\354\232\251\354\243\274\354\235\230\355\224\204\353\241\234\352\267\270\353\236\230\353\250\270/index.html" "b/categories/\354\213\244\354\232\251\354\243\274\354\235\230\355\224\204\353\241\234\352\267\270\353\236\230\353\250\270/index.html" new file mode 100644 index 000000000..fe3db5bed --- /dev/null +++ "b/categories/\354\213\244\354\232\251\354\243\274\354\235\230\355\224\204\353\241\234\352\267\270\353\236\230\353\250\270/index.html" @@ -0,0 +1 @@ + 실용주의프로그래머 | 디피의 개발일지
Home Categories 실용주의프로그래머
Category
Cancel
diff --git "a/categories/\354\225\214\352\263\240\353\246\254\354\246\230/index.html" "b/categories/\354\225\214\352\263\240\353\246\254\354\246\230/index.html" new file mode 100644 index 000000000..dfbb0b725 --- /dev/null +++ "b/categories/\354\225\214\352\263\240\353\246\254\354\246\230/index.html" @@ -0,0 +1 @@ + 알고리즘 | 디피의 개발일지
Home Categories 알고리즘
Category
Cancel

알고리즘 159

diff --git a/feed.xml b/feed.xml new file mode 100644 index 000000000..b4fa6f3c6 --- /dev/null +++ b/feed.xml @@ -0,0 +1,8415 @@ + + + + 디피의 개발일지 + 학습 및 프로젝트 기록 블로그 React, Spring + https://seongil-shin.github.io/ + + Tue, 03 Sep 2024 21:55:31 +0900 + Tue, 03 Sep 2024 21:55:31 +0900 + Jekyll v4.3.3 + + + JS bundler, package manager, compiler + <p>프론트엔드 관련 아티클을 읽다보면 다양한 bundler, package manger, compiler 도구에 대한 얘기가 나온다. 그때마다 익숙지않은 도구의 이름(pnpm, swc, vite 등)이 나오는데 뭐가 뭔지 잘 이해가 안간다. 따라서 각각의 역할이 무엇인지, 어떤 종류가 있고 어떤 장단이 있는지 간단히 정리해보려고 한다.</p> + +<h2 id="bundler">Bundler</h2> + +<p>여러개의 파일을 하나의 파일로 묶어주는 도구이다.</p> + +<p>초기에 브라우저는 모듈 시스템을 지원하지 않았다. <code class="language-plaintext highlighter-rouge">&lt;script&gt;</code> 간 모듈을 내보낼 수 없었고, 전역 객체를 통해서만 서로를 참조할 수 있었다. 하지만 웹 생태계가 커지면서 점점 더 다양한 요구조건이 생기기 시작했다. (최적화, 모듈 시스템, 코드 호환성(polyfill), 전처리 등). 번들러는 이러한 요구사항에 대응하기 위해 태어났다.</p> + +<p>현재 대표적인 번들러는 <code class="language-plaintext highlighter-rouge">webpack</code>, <code class="language-plaintext highlighter-rouge">rollup</code>, <code class="language-plaintext highlighter-rouge">esbuild</code>, <code class="language-plaintext highlighter-rouge">vite</code> 이다</p> + +<p>webpack</p> +<ul> + <li>초창기에 나타난 번들러로, 지속적인 업데이트를 거쳐가며 많이 사용되어왔다</li> + <li>“통합”에 집중하여, 번들을 통합하여 관리하도록 하였다</li> + <li>장점 + <ul> + <li>풍부한 plugin과 loader</li> + <li>강력한 개발서버 (Hot module replacement 등)</li> + <li>Code splitting (파일을 여러 번들로 분리하여 병렬로 스크립트를 로드할 수 있도록 함)</li> + </ul> + </li> +</ul> + +<p>rollup</p> +<ul> + <li>module bundler</li> + <li>“확장”에 집중하여, 작은 코드조각들을 거대하고 복잡한 어플리케이션 혹은 라이브러리로 만들어준다고 소개한다.</li> + <li>같은 소스코드를 다양한 환경에 맞춰 빌드할 수 있기에, 라이브러리를 만들때 큰 도움이 된다</li> + <li>vs webpack + <ul> + <li>webpack은 내부적으로 commonJS를 사용하고, rollup은 es6(+typescript)를 사용한다</li> + <li>초기에 webpack은 commonJS로만 빌드가 가능했다. rollup은 ES6 모듈형태로 빌드할 수 있기에 이는 라이브러리를 만들때 큰 도움이 되었다.</li> + <li>webpack에 비해 좀 더 빠르다. + <ul> + <li>webpack은 모듈을 함수로 감싸서 평가하지만, rollup은 모듈들을 호이스팅하여 한번에 평가한다</li> + </ul> + </li> + <li>조금 더 가볍다 + <ul> + <li>tree shaking은 기본적으로 ES6 코드에서 제대로 동작하기에</li> + </ul> + </li> + <li>CommonJS 코드를 ES6 코드에서 사용할 수 있다</li> + </ul> + </li> +</ul> + +<p>esbuild</p> +<ul> + <li><code class="language-plaintext highlighter-rouge">Go</code>를 기반으로 만들어진 번들러로 성능상 큰 강점이 있다. (JS 기반 번들러보다 10 ~ 100x)</li> + <li>아직 메이저 버전이 릴리스 되진 않았지만, 번들러로서 필수적인 기능은 갖췄다</li> + <li>하지만 설정이 webpack, rollup 만큼 유연하지 못하고 안정성 이슈가 있다</li> +</ul> + +<p>vite</p> +<ul> + <li>esbuild의 단점을 보완한 라이브러리</li> + <li><code class="language-plaintext highlighter-rouge">vue.js</code> 창시자가 만든 <code class="language-plaintext highlighter-rouge">frontend build tool</code>로, 두가지 포인트가 있다 + <ul> + <li><code class="language-plaintext highlighter-rouge">native ES modules</code> 기반의 강력한 개발서버</li> + <li>esbuild로 파일들을 통합하고 rollup을 통해 번들링</li> + </ul> + </li> + <li>esbuild 로 성능을 높이고, rollup으로 번들링의 유연성을 챙겼다</li> + <li>자세한 내용은 일전에 <a href="https://seongil-shin.github.io/posts/react-vite/">작성한 글</a>이 있다</li> + <li>키포인트 + <ul> + <li>개발 서버 구동시간이 0에 가까울 정도로 매우 빠름</li> + <li>모든 CommonJS 및 UMD 파일을 ESM으로 불러올 수 있도록 변환함</li> + <li>별도의 설정없이 다양한 리소스 import 가능</li> + <li>CSS 빌드 최적화</li> + </ul> + </li> + <li>주의할 점 + <ul> + <li>기본적으로 ES6 타겟으로 번들한다. 따라서 ES5 이하를 타겟으로 잡으려면 polyfill 추가가 필요하다. (또는 <a href="https://github.com/vitejs/vite/tree/main/packages/plugin-legacy">@vitejs/plugin-legacy</a> 플러그인 사용)</li> + <li>기본적으로 <code class="language-plaintext highlighter-rouge">index.html</code>이 빌드 시작점이고, 순수 JS 번들 생성을 위해서는 라이브러리 모드가 필요하다</li> + </ul> + </li> +</ul> + +<h2 id="package-manager">Package manager</h2> + +<p>프로젝트에서 사용하는 패키지를 쉽게 설치, 업데이트해주는 도구이다. 대표적으로는 <code class="language-plaintext highlighter-rouge">npm</code>, <code class="language-plaintext highlighter-rouge">yarn</code>, <code class="language-plaintext highlighter-rouge">pnpm</code>이 있다</p> + +<p>npm</p> +<ul> + <li>JS 패키지 매니저의 시초이며 node.js에 내장되어 있기 때문에 별도의 설치가 필요없다</li> + <li>장점 : 오랜 기간 사용되어왔기에 생태계가 풍부하다</li> + <li>단점 : 과거에는 속도, 보안 측면에서 떨어진다는 평가가 있었으나 업데이트를 거치면서 개선되었다</li> +</ul> + +<p>yarn</p> +<ul> + <li>npm과 큰 차이가 없고 프로세스도 거의 동일한 패키지매니저</li> + <li>장점 + <ul> + <li>npm 에 존재하던 보안문제를 해결하여 안전성을 보장하고 있다.</li> + <li>패키지 설치를 병렬로 진행하여 속도가 빠르다</li> + </ul> + </li> +</ul> + +<p>pnpm</p> +<ul> + <li>“performant npm”의 약자로, 효율적인 npm을 표방한다</li> + <li>장점 + <ul> + <li>npm 에서 node_modules에 매번 패키지를 설치했던 것과 달리 global 저장소에 한번만 저장하여 여러 프로젝트에서 같은 패키지를 참조하는 식으로 저장공간을 절약할 수 있다</li> + <li>Ghost Dependency 문제를 해결한다</li> + </ul> + </li> +</ul> + +<h3 id="npx-는-뭘까">npx 는 뭘까?</h3> + +<p>npm을 좀 더 편리하게 사용할 수 있도록 도와주는 도구로, npm 레지스트리에서 원하는 패키지를 실행할 수 있는 도구이다.</p> + +<p>패키지를 설치하지 않더라도 npm 레지스트리에 올라가있는 패키지의 최신버전을 실행 시키고, 자동으로 설치도 된다. 다음과 같은 경우 사용한다.</p> +<ul> + <li>npm run-script 를 사용하지 않고 로컬에 설치된 패키지를 사용할 경우</li> + <li>일회성 명령으로 실행할 경우</li> + <li>gist-based scripts를 실행할 경우</li> + <li>특정 노드 버전의 스크립트를 실행할 경우</li> +</ul> + +<h2 id="참고">참고</h2> +<ul> + <li>https://bepyan.github.io/blog/2023/bundlers</li> + <li>https://velog.io/@sebinn/%ED%8C%A8%ED%82%A4%EC%A7%80-%EB%A7%A4%EB%8B%88%EC%A0%80-%EB%B9%84%EA%B5%90-npm-yarn-pnpm</li> + <li>https://engineering.ab180.co/stories/yarn-to-pnpm</li> +</ul> + + Sat, 31 Aug 2024 19:45:24 +0900 + https://seongil-shin.github.io/posts/JS-bundler,-package-manager,-compiler/ + https://seongil-shin.github.io/posts/JS-bundler,-package-manager,-compiler/ + + #study + + + study, + + js + + + + + How I Use "AI" + <p><a href="https://nicholas.carlini.com/writing/2024/how-i-use-ai.html?utm_source=Nomad+Academy&amp;utm_campaign=8011d84a1b-EMAIL_CAMPAIGN_2024_08_09&amp;utm_medium=email&amp;utm_term=0_4313d957c9-8011d84a1b-357658928&amp;mc_cid=8011d84a1b&amp;mc_eid=71e499ba70#simpleapps">원본</a></p> + +<p>작성자가 언제 어떻게 LLM 을 활용하는지 공유하는 포스트</p> + +<ul> + <li>애플리케이션을 만드는 초기 단계에서 전체적인 베이스를 만드는데 도움을 받았음</li> + <li>새로운 기술의 튜터로써</li> + <li>코드 단순화를 위해</li> + <li>단조로운 작업을 빠르게 처리하기 위해 + <ul> + <li>불규칙적인 작업이더라도 LLM이 패턴을 파악하여 결과물을 내게할 수 있음</li> + </ul> + </li> + <li>자동화작업에 사용가능</li> + <li></li> +</ul> + + Sat, 31 Aug 2024 19:45:24 +0900 + https://seongil-shin.github.io/posts/How-I-Use-AI/ + https://seongil-shin.github.io/posts/How-I-Use-AI/ + + #study + + + study, + + article + + + + + Esbuild to reduce build times by 90% + <p><a href="https://blog.1password.com/new-extension-build-system/">원본</a></p> + +<p>기존에 빌드가 오래 걸렸던 원인들</p> +<ul> + <li>웹팩, 롤업 작업 시간이 대부분이었음</li> + <li>작은 의존성들이 하나씩 빌드됨. 이것들은 동시에 진행될 여지가 있음</li> + <li>몇몇 의존성은 필요한 것보다 오래 빌드됨.</li> + <li>변경되지 않은 파일에 대해서 typeshare 를 재실행하지 않기위해 find를 실행했으나, 그냥 typeshare를 실행하는 것보다 find 실행시간이 더 길었음</li> +</ul> + +<p>해커톤을 통해 몇몇 시도한 해결책</p> +<ul> + <li>esbuild를 webpack, rollup의 로더로 사용하여 성능 개선을 이루어냄. + <ul> + <li>rollup 에서는 80%의 성능개선을 이루어냄</li> + </ul> + </li> + <li>esbuild로 webpack, rollup을 완전히 대체함 -&gt; 90%의 성능개선</li> +</ul> + +<p>실제 서비스 적용</p> +<ul> + <li>esbuild, with typechecking + <ul> + <li><code class="language-plaintext highlighter-rouge">tsc</code>가 너무 느린 문제를 해결하기위해, <code class="language-plaintext highlighter-rouge">esbuild-plugin-typecheck</code>를 사용함.</li> + <li>이를 통해 논블록킹으로 tsc를 실행가능하게 한다</li> + <li>여기에 더해 에러메세지를 포맷팅하여 가독성을 높였고, 모든 빌드 입력 파일이 제대로 타입 체킹되었는지 확인하는 로직을 추가함</li> + </ul> + </li> + <li>production bundle size improvements + <ul> + <li>esbuild의 <a href="https://esbuild.github.io/analyze/">bundle size analyzer</a>를 사용하여 불필요하게 번들 사이즈를 늘리는 원인을 찾아내어 크기를 줄일 수 있었다</li> + </ul> + </li> +</ul> + + Sat, 31 Aug 2024 19:45:24 +0900 + https://seongil-shin.github.io/posts/Esbuild-to-reduce-build-times-by-90/ + https://seongil-shin.github.io/posts/Esbuild-to-reduce-build-times-by-90/ + + #study + + + study, + + article + + + + + 실용주의프로그래머 - 전체 후기 + <p>이 책은 프로젝트 과정 전반에 거쳐 소프트웨어 전문가답게 진행하는 방법에 대해 서술되어있다. 전반적으로 이해가 잘되도록 쓰여져 있고, 현인의 어록과도 같은 책이라 참고할만한 부분도 많이 있다.</p> + +<p>하지만 조직문화나 권한의 한계, 물리적인 시간의 한계로 모든 팁을 다 따르기는 어렵다. 또한 이러한 내용은 완전히 습관이 되어야하는데, 긴 책 내용을 전부 기억하기는 현실적으로 어렵다. 따라서 현재 내 상황에서 실천 가능한 팁들을 간단히 정리하였다.</p> + +<p>이 문서는 1차, 2차, … 로 비정기적으로 업데이트 해나갈 것이다. 다음 업데이트는 이전 차수 내용이 완전히 체화 되었을 때이다.</p> + +<h2 id="실천-가능한-팁-정리---1차">실천 가능한 팁 정리 - 1차</h2> + +<ul> + <li>팀 내 구성원끼리 서로를 믿을 수 있어야한다. 신뢰가 기반이 되어야 안전하게 아이디어를 교류할 수 있다.</li> + <li>자신이 작성한 코드에 책임을 져라. 어설픈 변명 대신 대안을 내라. 그저 모르겠다는 대답은 하지마라</li> + <li>깨진 창문을 방치하지마라. 당장 시간이 없다면 주석이나 메모라도 작성하여 인지는 하라</li> + <li>어떤 변화를 만들고 싶으면 작은 성공을 먼저 보여줘라. (Topic 4)</li> + <li>당장의 일에만 정신을 쏟지말고 큰 그림을 기억하라</li> + <li>피드백 주기를 만들어라 + <ul> + <li>회귀 테스트, 의뢰인 피드백, 짧은 배포주기</li> + <li>모호한 요구사항에 대해서도 여러번 질문을 하여 피드백받아 구체화하라</li> + <li>너무 먼 미래를 내다보지 말고 피드백을 받아라</li> + </ul> + </li> + <li>지식 포트폴리오를 만들어라 : 주기적인 투자, 리스크 관리, 다각화, 리밸런싱</li> + <li>문서는 애초에 포함하고 나중에 집어넣으려고 하지 말라 (ex : swagger가 좋은 예시)</li> + <li>에디터를 잘 활용하라. 자주 수동으로 사용하는 동작을 단축키로 해결할 수 있는지 찾아봐라</li> + <li>변경하기 쉬운 설계를 하라 + <ul> + <li>DRY : 코드를 포함한 문서, 데이터, 주석, 개발자 간의 주석을 줄여라 (Topic 9)</li> + <li>직교성 : 코드 사이의 결합도를 줄여라. 전역 데이터도 결합이다. 데메테르 법칙(<code class="language-plaintext highlighter-rouge">.</code>은 하나만) + <ul> + <li>상속 대신 인터페이스, 위임, 믹스인을 사용하는 것이 결합도가 낮다.</li> + </ul> + </li> + <li>프로그램을 데이터 변환 모델이라고 생각하라</li> + </ul> + </li> + <li>PERT 기법 : 최상, 일반, 최악의 시나리오별로 예상되는 추정치를 전달하는 방법</li> + <li>엔지니어링 일지를 적어라 : 디버깅 기록이나 엉뚱한 생각이나 어떤 것이든 기억할만한 무엇이든 적어도 됨</li> + <li>있을 수 없는 일이 발생했다면 최대한 빨리 멈춰라. 빨리 멈추는게 계속 작동하는 것보다 좋다 + <ul> + <li>있을 수 없는 일을 검사하기 위해 단정문을 사용할 수 있다. 단정문에 걸리면 최대한 빨리 멈춰라</li> + </ul> + </li> + <li>무언가 불안한 느낌이 들면, 왜 그런 느낌이 드는지 이유를 천천히 생각해보고, 테스트해봐라</li> + <li>잘못된 가정으로 코딩을 하지마라. 가정을 세웠다면 그것을 증명하라</li> + <li>속성 기반 테스트를 통해 예상치못한 상태를 검증할 수 있다.</li> + <li>리팩터링은 일찍 자주 습관적으로 하라.</li> + <li>테스트는 코드의 첫번째 사용자다. 테스트를 생각하는 것만으로도 큰 도움이 된다.</li> + <li>이름을 지을땐 코드의 역할에 따라, 일관적으로 지어라.</li> + <li>어려운 문제를 맞이했을때, 불필요한 제약조건을 세우진 않았는지 생각하라</li> + <li>어려운 문제를 맞이했을때, 잠시 숨을 돌리거나 고무오리 기법을 사용하는 것도 좋다</li> + <li>일상적으로 반복되는 작업을 자동화하라</li> + <li>특정한 방법론에 얽매이지말고, 팀에 잘 맞는 방법론의 좋은 점만 취하여라</li> + <li> + <p>사용자가 기대하는 것이 무엇인지 계속 생각하라. 그것이 진짜 목표이다.</p> + </li> + <li>하나의 메서드, 컴포넌트를 작성한 이후에는 어떻게 테스트할지를 생각해보자</li> + <li>중요한 메서드, 컴포넌트에는 테스트를 추가하자 (속성기반 테스트, 단위테스트 등)</li> + <li>반복되는 작업이 보이면 자동화하자</li> +</ul> + + Sun, 11 Aug 2024 21:07:38 +0900 + https://seongil-shin.github.io/posts/%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8-%EC%A0%84%EC%B2%B4-%ED%9B%84%EA%B8%B0/ + https://seongil-shin.github.io/posts/%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8-%EC%A0%84%EC%B2%B4-%ED%9B%84%EA%B8%B0/ + + #study + + #review + + + study, + + 실용주의프로그래머 + + + + + 실용주의프로그래머 9장 실용주의 프로젝트 + <ul> + <li>Topic 49 : 프로젝트 참여인원이 복수가 되었을때 실용적으로 각 부분을 위임하는 법</li> + <li>Topic 50 : 팀에게 잘 맞는 방법론을 사용하고 있는가</li> + <li>Topic 51 : 안정적인 소프트웨어를 지속적으로 생산해내는 방법</li> + <li>Topic 52 : 사용자를 기쁘게하는 방법</li> + <li>Topic 53 : 자신의 작업에 자부심을 가져라</li> +</ul> + +<h2 id="topic-49--실용주의-팀">Topic 49 : 실용주의 팀</h2> + +<p>실용주의 팀은 작아야한다. 구성원은 대략 10~12명이고, 구성의 변경이 적어야한다. 서로를 신뢰하고 의존할 수 있어야한다.</p> + +<blockquote> + <p>작고 안정적인 팀을 유지하라</p> +</blockquote> + +<p>원하는 바를 실현할 수 있는 환경에 실용주의 개발자 그룹이 만들어진다면, 자신들에게 맞는 역학을 금방 만들고 또 다듬어 낼 것이다</p> + +<h3 id="깨진-창문을-없애라">깨진 창문을 없애라</h3> + +<p>자질구레하게 계속되는 문제를 고치는데 열정을 유지하기 힘들다</p> + +<p>팀 전체가 깨진 창문을 용납해서는 안된다. 반드시 제품의 품질에 책임을 져야한다.</p> + +<h3 id="삶은-개구리">삶은 개구리</h3> + +<p>팀은 개인보다 더 삶은 개구리가 되기 쉽다. 모든 사람이 적극적으로 환경 변화를 감시하도록 권장하라. 범위의 확장, 일정 단축, 추가기능 등 기존에 인지하던 것과 다른 것들을 늘 깨어서 의식해야한다.</p> + +<p>변화를 거부하지 않아도 된다. 단지 그 변화를 인식하고는 있어라</p> + +<h3 id="여러분의-지식-포트폴리오를-계획하라">여러분의 지식 포트폴리오를 계획하라.</h3> + +<p>성공을 원하는 팀이면 자신들의 지식과 기술에 투자해야한다. +이때는 계획을 세워서 해야하는데, 시간이 남으면 하겠다는 안하겠다는 말과 같다. 계획을 세울 때 기능개발로만 몽땅 채우지말라. 대신에 아래 것들을 고려하라</p> + +<ul> + <li>구형 시스템 유지보수</li> + <li>프로세스 회고와 개선</li> + <li>새로운 기술 탐험 : 다들 쓰니까 쓴다는 이유보단 팀에 잘 맞는 것이 뭘지 조사하라</li> + <li>학습 및 기술 갈고 닦기</li> +</ul> + +<h3 id="팀의-존재를-소통하라">팀의 존재를 소통하라</h3> + +<p>팀이라는 것 역시 더 큰 조직의 일부이다. 외부사람들에게 과묵한 프로젝트팀이야말로 최악이다. 나머지 팀들과 의사소통하라.</p> + +<h3 id="반복하지-말라">반복하지 말라</h3> + +<p>팀원 간의 중복된 일을 없애기는 어렵다. 좋은 의사소통을 통해 이 문제를 피할 수 있다. 팀 동료에게 질문을 하고 거의 즉각적으로 답을 받을 수 있어야한다. 질문을 하기위해 회의를 기다려야하면 의사소통이 어려워진다.</p> + +<p>DRY를 지키려면 서로 관심을 유지하라</p> + +<h3 id="팀-예광탄">팀 예광탄</h3> + +<p>시스템의 끝에서 끝까지 전체에 걸쳐있는 단일 기능을 개발하라. 이 작업을 맡은 모든 구성원이 함께 일하는 것이 편안하고 익숙해야한다.</p> + +<h3 id="자동화">자동화</h3> + +<p>일상적으로 반복되는 일을 자동화하라. 수작업으로 하는 것보다 훨씬 일관적이고 정확하다</p> + +<h3 id="멈춰야-할-때를-알라">멈춰야 할 때를 알라</h3> + +<p>각 팀원이 자신의 방식대로 빛나게 하라. 프로젝트의 가치를 만들어 내기에 딱 좋을 만큼의 구조를 제공하라</p> + +<hr /> + +<h2 id="topic-50--코코넛만으로는-부족하다">Topic 50 : 코코넛만으로는 부족하다</h2> + +<p>화물 숭배에 빠지지 말라</p> + +<h3 id="맥락의-중요성">맥락의 중요성</h3> + +<p>특정한 개발 방법이나 프레임워크를 굳이 사용하는 이유가 무엇인가? 유명해서인가? 아니면 현재 프로젝트 또는 팀에 잘 맞아서인가?</p> + +<blockquote> + <p>유행하는 것이 아니라 실제로 잘 맞는 것을 사용하라</p> +</blockquote> + +<p>유명한 팀들이 성공한 방식을 따라하기보다는 맥락을 보고 잘 맞는 것을 사용하라.</p> + +<p>잘 맞는 방법론이더라도 다 정확히 따라야할 필요는 없다. 좋은 것만 취하고 안맞는 것은 버려라</p> +<h3 id="진짜-목표">진짜 목표</h3> + +<p>진짜 목표는 “에자일을 한다”, “린을 한다” 가 아니라 사용자에게 작동하는 소프트웨어를 제공하는 것이다.</p> + +<p>CICD가 불가능한 목표라고 생각하는 팀은 많다. 하지만 모든 목표가 그렇듯 계속 올바른 방향을 바라보는 것이 중요하다. 제품 출시 주기를 조금씩 줄여나가라. 최종적으로는 필요할때마다, 사업적으로 의미 있을때마다 배포하는 것이 목표이다.</p> + +<blockquote> + <p>사용자에게 필요할 때 제공하라</p> +</blockquote> + +<p>이런 지속적 개발을 위해선 feature 브랜치가 아니라 main 또는 trunk 브랜치에서 개발해야한다. 그리고 사용자에게 시범적 기능을 제공할 때는 ‘기능 스위치’ 같은 기법을 활용하라</p> + +<hr /> + +<h2 id="topic-51--실용주의-시작-도구">Topic 51 : 실용주의 시작 도구</h2> + +<blockquote> + <p>생각없이 행할 수 있는 중요한 작업의 수가 늘어남에 따라 문명은 발전한다</p> + <ul> + <li>엘프리드 노스 화이트헤드</li> + </ul> +</blockquote> + +<p>프로젝트에서 거듭 발생하는 일상적인 작업은 모두 자동화해야한다. 수작업은 운에 맡기는 것이고, 반복 가능성도 보장 받지 못하다. 그 작업의 절차를 여러 사람이 서로 다르게 해석할 여지가 있으면 더욱 그렇다</p> + +<p>여기에는 아래와 같은 프로젝트를 견고하게 만들어주는 도구들이 있다.</p> + +<ul> + <li>버전관리 + <ul> + <li>프로젝트를 빌드하는데 필요한 모든 것을 버전 관리하에 두어야한다. 이렇게하면 빌드 장비를 일시적으로 쓰고 버릴 수 있다.</li> + <li>배포 설정도 버전관리 하에 있으므로 서비스 릴리즈도 자동으로 처리된다.</li> + <li>버전 관리 시스템으로 빌드, 테스트, 릴리스를 운용하라.</li> + </ul> + </li> + <li>회귀 테스트 + <ul> + <li>일찍, 자주, 자동으로 테스트하라.</li> + <li>테스트는 코드가 완성되었다는 확신을 줄 수도 있다.</li> + <li>테스트 환경은 실제 환경과 최대한 비슷하게 설정해야한다. 두 환경의 차이에서 버그가 생긴다.</li> + <li>테스트의 몇가지 주요 유형 + <ul> + <li>단위테스트</li> + <li>통합테스트 : 시스템에서 버그가 제일 많이 나오는 것이 모듈을 통합하는 부분이다.</li> + <li>유효성 평가 및 검증 : 시스템의 기능적 요구사항을 충족하는지 테스트해야한다. 잘못된 문제를 푸는 것은 아닌지, 최종 사용자의 접근 방식이 개발자의 테스트 데이터와 어떻게 다른지에 대해 관심을 가져라.</li> + <li>성능 테스트</li> + <li>테스트를 테스트하라 : 버그를 심어놓고 테스트가 정상적으로 동작하는지 확인하라</li> + <li>철저한 테스트 : 코드 커버리지만 올리지 말고 상태 조합을 테스트하라</li> + <li>속성 기반 테스트 : 컴퓨터가 상태를 생성하도록 하여 예상치 못한 상태를 테스트하여라</li> + </ul> + </li> + <li>버그는 한번만 잡아라 + <ul> + <li>인간 테스터가 버그를 찾았다면 더이상 인간 테스터가 그 버그를 만나선 안된다.</li> + <li>해당 버그를 자동화 테스트에 포함시켜라</li> + </ul> + </li> + </ul> + </li> + <li>전체 자동화 + <ul> + <li>수작업 절차를 사용하지 말라.</li> + <li>‘하나만’ 이라는 생각으로 수작업 절차를 추가하면 커다란 창문을 깨는 것이다</li> + </ul> + </li> +</ul> + +<hr /> + +<h2 id="topic-52--사용자를-기쁘게-하라">Topic 52 : 사용자를 기쁘게 하라</h2> + +<p>소프트웨어 프로젝트 자체가 아니라, 사용자가 실제로 기대하는 것에 진짜 의미가 있다. 소프트웨어는 이런 목적을 달성하기위한 수단일 뿐이다.</p> + +<p>이런 숨겨져있는 프로젝트 배후의 가치에 대해 사용자가 기대하는 바의 일부가 드러났다면, 이런 기대를 어떻게 충족시킬 수 있을지 고민하라</p> + +<ul> + <li>모든 팀 구성원이 이러한 기대를 이해해야한다</li> + <li>결정을 내릴때 어떤 것이 기대를 충족시킬 수 있는지 생각하라</li> + <li>이런 기대를 염두에 두고 요구사항을 비판적으로 분석하라. 요구사항을 바꾸는 것도 제안하라</li> + <li>프로젝트 진행 중에도 계속 사용자의 기대에 대해 생각하라</li> +</ul> + +<blockquote> + <p>사용자를 기쁘게하라. 그저 코드만 내놓지 말라</p> +</blockquote> + +<hr /> + +<h2 id="topic-53--오만과-편견">Topic 53 : 오만과 편견</h2> + +<p>실용주의 프로그래머는 책임을 회피하지 않는다. +자신의 코드만 좋게보지말고 동료들의 코드를 깎아 내리지 말라. +경계심 때문에 자신의 코드를 방어하지 말라. 같은 맥락에서 다른 사람의 코드를 존중해야 한다.</p> + +<p>자신이 만든 코드에 서명을 할 수 있어야하고, 품질을 보증할 수 있어야한다.</p> + +<hr /> + +<h2 id="맺은말">맺은말</h2> + +<p>소프트웨어는 의도치 않게 디스토피아를 만들어낼 수 있다. 코드를 작성할 때마다 두가지 질문을 던져라</p> + +<ol> + <li>사용자를 보호했는가?</li> + <li>나라면 이것을 쓸까?</li> +</ol> + +<p>어떤 창의적인 발상은 윤리적 행위의 경계선을 달리기 시작할 때가 있다. 자신이 그런 프로젝트에 참가했다면, 그 프로젝트의 후원자와 같은 책임이 있다.</p> + +<hr /> + +<h2 id="후기">후기</h2> + +<p>이번 장은 프로젝트 전체적인 관점에서의 실용주의 팁들을 던진 장이다. 말단 개발자인 나로서는 실천하기 어려운 것들도 많지만, 몇가지는 머리 속에 새겨뒀다가 제안을 할 수 있을 거 같다.</p> + +<ul> + <li>Topic 49 : 팀을 실용적으로 운용하는 방법에 대해 알려주었다 + <ul> + <li>작고 안정적인 팀이 서로를 신뢰할 수 있어야한다.</li> + <li>깨진 창문을 용납하지 말라</li> + <li>변화를 인식하라</li> + <li>팀 내부에서는 편안하고 빠른 소통이 이뤄질 수 있도록하고, 팀 외부 사람과도 소통을 잘하라</li> + <li>예광탄, 자동화를 통해 팀 생산력을 올릴 수 있다</li> + </ul> + </li> + <li>Topic 50 : 어떤 방법론, 프레임워크에 매몰되지말고 팀에 잘맞는 것을 취사선택하라. 프로젝트의 진짜 목표에 집중하라</li> + <li>Topic 51 : 버전관리, 회귀 테스트, 자동화라는 도구를 통해 프로젝트를 단단하게 만들 수 있다</li> + <li>Topic 52 : 언제나 사용자의 기대를 충족하는 것을 1순위로 생각하라</li> + <li>Topic 53 : 자신의 작성한 코드의 품질을 보증할 수 있어야한다.</li> +</ul> + +<p>topic 50, 52, 53은 태도에 대한 얘기로 본다. 이부분은 머릿속에 기억하여 항상 실천하려는 자세를 취할 수 있다.</p> + +<p>topic 49, 51 에서도 기억하면 팀 또는 프로젝트에 개선할 부분을 더 잘 찾을 수 있을 거 같다.</p> + + Sun, 11 Aug 2024 19:33:56 +0900 + https://seongil-shin.github.io/posts/%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8-9%EC%9E%A5-%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8/ + https://seongil-shin.github.io/posts/%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8-9%EC%9E%A5-%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8/ + + #study + + + study, + + 실용주의프로그래머 + + + + + 실용주의프로그래머 8장 프로젝트 전에 + <h2 id="topic-45--요구사항의-구렁텅이">Topic 45 : 요구사항의 구렁텅이</h2> + +<blockquote> + <p>자신이 뭘 원하는지 정확히 아는 사람은 아무도 없다.</p> +</blockquote> + +<h3 id="요구사항-미신">요구사항 미신</h3> + +<p>무엇을 다루든 정확한 명세란 것은 거의 불가능하다. 이때 프로그래머는 사람들이 자신이 무엇을 원하는지 깨닫도록 도와줄수 있다.</p> + +<blockquote> + <p>프로그래머는 사람들이 자신이 원하는 바를 꺠닫도록 돕는다.</p> +</blockquote> + +<h3 id="상담-치료로서의-프로그래밍">상담 치료로서의 프로그래밍</h3> + +<p>신입 개발자의 실수는 요구사항을 받고 바로 해결책을 구현하는 것이다.</p> + +<p>어떤 요구사항을 받았을때 여러 경우를 생각하고 되물어 확인하자 (사소하여 짜증날거 같은 질문이라해도)</p> +<ul> + <li>요구사항: 5만 원 이상인 모든 주문은 배송비가 무료이다</li> + <li>질문 : 어떤 종류의 배송비가 무료인지? 국제 배송은? 5만원에 배송비 포함인지? 세금 포함인지? 나중에 기준이 바뀔지?</li> +</ul> + +<p>우리의 역할은 기획자의 말을 해석해서 그로 인한 영향을 다시 알려주는 것이다.</p> + +<h3 id="요구사항은-과정이다">요구사항은 과정이다</h3> + +<blockquote> + <p>요구사항은 피드백을 반복하며 알게 된다.</p> +</blockquote> + +<p>기획자는 우리의 피드백을 받고 자기 생각을 가다듬는다. 이때 피드백을 말로 표현하기 어려울 수도 있다. 그때는 프로토타입이나 목업으로 대화를 진행할 수 있다.</p> + +<p>이러한 요구사항 가다듬기는 프로젝트가 끝날때까지도 이어진다. 따라서 프로젝트 전체를 요구사항 수집 단계로 봐야하고, 짧은 주기로 반복하여 피드백을 자주 받는 것이 좋다.</p> + +<h3 id="요구사항-문서화">요구사항 문서화</h3> + +<p>요구사항 문서는 의뢰인을 위한 것이 아니다. 거대한 요구사항 문서를 만들어도 의뢰인은 읽지 않을 것이다. 요구사항 문서는 개발자를 위해서 세부사항을 명시한 것이다.</p> + +<p>요구사항 문서화는 계획을 위해서도 좋다. 짧은 설명을 인덱스 카드 형식으로 하여 상태에따라 이리저리 옮길 수 있게 할 수 있다. 설명이 부족할 수도 있는데, 이것을 이용해서 개발자로 하여금 의뢰인에게 질문을 던지게 할 수 있다</p> + +<h3 id="지나치게-자세한-명세">지나치게 자세한 명세</h3> + +<p>요구사항을 만들 때는 추상적으로 작성하는 것이 좋다. 모호하게 적으라는 것이 아니라, 의미론적 불변식을 요구사항으로 잘 갈무리하고, 구체적인 작업방식은 정책으로 문서화해야한다.</p> + +<p>요구사항은 아키텍처나 설계가 아니며 필요를 표현하는 것이다</p> + +<h3 id="딱-하나만-더">딱 하나만 더</h3> + +<p>요구사항이 조금씩 늘어나는 것을 막는 방법은 피드백이다. 정기적으로 피드백을 주고받아 요구사항이 늘어날 떄의 영향을 의뢰인이 체험할 수 있도록 하라. 피드백을 주고 받아 추가되는 요구사항을 위해 무엇을 희생 가능할지 함께 고민할 수 있다</p> + +<h3 id="용어사전-관리하기">용어사전 관리하기</h3> + +<p>프로젝트에서 사용하는 용어를 모은 사전을 만들어라. 최종 사용자에서 말단 직원까지 프로젝트에 참가하는 모든 사람이 동일한 용어를 사용해야한다.</p> + +<hr /> + +<h2 id="topic-46--불가능한-퍼즐-풀기">Topic 46 : 불가능한 퍼즐 풀기</h2> + +<p>풀기 어려운 문제는 상상 속이 아닌 실제 제약 조건을 알아내고 그 속에서 해법을 찾아야한다. 어떤 제약 조건은 절대적이라 따라야하지만, 어떤 조건은 단순한 지레짐작이다.</p> + +<h3 id="자유도">자유도</h3> + +<blockquote> + <p>생각의 틀을 벗어나지 말고, 틀을 찾아라</p> +</blockquote> + +<p>모든 선입견을 의심하고 그것이 진짜 바꿀 수 없는 제약인지 가늠해봐야한다. 풀리지 않는 문제를 마주하면 모든 해결책을 생각해봐라. 아무리 쓸모없고 바보 같더라도 버리지 말고 잘 모아두고 왜 쓸모없다고 생각했는지 설명해봐라</p> + +<h3 id="자신만의-방법에서-빠져나와라">자신만의 방법에서 빠져나와라</h3> + +<p>어려운 문제를 마주하고 그것이 풀리지 않는다면 잠시 다른 일을 하라. 무의식적으로 문제가 해결될 수도 있다.</p> + +<blockquote> + <p>간단히 표현하면 딴짓을 한 사람이 의식적으로 노력한 사람보다 복잡한 해결과제를 더 잘 해냈다.</p> + <ul> + <li>사이콜로지 투데이</li> + </ul> +</blockquote> + +<p>만약 손을 놓기 싫다면 다른 사람과 이야기 해봐라</p> +<ul> + <li>왜 이 문제를 풀고 있는가?</li> + <li>문제를 풀어서 얻는 것은 무엇인가?</li> + <li>풀려고 하는 문제가 특수한 경우인가? 특수한 경우를 없앨 수는 없나?</li> + <li>관련된 문제 중 풀 수 있는 더 간단한 문제는 없나?</li> +</ul> + +<h3 id="행운은-준비된-사람에게-찾아온다">행운은 준비된 사람에게 찾아온다.</h3> + +<p>무의식적으로 문제를 해결하기 위해서는 원료가 많아야한다. 엔지니어링 일지처럼 일상적인 작업을 할 때 무엇은 잘되고 무엇은 안되는지 피드백을 줄 수 있다.</p> + +<hr /> + +<h2 id="topic-47--함께-일하기">Topic 47 : 함께 일하기</h2> + +<h3 id="페어-프로그래밍">페어 프로그래밍</h3> + +<p>사람들은 모두 다르기에 다른 배경을 가지고 문제를 푸는데도 다른 접근방법을 사용한다. 주어진 문제에 집중하는 정도나 주의력도 제각각이다. 입력을 담당한 개발자는 문법이나 코딩 스타일에 집중할 수 있고, 다른 개발자는 문제를 더 높은 수준에서 생각할 수 있다.</p> + +<p>뇌의 용량은 한정되어있고, 문법이나 코딩스타일에 맞추는 일은 뇌 용량을 많이 소비한다. 두번째 사람이 전체적인 문제를 생각하는 뇌를 맡는다. 또 두번째 사람의 존재로 인해 안좋은 습관이나 꼼수를 덜 쓰게 된다</p> + +<h3 id="몹-프로그래밍">몹 프로그래밍</h3> + +<p>페어프로그래밍을 넘어 더 많은 사람이 참가할 수도 있다. 이때는 의뢰인처럼 개발팀의 일원이 아닌 사람도 참가할 수 있다</p> + +<h3 id="무엇을-해야할까">무엇을 해야할까?</h3> + +<p>처음에는 어색할 수 있지만 조금씩 시도해볼 수 있다. 이렇게 일하는 동안에는 기술적인 면 외에 인간적인 측면도 신경써야한다.</p> + +<ul> + <li>누가 똑똑한지 겨루지마라. 각자 뛰어난 부분이 있다</li> + <li>소규모로 시작해라</li> + <li>코드만 비판하고 사람을 비판하지 마라. ‘넌 틀렸어’보다 ‘이 부분을 한 번 볼까요?’가 좋다</li> + <li>다른 사람의 관점을 듣고 이해하려고 노력하라.</li> + <li>자주 회고를 하라.</li> +</ul> + +<p>너무 무턱대고 시도하지는 말고, 사례 보고서를 찾아보고 연구하라. 장점과 걱정되는 점에 대해 감을 잡아라.</p> + +<blockquote> + <p>코드에 혼자 들어가지는 말라.</p> +</blockquote> + +<hr /> + +<h2 id="topic-48--애자일의-핵심">Topic 48 : 애자일의 핵심</h2> + +<blockquote> + <p>애자일은 명사가 아니다. 애자일은 무언가를 하는 방식이다.</p> +</blockquote> + +<p>애자일은 일하는 방식이지 우리 자신이 아니다. 애자일 선언에서 언급된 애자일의 가치는 아래와 같다.</p> + +<blockquote> + <p>우리는 소프트웨어를 개발하고, 또 다른 사람의 개발을 도와주면서 소프트웨어 개발의 더 나은 방법들을 찾아가고 있다. 이 과정에서 우리는 다음과 같은 가치들을 찾아냈다.</p> + + <ul> + <li>공정과 도구보다 개인과 상호작용</li> + <li>포괄적인 문서보다 작동하는 소프트웨어</li> + <li>계약 협상보다 고객과의 협력</li> + <li>계획을 따르기보다 변화에 대응하기</li> + </ul> + + <p>왼쪽도 가치가 있지만 오른쪽에 더 높은 가치를 둔다</p> +</blockquote> + +<p>애자일 선언은 고정불변의 문서라기보다는 만들어가는 과정에 대한 제안이다.</p> + +<h3 id="애자일-프로세스라는-것은-있을-수-없다">애자일 프로세스라는 것은 있을 수 없다</h3> + +<p>애자일, 즉 기민함은 변화에 대응하는 것이다. 소프트웨어를 개발할 때 따라야하는 단 한 가지 계획이란 없다. 애자인 선언의 네가지 가치중 세가지가 이처럼 피드백을 수집하고 그에 대응하라는 얘기다.</p> + +<p>이 가치들은 무엇을 하라는 것이 아니라, 할일을 결정할 때 무엇을 추구할지 알려주는 것이다. 그 결정은 상황에 따라 다르다.</p> + +<h3 id="피드백-고리">피드백 고리</h3> + +<p>피드백고리를 만들어라. 이름 짓기 같은 작은 고리라도 피드백고리를 반복하는 것이 중요하다. 매번 작은 단계를 진행하고, 평가하고, 고칠 것이 있으면 고쳐라</p> + +<p>이 과정이 설계를 주도한다. 좋은 설계는 바꾸기 쉬운 결과물을 만들기 때문에 애자일이 잘 작동하려면 좋은 설계를 만들어야한다.</p> + +<hr /> + +<h2 id="후기">후기</h2> + +<p>프로젝트 전, 진행 중에 필요한 팁들을 알려준 장이다.</p> + +<ul> + <li>Topic 45 : 요구사항이라는 것은 기획자가 단순히 던져주는 것이 아니라, 개발자가 함께 만들어가는 것이라고 알게 해주었다. + <ul> + <li>요구사항을 받았으면 바로 구현하기보단, 요구사항을 구현했을때의 영향을 생각하여 되물을 수 있다</li> + <li>요구사항은 피드백을 반복하며 구체화된다</li> + </ul> + </li> + <li>Topic 46 : 풀기 어려운 문제를 맞이하였을 때, 어떤 부분에서 막히는지 확인하라. 그리고 그 부분이 진짜로 제약조건인지 확인하라 + <ul> + <li>문제의 해결책을 의논할 때는 바보 같더라도 모두 모아라. 선입견에 빠지지말고 왜 바보같은지 합리적인 이유가 있는지 확인하라</li> + </ul> + </li> + <li>Topic 47 : 페어프로그래밍, 몹 프로그래밍을 통해 코드를 작성하는 사람과 전체를 보는 사람의 뇌를 분리시켜 코드의 품질을 올릴 수 있다</li> + <li>Topic 48 : 에자일 방법론 형식에 매몰되지 말라. 피드백 고리를 만들고 기민하게 변화하는 것이 에자일의 가치이다.</li> +</ul> + + Wed, 07 Aug 2024 17:32:06 +0900 + https://seongil-shin.github.io/posts/%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8-8%EC%9E%A5-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%A0%84%EC%97%90/ + https://seongil-shin.github.io/posts/%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8-8%EC%9E%A5-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%A0%84%EC%97%90/ + + #study + + + study, + + 실용주의프로그래머 + + + + + 잘못된 DRY + <p>https://velog.io/@eunbinn/dry-the-common-source-of-bad-abstractions</p> + +<ul> + <li>DRY 원칙을 너무 지키다보니 잘못된 추상화를 하는 경우가 있다</li> + <li>바로 서로 성격이 다른 코드이지만, 비슷한 패턴을 가진다는 이유로 같은 코드로 묶는 경우이다.</li> + <li>ex) 네비게이션에 <code class="language-plaintext highlighter-rouge">Article</code>, <code class="language-plaintext highlighter-rouge">Menu</code>, <code class="language-plaintext highlighter-rouge">Buy</code> 버튼이 있을때, <code class="language-plaintext highlighter-rouge">Article</code>, <code class="language-plaintext highlighter-rouge">Menu</code>와 <code class="language-plaintext highlighter-rouge">Buy</code> 버튼은 서로 성격이 다르지만 같은 팩터리 메서드로 묶는다면, 추후 <code class="language-plaintext highlighter-rouge">Buy</code> 버튼만 변경될 우려가 있다.</li> + <li>따라서 서로 다른 성격의 코드는 다르게 묶거나, 다양한 형태에 대응 가능하도록 하는 것이 좋다.</li> +</ul> + + + Tue, 06 Aug 2024 11:17:27 +0900 + https://seongil-shin.github.io/posts/%EC%9E%98%EB%AA%BB%EB%90%9C-DRY/ + https://seongil-shin.github.io/posts/%EC%9E%98%EB%AA%BB%EB%90%9C-DRY/ + + #study + + + study + + + + + 실용주의프로그래머 7장 코딩하는 동안 + <p>코딩은 설계내용을 단순히 코드로 바꾸는 과정이 아니다. 이번 장에서는 프로그램을 정확하게 동작하게 하려면 어떻게 해야하는지 다룬다. 운전할 때와 마찬가지로 코딩도 반복적인 일이지만 정신을 늘 기민하게 유지하여야한다.</p> + +<ul> + <li>topic 37 : 본능과 무의식적인 생각을 더 잘 활용할 수 있다</li> + <li>topic 38 : 우연에 맡기는 프로그래밍을 피하는 방법</li> + <li>topic 39 : 알고리즘의 속도</li> + <li>topic 40 : 리팩터링</li> + <li>topic 41 : 테스트로 코딩하기</li> + <li>topic 42 : 속성 기반 테스트를 통해 고정관념에서 벗어난 테스트하기</li> + <li>topic 43 : 읽고 분석하기 쉬운 코드를 쓰는 법</li> + <li>topic 44 : 이름 짓기</li> +</ul> + +<h2 id="topic-37--파충류의-뇌에-귀-기울이기">Topic 37 : 파충류의 뇌에 귀 기울이기</h2> + +<p>경험이 늘어날수록 무의식적인 지식이 쌓여져가고, 본능적으로 싸늘한 느낌을 느낄 때가 있다. 이럴 때는 그런 반응을 인지하고, 왜 그런 느낌이 드는지를 알아내야한다. 여기서는 그런 느낌이 드는 몇가지 흔한 상황을 살펴보자.</p> + +<h3 id="백지의-공포">백지의 공포</h3> + +<p>어떤 일을 시작할때 두려움을 느끼는 경우가 많다. 이 원인은 두가지가 있다.</p> +<ol> + <li>무의식적으로 어떤 작업을 시작할때 문제점이 있을거 같다고 본능이 말해주는 경우이다. 이때는 시간을 주어 생각해보면 무엇이 문제인지 알수 있다.</li> + <li>단순히 실수할까봐 두려운 걸 수도 있다.</li> +</ol> + +<h3 id="자신과-싸우기">자신과 싸우기</h3> + +<p>어떤 날은 코드를 짜는 것이 매우 힘들 수 있다. 이때는 억지로 계속 작성하지말고, 무언가 문제가 있는지 생각해보자. 구조나 설계가 틀렸는가? 엉뚱한 문제가 아닐까?</p> + +<h3 id="파충류와-이야기하는-법">파충류와 이야기하는 법</h3> + +<blockquote> + <p>여러분 내면의 파충류에게 귀 기울여라</p> +</blockquote> + +<p>일단 하고 있는 일을 멈춰라. 약간의 시간을 두고 뇌가 정리를 할 수 있게 해라. 밥을 먹거나 산책을 해도 된다. 그러면 해결책이 떠오를 때가 있다.</p> + +<p>만약 이래도 안되면 문제를 표면으로 끄집어 내라. 작성하는 코드에 대해 그림을 그려보거나 동료에게 설명해봐라. 고무오리도 좋다.</p> + +<p>이렇게하여도 해결이 안된다면 뇌에게 우리가 하는 일이 문제가 없다는 걸 알려줘야한다. 프로토타이핑의 시간이다.</p> + +<ol> + <li>프로토타이핑 중이라고 포스트잇에 써라</li> + <li>기존 코드가 방해된다면 잠시 치워두어라</li> + <li>프로토타이핑은 원래 실패한다고 상기하고, 성공하더라도 원래 버리는 것이라고 생각하라.</li> + <li>텅 빈 에디터 화면에 하고자하는 것을 주석으로 적어라</li> + <li>코딩을 시작하라</li> +</ol> + +<p>이렇게하면 불안감은 사라지고 빨리 해치우고 싶다는 생각이 들 것이다. 그래도 안된다면 다시 첫단계(휴식)부터 시작하라.</p> + +<h3 id="여러분의-코드뿐이-아니다">여러분의 코드뿐이 아니다.</h3> + +<p>다른 사람의 코드를 읽으면서 중요해보이는 곳에 메모를 하고, 실험을 할 수 있다. 이상한 부분이 있다면 적어놓고 패턴을 찾아보아라. 만약 그렇게 코드를 작성한 이유를 찾는다면 코드를 이해하기가 훨씬 쉬워진다. 다른 사람의 패턴을 여러분이 사용할 수도 있다.</p> + +<h3 id="코드뿐이-아니다">코드뿐이 아니다.</h3> + +<p>설계, 요구사항 등 코드 외적인 것에서도 직감이 발동할 수 있다. 이 직감에 귀를 기울이고 적극적으로 생각하라</p> + +<hr /> + +<h2 id="topic-38--우연에-맡기는-프로그래밍">Topic 38 : 우연에 맡기는 프로그래밍</h2> + +<h3 id="우연에-맡기는-프로그래밍-하기">우연에 맡기는 프로그래밍 하기</h3> + +<p>몇번 돌려보고 문제가 없는거 같아 코딩을 계속하다보면 어느순간 문제가 생기는 경우가 있다. 이경우 원인을 파악하기 힘든데, 왜 코드가 잘 돌아갔는지 몰랐기 때문이다. 이처럼 우연에 맡겨 코딩을 하는 경우가 있다</p> + +<ul> + <li>구현에서 생기는 우연 + <ul> + <li>우연히 현재 존재하는 에러나 예외적인 경우에 의존하여 코드를 짜는 경우가 있다 + <ul> + <li>ex) 외부 라이브러리를 사용하는데 문서화되지않은, 의도하지 않은 동작을 사용. 다음 버전에서 고쳐질 수 있음.</li> + </ul> + </li> + <li>다른 루틴을 호출할 때는 문서화된 동작에만 의존하라. 그럴 수 없다면 추측을 문서로 상세히 남겨라</li> + </ul> + </li> + <li>비슷하다고 괜찮을 리는 없다 + <ul> + <li>항상 시간이 1시간 차이나는 경우, 모든 개발자가 각자 시간을 다룰때 1시간을 더하거나 빼는 식으로 코딩을 할 수 있다. 하지만 이런 코드가 퍼져나가면 더이상 수습이 어렵다</li> + </ul> + </li> + <li>유령 패턴 + <ul> + <li>우연히 발생한 패턴을 기반으로 코드를 짤 수 있다.</li> + <li>가정하지말고 증명하라</li> + </ul> + </li> + <li>상황에서 생기는 우연 + <ul> + <li>현재 환경에서 우연히 잘 동작하는 코드일 수 있다. ex) 네트워크 환경이 안좋은 경우, 서버의 시간이 부정확한 경우</li> + <li>우연에 맡기는 프로그래밍을 하지 말라</li> + </ul> + </li> + <li>암묵적인 가정 + <ul> + <li>가정을 했으면 이를 문서화하라. 가정은 개발자마다 다르며 사실에 근거하지 않은 가정은 재앙의 근원이 된다.</li> + </ul> + </li> +</ul> + +<h3 id="의도적으로-프로그래밍하기">의도적으로 프로그래밍하기</h3> + +<ul> + <li>언제나 지금 무엇을 하고 있는지 의식하라</li> + <li>경험이 적은 프로그래머에게 코드를 설명할 수 있는가?</li> + <li>자신도 잘 모르는 코드를 만들지 말라</li> + <li>간단하게라도 계획을 세워라</li> + <li>가정에 의존하지 마라.</li> + <li>가정을 기록으로 남겨라</li> + <li>가정을 테스트하라.</li> + <li>우선순위를 정하라</li> + <li>기존 코드가 앞으로 짤 코드를 지배하도록 하지 마라.</li> +</ul> + +<hr /> + +<h2 id="topic-39--알고리즘의-속도">Topic 39 : 알고리즘의 속도</h2> + +<h3 id="실전에서의-알고리즘-속도">실전에서의 알고리즘 속도</h3> + +<p>입력값이 얼마나 큰 값이 들어올지 스스로 물어봐야한다. 숫자가 외부 요인에 따라 달라지면, 시간/메모리를 다시 계산하라</p> + +<p>입력 데이터가 작을땐 선형적이어도, 크면 스래싱이 발생하면서 수행시간이 폭증할 수 있다. 이론적으로 추정을 이미 했더라도 실무적으로 문제가 생길 수 있다</p> + +<blockquote> + <p>여러분의 추정을 테스트하라</p> +</blockquote> + +<p>하지만 언제나 가장 빠른 알고리즘이 최고가 아니다. 복잡한 알고리즘을 사용한만큼 디버깅, 유지보수의 시간이 늘어날 수 있다.</p> + +<p>성급한 최적화를 조심하고, 그 알고리즘이 정말로 병목인지 먼저 확인하라</p> + +<hr /> + +<h2 id="topic-40--리팩터링">Topic 40 : 리팩터링</h2> + +<blockquote> + <p><strong>밖으로 드러나는 동작은 그대로 유지</strong>한 채 내부 구조를 변경함으로써 이미 존재하는 코드를 재구성하는 <strong>체계적</strong> 기법</p> + <ul> + <li>마틴 파울러</li> + </ul> +</blockquote> + +<h3 id="리팩터링은-언제-하는가">리팩터링은 언제 하는가?</h3> + +<p>무언가를 더 많이 알게 되었을때, 더이상 코드가 잘 맞지 않을때, 무엇이든 잘못되었다는 생각이 들 때 수행한다.</p> + +<ul> + <li>중복</li> + <li>직교적이지 않은 설계</li> + <li>더이상 유효하지 않은 지식</li> + <li>사용 사례</li> + <li>성능</li> + <li>테스트 통과 : 테스트가 통과하여 이상 없음을 발견하였을때 코드를 정리하기 좋은 타이밍이다.</li> +</ul> + +<p><strong>현실적인 문제</strong></p> + +<p>일정의 압박은 리팩터링을 하지 않는 변명이 될 수 있다. 하지만 리팩터링을 하지 않으면 추후 더 큰 문제가 될 수 있다. (마치 종양처럼)</p> + +<blockquote> + <p>일찍 리팩터링하고, 자주 리팩터링하라</p> +</blockquote> + +<p>문제가 작을때 코딩하는 동안 함께 진행하는 편이 더 쉽다.</p> + +<h3 id="리팩터링은-어떻게-하는가">리팩터링은 어떻게 하는가?</h3> + +<p>리팩터링의 본질은 재설계이다. 하지만 거대한 코드를 헤집어 놓으면 더 안좋은 상황이 생길 수 있다. 리팩터링은 천천히 신중하게 진행해야하는 작업이다. 관련하여 마틴 파울러의 조언이 몇가지 있다</p> + +<ol> + <li>리팩터링과 기능 추가를 동시에 하지 말라</li> + <li>리팩터링을 하기 전에 든든한 테스트가 있는지 확인하라. 가능한 자주 테스트를 돌려라.</li> + <li>단계를 작게 나누어서 신중하게 작업하라. 한 단계가 끝날때마다 테스트를 돌리는 것이 좋다.</li> +</ol> + +<hr /> + +<h2 id="topic-41--테스트로-코딩하기">Topic 41 : 테스트로 코딩하기</h2> + +<blockquote> + <p>테스트는 버그를 찾기 위한 것이 아니다</p> +</blockquote> + +<p>테스트의 이득은 테스트를 실행할 때가 아니라, 테스트를 생각해내고 작성할 때 생긴다</p> + +<h3 id="테스트가-코딩을-주도한다">테스트가 코딩을 주도한다</h3> + +<p>테스트 작성에 대해 생각하는 것은 코드의 작성자가 아니라 사용자인 것처럼 메서드를 외부의 시선에서 볼 수 있게 해준다.</p> + +<blockquote> + <p>테스트가 코드의 첫번째 사용자다</p> +</blockquote> + +<p>다른 코드와 긴밀하게 결합된 메서드는 테스트하기가 어렵다. 메서드를 실행하기도 전에 환경 구성을 한참해야하기 때문이다. 따라서 테스트하기 좋게 만들면 결합도도 내려간다.</p> + +<p>또 무언가를 테스트하려면 그것을 이해해야한다. 코딩을 시작하기 전에 경계조건 테스트를 생각한다면 함수를 단순하게 만드는 패턴을 찾을 수 있을 것이다. 테스트해야하는 오류조건을 먼저 생각하면 그에 맞게 함수구조를 잡을 것이다</p> + +<p><strong>테스트 주도 개발</strong></p> + +<ol> + <li>추가하고 싶은 작은 기능을 하나 결정한다</li> + <li>그 기능이 구현되었을때 통과할 테스트를 하나 작성한다</li> + <li>테스트를 실행한다. 방금 추가한 테스트는 실패해야한다.</li> + <li>실패하는 테스트를 통과하는 최소한의 코드를 작성한다. 이후 테스트가 전부 통과하는지 확인한다</li> + <li>코드를 리팩터링한다. 방금 작성한 테스트나 함수를 개선할 수 있을지 살펴본다. 개선 후에도 잘 테스트를 통과하는지 확인한다.</li> +</ol> + +<p>TDD는 이를 매우 짧은 주기로 반복하여 끊임없는 테스트 작성과 통과를 반복한다. 이는 테스트를 막 배운 사람에게는 효과가 크다. 하지만 이 TDD의 노예가 된 사람들이 있다.</p> + +<ul> + <li>테스트 커버리지 100% 채우기위해 과도한 시간을 투자한다</li> + <li>많은 수의 중복 테스트가 생긴다.</li> + <li>밑에서부터 시작하여 위로 올라가는 방식으로 설계를 한다.</li> +</ul> + +<p>TDD를 실천하되, 너무 중독되지는 말자</p> + +<h3 id="tdd--목표가-어디인지-알아야한다">TDD : 목표가 어디인지 알아야한다</h3> + +<p>TDD는 전체 문제를 작은 단계로 나누어 접근하도록 해주지만, 반대로 전체 문제를 보지 않고 작은 문제에 매몰 되게도 할 수 있다.</p> + +<p>테스트를 진행할 때마다 목적지를 항상 떠올려야한다</p> + +<h3 id="단위테스트">단위테스트</h3> + +<p>소프트웨어를 컴포넌트로 나누어 조립하여 구성하는 방식은 자주 쓰인다. 하지만 이때 각 컴포넌트는 연결되기 전에 철저하게 테스트 되어야한다.</p> + +<p>이때 각 컴포넌트는 단위 테스트를 거칠 수 있다. 단위 테스트를 통과한다면 조립하더라도 각 개별 부분은 정상 동작한다고 믿을 수 있다.</p> + +<p>이때 단위테스트로 계약을 지키는지, 코드로 표현된 계약이 우리가 생각한 것과 일치하는지 확인할 수 있다. 다양한 종류의 케이스와 경계조건 케이스를 가지고 테스트 해볼 수 있다.</p> + +<h3 id="임시-테스트">임시 테스트</h3> + +<p>임시테스트는 <code class="language-plaintext highlighter-rouge">console.log()</code> 나 디버거 등을 직접 실행해보면서 입력하는 코드 조각일 수 있다. 디버깅이 종료되면 이러한 임시테스트는 정식 테스트의 형태로 만들어 놔야한다.</p> + +<h3 id="테스트-접점-만들기">테스트 접점 만들기</h3> + +<p>배포한 후에도 테스트할 일이 자주 생긴다. 이때 디버깅을 쉽게하기 위해 다음 방법을 사용할 수 있다</p> +<ul> + <li>로그 : 일관적이고 파싱하기 쉬워야한다</li> + <li>단축키 조합 : 특정 단축키, URL로 접근 시 디버거 창이 뜨도록 만들 수 있음</li> + <li>기능 스위치 : 특정 사용자에게는 더 많은 진단 메세지를 남기기</li> +</ul> + +<h3 id="테스트-문화">테스트 문화</h3> + +<p><strong>우리가 테스트 안하면 사용자가 테스트하게 된다</strong>. 선택지는 세개다</p> +<ul> + <li>테스트 먼저 : 최상의 선택으로, TDD가 포함된다.</li> + <li>테스트와 코드를 함께 : 테스트를 먼저 쓰기가 어려울 경우 사용가능하다.</li> + <li>테스트하지 않음</li> +</ul> + +<p>제대로 된 테스트 문화를 가졌다면 모든 테스트가 항상 통과해야한다. 실패해도 무시하는 테스트가 하나 생기면 점점 더 많이 무시하게 된다</p> + +<hr /> + +<h2 id="topic-42--속성-기반-테스트">Topic 42 : 속성 기반 테스트</h2> + +<p>우리가 작성한 코드를 스스로 테스트하다보면, 잘못된 가정하에서 테스트를 할 수 있다. 하지만 그렇다고 다른 사람에게 테스트를 맡기면, 테스트를 생각할때 나오는 이점이 없어진다.</p> + +<p>이 문제를 해결하기 위해서는 컴퓨너에게 테스트를 맡기는 방법이 있다.</p> + +<h3 id="계약-불변식-속성">계약, 불변식, 속성</h3> + +<p>속성 = 계약 + 불변식</p> +<ul> + <li>계약 : 선행조건에 맞추어 입력을 넣으면, 출력이 후행 조건에 맞음을 보장해줌</li> + <li>불변식 : 함수 실행 전후로 계속 참이 되는 조건 (ex : 정렬 함수에서 리스트의 원소의 개수는 항상 같음)</li> +</ul> + +<p>코드에서 속성을 찾아내서 테스트 자동화에 사용할 수 있는데, 이걸 속성 기반 테스트라고 한다.</p> + +<blockquote> + <p>속성 기반 테스트로 가정을 검증하라.</p> +</blockquote> + +<p>파이썬에서는 Hypothesis 라이브러리와 pytest로 이런 테스트가 가능하다.</p> + +<h3 id="테스트-데이터-생성">테스트 데이터 생성</h3> + +<p>대부분의 속성 기반 테스트에서는 데이터를 어떻게 생성할지 지정하는 작은 언어가 있다</p> + +<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +</pre></td><td class="rouge-code"><pre><span class="nd">@given</span><span class="p">(</span><span class="n">some</span><span class="p">.</span><span class="nf">integers</span><span class="p">())</span> +<span class="nd">@given</span><span class="p">(</span><span class="n">some</span><span class="p">.</span><span class="nf">integers</span><span class="p">(</span><span class="n">min_value</span><span class="o">=</span><span class="mi">5</span><span class="p">,</span> <span class="n">max_value</span><span class="o">=</span><span class="mi">10</span><span class="p">).</span><span class="nf">map</span><span class="p">(</span><span class="k">lambda</span> <span class="n">x</span><span class="p">:</span> <span class="n">x</span> <span class="o">*</span> <span class="mi">2</span><span class="p">))</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<h3 id="속성-기반-테스트는-우리를-자주-놀래킨다">속성 기반 테스트는 우리를 자주 놀래킨다</h3> + +<p>속성 기반 테스트는 입력을 생성하는 규칙과 출력을 검증하는 단정문만 설정한 채 알아서 동작한다. 따라서 정확히 어떤 일이 일어날지 모른다.</p> + +<p>만약 속성 기반 테스트가 실패했다면 테스트 함수가 어떤 매개변수를 사용했는지 알아낸다음 그 값을 이용하여 단위테스트를 추가하는 것이 좋다.</p> +<ul> + <li>속성 기반 테스트의 여러가지 다른 수행결과와 상관없이 문제가 발생하는 상황에 집중할 수 있게 해준다.</li> + <li>속성 기반 테스트는 임의의 값을 생성하기에 다음번에 해당 값이 생성될 보장은 없기 때문에.</li> +</ul> + +<h3 id="속성-기반-테스트는-설계에도-도움을-준다">속성 기반 테스트는 설계에도 도움을 준다</h3> + +<p>단위 테스트는 메서드의 첫번째 사용자로서 코드에 대해 생각하게 해준다.</p> + +<p>속성 기반 테스트는 코드를 불변식과 계약이라는 관점으로 바라보게 해준다. 이로써 경계조건은 줄어들고 데이터의 일관성을 해치는 함수는 도드라진다.</p> + +<p>둘은 서로를 보완하며 서로 다른 문제를 해결해준다.</p> + +<hr /> + +<h2 id="topic-43--바깥에서는-안전에-주의하라">Topic 43 : 바깥에서는 안전에 주의하라</h2> + +<h3 id="나머지-90">나머지 90%</h3> + +<p>코드를 완성한 이후 코드가 잘못될 수 있는 경우를 찾아봐라. 그리고 각 경우에 대한 단위 테스트를 추가해야한다</p> +<ul> + <li>잘못된 매개변수를 넘기는 경우</li> + <li>리소스를 흘리거나, 리소스가 모자라는 경우</li> +</ul> + +<p>하지만 이렇게 내부 오류를 찾는것만으로는 불충분하다. 외부의 공격에도 대비해야한다</p> + +<h3 id="기본-보안-원칙">기본 보안 원칙</h3> + +<ol> + <li>공격 표면을 최소화하라 + <ol> + <li>공격자가 데이터를 입력/추출할 수 있거나, 서비스를 실행시킬 수 있는 모든 접근지점</li> + <li>코드가 복잡할수록 공격지점은 많아진다. 코드는 단순할수록 좋다</li> + <li>입력데이터를 신뢰하지마라</li> + <li>인증없는 서비스는 공격 매개체다</li> + <li>인증을 요구하는 서비스도 공격 매개체다. + <ol> + <li>인증 받은 사용자수를 최소로 유지하고, 유효하지 않은 유저, 서비스를 정리하라</li> + </ol> + </li> + <li>출력데이터도 공격 매개체다. (ex : 오류메세지로 ‘다른 사용자가 사용중인 비밀번호’) + <ol> + <li>응답에 들어있는 데이터가 사용자의 권한에 적절한지 늘 확인하라.</li> + </ol> + </li> + <li>디버깅 정보는 공격 매개체다</li> + </ol> + </li> + <li>최소 권한 원칙 + <ol> + <li>최소한의 권한을 꼭 필요한 시간만큼만 제일 짧게 부여하라</li> + </ol> + </li> + <li>안전한 기본값</li> + <li>민감 정보를 암호화하라</li> + <li>보안 업데이트를 신속히 적용하라 + + <h3 id="상식-대-암호">상식 대 암호</h3> + </li> +</ol> + +<p>암호학은 상식과 맞지 않을 수 있다.</p> + +<p>암호화의 첫번째이자 가장 중요한 규칙은 <strong>절대 직접 만들지말라</strong>는 것이다. 암호의 세계에서는 사소한 오류가 전체 암호화를 무용지물로 만들 수 있다. 이미 만들어진 프레임워크를 사용하라. (자주 업데이트 되는)</p> + +<p>로그인과 같은 인증서비스도 외부 서비스를 사용하면 외부에서 대신 고민해준다. 따라서 가능하면 해당 서비스를 사용하는 것이 좋을 수 있다</p> + +<hr /> + +<h2 id="topic-44--이름짓기">Topic 44 : 이름짓기</h2> + +<p>코드에서 하는 역할에 따라 이름을 지어야한다.</p> + +<p>예시 1 : 상품 고객 변수 이름을 <code class="language-plaintext highlighter-rouge">user</code> 대신 <code class="language-plaintext highlighter-rouge">customer</code>나 <code class="language-plaintext highlighter-rouge">buyer</code>로 하면 무언가를 구매하려는 사람으로 인식할 수 있게 된다.</p> + +<p>예시 2 : 할인을 적용하는 메서드</p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +</pre></td><td class="rouge-code"><pre><span class="kd">public</span> <span class="kt">void</span> <span class="nf">deductPercent</span><span class="o">(</span><span class="kt">double</span> <span class="n">amount</span><span class="o">)</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>이 코드에는 아래 질문점이 생긴다</p> +<ul> + <li>왜 이 일을 하는가?</li> + <li>amount는 할인금액인가 할인율인가?</li> +</ul> + +<p>아래처럼 고칠 수 있다.</p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +</pre></td><td class="rouge-code"><pre><span class="kd">public</span> <span class="kt">void</span> <span class="nf">applyDiscount</span><span class="o">(</span><span class="nc">Percentage</span> <span class="n">discount</span><span class="o">)</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<h3 id="문화를-존중하라">문화를 존중하라</h3> + +<p>반복문의 <code class="language-plaintext highlighter-rouge">i</code>, <code class="language-plaintext highlighter-rouge">j</code> 는 사용하지말라는 얘기가 많지만 C 프로그래밍에서는 많이 사용되는 변수명이다. 물론 해당 관습이 없는 곳에서는 사용하지말라</p> + +<p>camelCase나 snake_case도 해당 분야의 문화를 존중해야한다.</p> + +<h3 id="일관성">일관성</h3> + +<p>팀의 모든 사람이 각 단어의 뜻을 알고 일관성 있게 사용해야한다. 팀에서 특별한 의미가 있는 단어를 모은 사전을 만들 수도 있다.</p> + +<h3 id="이름-바꾸기는-더-어렵다">이름 바꾸기는 더 어렵다</h3> + +<p>코드는 계속 변화하며 의미는 계속 변경된다. 그에 맞춰 부지런히 이름을 변경하지 않으면 잘못된 이름이 계속 사용될 수 있다.</p> + +<hr /> + +<h2 id="후기">후기</h2> + +<p>이번 장에서는 일반적으로 코딩을 하면서 발생할 수 있는 위험 신호(?)를 어떻게 잘 감지하고, 잘 활용할 수 있는지에 대해 다루었다</p> + +<ul> + <li>topic 37 : 파충류의 뇌에 귀 기울이기 + <ul> + <li>코딩을 하면서 무의식적으로 싸늘한 느낌이 들때 그 신호를 무시하지말고, 어떤 점에서 그 느낌이 드는지 생각해보고 테스트해봐라</li> + <li>이유를 잘 모르겠으면 산책을 하거나, 고무오리 기법을 사용할 수도 있다.</li> + </ul> + </li> + <li>Topic 38 : 우연에 맡기는 프로그래밍 + <ul> + <li>우연히 당시에만 잘 돌아가는 코드를 짤 수도 있다.</li> + <li>어떤 가정을 할 때, 그 가정이 합당한지 확인하라. 다른 사람에게 설명할때 합리적인지 생각하라</li> + </ul> + </li> + <li>Topic 39 : 알고리즘의 속도 + <ul> + <li>이론적 속도와 실제 환경에서의 속도가 다를 수 있다.</li> + <li>성급한 최적화는 조심하되, 그 모듈이 정말 병목이라면 테스트해봐라</li> + </ul> + </li> + <li>Topic 40 : 리팩터링 + <ul> + <li>최대한 일찍, 자주 리팩터링을 진행하라.</li> + <li>리팩터링을 할때는 단계를 작게 나누고, 기능추가를 하지말라.</li> + <li>든든한 테스트가 있다면 리팩터링을 할때 큰 도움이 된다</li> + </ul> + </li> + <li>Topic 41 : 테스트로 코딩하기 + <ul> + <li>테스트의 가장 큰 이득은 테스트를 생각하고 작성할 때 생긴다.</li> + <li>테스트는 메서드의 첫번째 사용자이다.</li> + <li>테스트 하기 쉬운 코드를 작성하다보면 결합도가 낮은 코드가 만들어진다.</li> + <li>TDD는 좋지만 너무 중독되지는 말자. 항상 큰 문제를 우선하여 생각하라</li> + <li>실패하는 테스트를 방치하지 말라. 실패해도 무시하는 테스트가 생기면 점점 많아진다.</li> + </ul> + </li> + <li>Topic 42 : 속성 기반 테스트 + <ul> + <li>(속성 = 계약 + 불변식)을 테스트하는 것으로, 어떤 가정을 테스트하기에 좋은 방식으로 보인다</li> + </ul> + </li> + <li>Topic 43 : 바깥에서는 안전에 주의하라 + <ul> + <li>보안과 관련된 것은 직접 만들지말고 가능한 이미 검증된 서비스를 사용하라</li> + <li>기본 보안 원칙을 지켜라 + <ul> + <li>공격표면 최소화, 최소권한원칙, 안전한 기본값, 민감정보 암호화, 보안업데이트</li> + </ul> + </li> + </ul> + </li> + <li>Topic 44 : 이름짓기 + <ul> + <li>코드에서 하는 역할에 따라 이름을 지어라.</li> + <li>팀의 모든 사람이 같은 단어를 같은 뜻으로 사용하는 것이 좋다.</li> + </ul> + </li> +</ul> + + Mon, 05 Aug 2024 18:06:46 +0900 + https://seongil-shin.github.io/posts/%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8-7%EC%9E%A5-%EC%BD%94%EB%94%A9%ED%95%98%EB%8A%94-%EB%8F%99%EC%95%88/ + https://seongil-shin.github.io/posts/%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8-7%EC%9E%A5-%EC%BD%94%EB%94%A9%ED%95%98%EB%8A%94-%EB%8F%99%EC%95%88/ + + #study + + + study, + + 실용주의프로그래머 + + + + + 실용주의프로그래머 6장 동시성 + <ul> + <li>동시성 : 둘 이상의 코드 조작이 실행될 때 동시에 실행 중인 것처럼 행동하는 것. 소프트웨어 동작 + <ul> + <li>코드의 다른 부분으로 실행을 전환할 수 있는 환경에서 코드를 구동해야함. ex) 스레드, 프로세스</li> + </ul> + </li> + <li>병렬성 : 실제로 동시에 실행되는 것. 하드웨어 동작 + <ul> + <li>두가지 일을 동시에 할 수 있는 하드웨어가 필요함. ex) CPU내 코어, 네트워크로 연결된 컴퓨터 등</li> + </ul> + </li> +</ul> + +<h3 id="모든-일에-동시성이-있다">모든 일에 동시성이 있다.</h3> + +<p>시스템이 커지면 동시성은 필수다. 어떤 일을 할 때, 한번에 하나씩 순차적으로 수행 하면 시스템이 매우 느려질 것이기 때문이다.</p> + +<p>따라서 코드 간의 결합 이외에도, <code class="language-plaintext highlighter-rouge">시간적 결합</code>을 깨는 것도 중요하다.</p> + +<h2 id="topic-33--시간적-결합-깨트리기">Topic 33 : 시간적 결합 깨트리기</h2> + +<p>코드를 짜다보면 순차적 사고를 하기 마련이다. 하지만 유연성과 동시성을 확보하기 위해 시간이나 순서에 의존하는 시간적 결합을 끊어야한다. 그럼 시간 관련 의존성을 줄일 수 있고, 더 빠르고 안정적인 시스템을 만들 수 있다.</p> + +<h3 id="동시성-찾기">동시성 찾기</h3> + +<p>동시에 일어나도 되는 일과 반드시 순서대로 일어나야하는 일을 구분하기 위해, 활동 다이어그램 등을 사용하여 작업 흐름을 모델화 할 수 있다.</p> + +<h3 id="동시-작업의-기회">동시 작업의 기회</h3> + +<p>활동 다이어그램을 통해 동시에 작업할 수 있는 부분들이 있다. 이 중에서 데이터베이스 접근, 외부 서비스 요청 등 우리 코드가 아닌 곳에서 시간이 걸리는 일을 할 때, 우리 코드는 다른 작업을 할 수 있을 것이다.</p> + +<h3 id="병렬-작업의-기회">병렬 작업의 기회</h3> + +<p>큰 작업을 독립적인 부분 작업으로 나누어 여러 프로세서에서 동시에 일을 처리할 수 있다.</p> + +<hr /> + +<h2 id="topic-34--공유-상태는-틀린-상태">Topic 34 : 공유 상태는 틀린 상태</h2> + +<blockquote> + <p>공유 상태는 틀린 상태다</p> +</blockquote> + +<p>상태가 공유되면 동시성을 구현할 때 프로그램 오류가 발생할 수 있다. 따라서 어떤 상태가 있다면 이를 원자적으로 갱신하여야한다. 여기에는 다음 방법들이 있다.</p> + +<ul> + <li>세마포어 + <ul> + <li>세마포어를 소유하고 있을 때만 조회/갱신을 할 수 있도록 함.</li> + <li>단점 : 누군가가 세마포어를 얻는걸 잊어버릴 수 있음.</li> + </ul> + </li> + <li>리소스를 트랜잭션으로 관리하라 + <ul> + <li>리소스 제어를 중앙으로 집중하여, 조회와 동시에 업데이트를 할 수 있도록 한다.</li> + </ul> + </li> +</ul> + +<h3 id="여러-리소스와-트랜잭션">여러 리소스와 트랜잭션</h3> + +<p>어떤 작업을 수행할 때, 여러 리소스가 동시에 필요하면 공유 상태 관리는 더 복잡해진다. 하지만 이때도 여러 리소스를 하나의 리소스로 보고, 이 리소스에 접근하는 코드를 모듈로 옮긴 후, 클라이언트는 그 리소스를 달라고 요청하면 그림이 간단해진다. 클라이언트가 여러 리소스를 직접 호출하는 것보다 훨씬 간단하다.</p> + +<h3 id="트랜잭션이-없는-갱신">트랜잭션이 없는 갱신</h3> + +<p>수정 가능한 리소스를 공유하는 애플리케이션 코드 어디에서나 동시성 문제가 발생할 수 있다. 여기에는 한가지 팁이 있다.</p> + +<blockquote> + <p>불규칙한 실패는 동시성 문제인 경우가 많다.</p> +</blockquote> + +<hr /> + +<h2 id="topic-35--액터와-프로세스">Topic 35 : 액터와 프로세스</h2> + +<p><strong>액터</strong></p> + +<p>자신만의 비공개 지역상태를 가진 독립적인 가상 처리 장치. +우편함을 하나씩 보유하고 있고, 메세지를 받으면 차례대로 처리한다. 메세지를 처리할 때 액터는 다른 액터를 생성하거나, 메세지를 보낼 수 있고, 새로운 상태를 생성할 수 있다.</p> + +<h3 id="액터는-언제나-동시성을-띤다">액터는 언제나 동시성을 띤다</h3> + +<ul> + <li>액터를 관리하는 것은 없다.</li> + <li>시스템이 저장하는 상태는 메시지와 각 액터 내의 지역 상태일 뿐이다.</li> + <li>모든 메시지는 일방향이고, 메시지 내에서 다른 메시지를 보내는 것만 가능하다.</li> + <li>한번에 하나의 메시지만 처리한다.</li> +</ul> + +<p>위 특징들로 액터들은 아무것도 공유하지 않고 비동기적으로 동시에 실행된다. 따라서 동시성을 다루는 코드도 필요없다.</p> + +<blockquote> + <p>공유 상태 없는 동시성을 위하여 액터를 사용하라</p> +</blockquote> + +<hr /> + +<h2 id="topic-36--칠판">Topic 36 : 칠판</h2> + +<p><code class="language-plaintext highlighter-rouge">칠판</code>은 일종의 자유방임주의적 동시성으로, 독립된 프로세스, 에이전트 등이 수집한 사실을 붙이고 누군가는 떼어내기도 한다. (ex : 린다, JavaSpace, T Spaces 등)</p> + +<p>복잡한 규칙과 과정을 준수해야하는 대출 시스템에서 사용할 수도 있는데, 어떤 사실이 칠판에 올라가면 규칙 엔진이 돌아가서 적절한 규칙을 발동시키면 된다.</p> + +<h3 id="메시지-시스템과-칠판의-유사성">메시지 시스템과 칠판의 유사성</h3> + +<p>카프카와 같은 메시징 시스템은 단순히 메시지를 보내는 것 이상으로 이벤트 로그를 통한 영속성지원, 패턴 매칭으로 메시지 조회를 지원한다. 따라서 메시징 시스템을 칠판으로 사용할 수도 있다.</p> + +<h3 id="하지만-간단하지-않다">하지만 간단하지 않다.</h3> + +<p>아키텍처에서 액터, 칠판, 마이크로서비스를 사용하면 애플리케이션에서 생길 수 있는 동시성 문제를 예방할 수 있다. 하지만, 이런 접근 방식은 많은 동작이 간접적으로 일어나서 분석이 어렵다.</p> + +<p>이에 대한 기법으로, 처리를 시작할 때 고유한 ‘추적 아이디 (Trace Id)’를 붙일 수 있다.</p> + + Sun, 04 Aug 2024 18:04:36 +0900 + https://seongil-shin.github.io/posts/%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8-6%EC%9E%A5-%EB%8F%99%EC%8B%9C%EC%84%B1/ + https://seongil-shin.github.io/posts/%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8-6%EC%9E%A5-%EB%8F%99%EC%8B%9C%EC%84%B1/ + + #study + + + study, + + 실용주의프로그래머 + + + + + 실용주의프로그래머 5장 구부러지거나 부러지거나 + <p>이번 장에서는 되돌릴 수 있는 의사결정을 내리는 구체적인 방법을 설명한다.</p> + +<ul> + <li>topic 28 : 결합도 줄이기</li> + <li>topic 29 : 실세계를 갖고 저글링하기. 이벤트에 반응하는 네 가지 서로 다른 전략</li> + <li>topic 30 : 변환 프로그래밍. 함수 파이프라인</li> + <li>topic 31 : 상속세. 유연하고 바꾸기 쉬운 코드를 만들 수 있는 대안</li> + <li>topic 32 : 설정. 세부사항을 코드 밖으로 옮기는 방법</li> +</ul> + +<h2 id="topic-28--결합도-줄이기">Topic 28 : 결합도 줄이기</h2> + +<p>코드에서 나타나는 결합의 증상</p> +<ul> + <li>관계없는 모듈이나 라이브러리 간의 희한한 의존 관계</li> + <li>한 모듈의 간단한 수정이 이와 관계없는 모듈을 통해 시스템 전역으로 퍼져나가거나 시스템의 다른 곳에서 무언가를 꺠뜨리는 경우</li> + <li>개발자가 수정하는 부분이 시스템에 어떤 영향을 미칠지 몰라 코드의 수정을 두려워하는 경우</li> + <li>변경 사항에 누가 영향을 받는지 파악하고 있는 사람이 없어서 결국 모든 사람이 참석해야하는 회의</li> +</ul> + +<h3 id="열차-사고">열차 사고</h3> + +<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +</pre></td><td class="rouge-code"><pre><span class="k">public</span> <span class="kt">void</span> <span class="nf">applyDiscount</span><span class="p">(</span><span class="n">customer</span><span class="p">,</span> <span class="n">order_id</span><span class="p">,</span> <span class="n">discount</span><span class="p">)</span> <span class="p">{</span> + <span class="n">totals</span> <span class="o">=</span> <span class="n">customer</span><span class="p">.</span><span class="n">orders</span><span class="p">.</span><span class="n">find</span><span class="p">(</span><span class="n">order_id</span><span class="p">).</span><span class="n">getTotals</span><span class="p">();</span> + <span class="n">totals</span><span class="p">.</span><span class="n">grandTotal</span> <span class="o">=</span> <span class="n">totals</span><span class="p">.</span><span class="n">grandTotal</span> <span class="o">-</span> <span class="n">discount</span><span class="p">;</span> + <span class="n">totals</span><span class="p">.</span><span class="n">discount</span> <span class="o">=</span> <span class="n">discount</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>이 코드를 지원하기 위해 앞으로 바꾸면 안되는 것들이 너무 많다. 기차의 객차가 연결되어있듯이 메서드나 속성들이 모두 연결되어있다. 이런 코드를 열차 코드라고 부른다.</p> + +<p>만약 할인율을 변경해야할때는 어떻게 해야할까? <code class="language-plaintext highlighter-rouge">totals</code> 객체의 필드는 어디서든 변경할 수 있다. 따라서 다른 곳에서 필드 값을 바꾸는 부분이 있다면 모두 변경해야한다.</p> + +<p>이것은 책임의 문제이다. <code class="language-plaintext highlighter-rouge">totals</code> 객체가 합계를 관리하는 책임을 져야하는데 그렇게 하고 있지 않다. 누구나 질의하고 갱신할 수 있는 다수의 필드를 가진 컨테이너일 뿐이기에 그렇다.</p> + +<p>이를 고치려면 다음 원칙을 적용해야한다.</p> + +<blockquote> + <p>묻지말고 말하라</p> +</blockquote> + +<p>다른 객체의 내부 상태에 따라 판단을 내리고 그 객체를 갱신해서는 안된다. 즉, 할인 처리를 <code class="language-plaintext highlighter-rouge">totals</code> 객체에 위임해야한다.</p> + +<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +</pre></td><td class="rouge-code"><pre><span class="k">public</span> <span class="kt">void</span> <span class="nf">applyDiscount</span><span class="p">(</span><span class="n">customer</span><span class="p">,</span> <span class="n">order_id</span><span class="p">,</span> <span class="n">discount</span><span class="p">)</span> <span class="p">{</span> + <span class="n">customer</span><span class="p">.</span><span class="n">orders</span><span class="p">.</span><span class="n">find</span><span class="p">(</span><span class="n">order_id</span><span class="p">).</span><span class="n">getTotals</span><span class="p">().</span><span class="n">applyDiscount</span><span class="p">(</span><span class="n">discount</span><span class="p">);</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>더 개선하면 <code class="language-plaintext highlighter-rouge">customer</code> 객체에서 주문 컬렉션을 가져오지 말아야한다.</p> + +<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +</pre></td><td class="rouge-code"><pre><span class="k">public</span> <span class="kt">void</span> <span class="nf">applyDiscount</span><span class="p">(</span><span class="n">customer</span><span class="p">,</span> <span class="n">order_id</span><span class="p">,</span> <span class="n">discount</span><span class="p">)</span> <span class="p">{</span> + <span class="n">customer</span><span class="p">.</span><span class="n">findOrder</span><span class="p">(</span><span class="n">order_id</span><span class="p">).</span><span class="n">getTotals</span><span class="p">().</span><span class="n">applyDiscount</span><span class="p">(</span><span class="n">discount</span><span class="p">);</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>추가로 <code class="language-plaintext highlighter-rouge">order</code> 객체가 <code class="language-plaintext highlighter-rouge">totals</code> 객체를 가졌다는 걸 굳이 드러낼 필요도 없다.</p> + +<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +</pre></td><td class="rouge-code"><pre><span class="k">public</span> <span class="kt">void</span> <span class="nf">applyDiscount</span><span class="p">(</span><span class="n">customer</span><span class="p">,</span> <span class="n">order_id</span><span class="p">,</span> <span class="n">discount</span><span class="p">)</span> <span class="p">{</span> + <span class="n">customer</span><span class="p">.</span><span class="n">findOrder</span><span class="p">(</span><span class="n">order_id</span><span class="p">).</span><span class="n">applyDiscount</span><span class="p">(</span><span class="n">discount</span><span class="p">);</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>여기서 더 숨길 수도 있지만, 여기서는 <code class="language-plaintext highlighter-rouge">customer</code>와 <code class="language-plaintext highlighter-rouge">order</code>가 최상위 개념이다. 이정도는 드러내도 괜찮을 것이다.</p> + +<p><strong>데메테르 법칙</strong></p> + +<p>LoD(데메테르 법칙) : 어떤 클래스 C에 정의된 메서드는 다음 목록에 속하는 것만 사용할 수 있다.</p> +<ul> + <li>C의 다른 인스턴스 메서드</li> + <li>메서드의 매개변수</li> + <li>스택이나 힙에 자신에 생성하는 객체의 메서드</li> + <li>전역 변수</li> +</ul> + +<p>다음 말이 좀 더 쉬울 수 있다.</p> +<blockquote> + <p>메서드 호출을 엮지 말라</p> +</blockquote> + +<p>무언가를 접근할 때 <code class="language-plaintext highlighter-rouge">.</code>을 딱하나만 사용하도록 노력하라. 다만 엮는 것들이 절대로 바뀌지 않을 것 같다면 괜찮다. (언어에서 기본적으로 포함된 기능들. 외부 라이브러리는 아님)</p> + +<p><strong>연쇄와 파이프라인</strong></p> + +<p>함수에서 함수로 데이터를 넘겨가며 데이터를 변환하는 것은 열차사고와는 다르다. 숨겨진 구현 세부 사항에 의존하지 않기 때문이다. 하지만 결합이 생기지 않는 것은 아닌데, 한 함수의 결과물이 다음 함수의 입력 형식이어야하기 때문이다. 하지만 이러한 결합은 열차사고보단 문제가 되는 경우가 적다.</p> + +<h3 id="글로벌화의-해악">글로벌화의 해악</h3> + +<p>전역 데이터는 애플리케이션 컴포넌트 간의 결합을 만들어낸다. 전역 데이터는 시스템 전체에 영향을 줄 수 있다. 변경될 때 바꿔야할 곳을 전부 바꿨는지 확인하기 어려울 수 있다.</p> + +<blockquote> + <p>전역 데이터를 피하라.</p> +</blockquote> + +<p><strong>싱글턴도 전역 데이터이다.</strong></p> + +<p>외부로 노출된 인스턴스 변수가 많은 싱글턴은 여전히 전역 데이터이다. 메서드 안으로 데이터를 숨기면 한층 낫긴하지만 설정데이터를 단 한 벌만 가지고 있는 것에는 변함이 없다.</p> + +<p><strong>외부 리소스도 전역 데이터다</strong></p> + +<p>수정 가능한 외부 리소스(DB, API 등)는 모두 전역 데이터다. 이러한 리소스들을 코드로 모두 감싸는 것이 좋다.</p> + +<blockquote> + <p>전역적이어야 할 만큼 중요하다면 API로 감싸라.</p> +</blockquote> + +<h3 id="결국은-모두-etc">결국은 모두 ETC</h3> + +<p>직접적으로 아는 것만 다루는 부끄럼쟁이 코드를 계속 유지하라. 그러면 결합도를 크게 낮출 수 있다.</p> + +<hr /> + +<h2 id="topic-29--실세계를-갖고-저글링하기">Topic 29 : 실세계를 갖고 저글링하기</h2> + +<p>이번 장은 반응적인 애플리케이션을 작성하는 법을 다룬다.</p> + +<h3 id="이벤트">이벤트</h3> + +<p>이벤트는 무언가 정보가 있다는 것을 의미한다. 정보는 외부에서 올 수도 있고, 내부에서 생길 수도 있다. 어디에서 온 것이든 애플리케이션이 이런 이벤트에 반응하고 하는 일을 조절하도록 만들면, 실세계에서 더 잘 작동하는 애플리케이션을 만들 수 있다.</p> + +<p>이러한 애플리케이션을 만드는 다음 네가지 전략이 있다.</p> +<ul> + <li>유한 상태 기계</li> + <li>감시자 패턴</li> + <li>게시 - 구독</li> + <li>반응형 프로그래밍과 스트림</li> +</ul> + +<h3 id="유한-상태기계">유한 상태기계</h3> + +<p>FSM은 작성해야하는 부분은 적지만, 한번 작성하면 이벤트를 어떻게 처리할지 한눈에 알기 쉽다.</p> + +<h3 id="감시자-패턴">감시자 패턴</h3> + +<p>감시자 패턴은 이벤트를 발생시키는 쪽인 감시대상과, 이벤트에 관심이 있는 감시자로 이루어진다.</p> + +<p>감시자는 자신이 관심 있는 이벤트를 감시대상으로 등록한다. 보통은 호출될 함수의 참조도 같이 넘긴다. 추후 해당 이벤트가 발생 시, 등록된 감시자 목록을 보며 함수들을 일일이 호출한다.</p> + +<p>감시자 패턴은 수십년간 잘 작동해왔지만 한가지 문제점이 있다. 바로 모든 감시자가 감시 대상에 등록해야하는 결합이 생긴다는 것이다. 또 감시 대상이 콜백을 직접 호출하기에 성능 병목이 생길 수 있다. 이러한 문제점은 <code class="language-plaintext highlighter-rouge">게시-구독</code>으로 해결한다.</p> + +<h3 id="게시---구독">게시 - 구독</h3> + +<p>감시자 패턴을 일반화한 것으로 감시자 모델의 결합도를 높이는 문제와 성능 문제를 해결한다.</p> + +<p>게시-구독 모델은 <code class="language-plaintext highlighter-rouge">게시자</code>와 <code class="language-plaintext highlighter-rouge">구독자</code>가 있고, 채널로 연결된다. 채널은 코드 밖에 있으며, 게시자가 채널로 이벤트를 보내고, 구독자는 채널을 통해 이벤트를 받는다. 이는 비동기적으로 이루어진다.</p> + +<p>이러한 게시-구독 모델은 대부분의 클라우드 서비스가 제공하기에 직접 구현할 필요는 없다. 또한 추가적인 결합이 없어 기존 코드를 수정하지 않고 이벤트 처리코드를 추가하거나 교체할 수 있다.</p> + +<p>단점은 너무 많이 사용하는 시스템에서는 현재 어떤 일이 벌어지는지 파악하기가 어렵다는 것이다. 게시자가 이벤트를 보내고 어떤 구독자가 처리하는지 확인하기 어렵다.</p> + +<h3 id="반응형-프로그래밍과-스트림-그리고-이벤트">반응형 프로그래밍과 스트림 그리고 이벤트</h3> + +<p>반응형 프로그래밍 : 하나의 값이 바뀌면, 그 값을 사용하는 다른 값이 반응하는 것</p> +<ul> + <li>ex) 엑셀, 리액트, 뷰</li> +</ul> + +<p>스트림 : 이벤트를 일반적인 자료구조처럼 다룰 수 있게 해줌. 이벤트의 리스트. 이벤트를 처리하고, 조합하고, 골라내는 등 일반적인 자료구조처럼 다룰 수 있음</p> +<ul> + <li>비동기적으로 작동할 수도 있다.</li> +</ul> + +<p>반응형 이벤트의 표준은 https://reactivex.io/ 에서 정의했다. 이 사이트에서는 언어에 무관한 원칙들을 정의하고 몇가지 공통 구현사항을 문서화 했다. 이를 구현한 것으로는 자바스크립트 RxJS 등이 있다.</p> + +<p><strong>후기)</strong></p> + +<p>스트림 부분은 잘 이해가 안갔음. 사용해본적이 없어서 정확히 어떤 것에서 큰 장점이 있는지 모르겠음. 한번 예제라도 건드려 보는게 좋을거 같다</p> + +<h3 id="어디에나-이벤트가-있다">어디에나 이벤트가 있다</h3> + +<p>이벤트는 모든 곳에 있다. 하지만 이벤트가 어디서 발생하든 이벤트를 중심으로 공들여 만든 코드는 일직선으로 수행되는 코드보다 더 잘 반응하고 결합도가 낮다.</p> + +<hr /> + +<h2 id="topic-30--변환-프로그래밍">Topic 30 : 변환 프로그래밍</h2> + +<p>모든 프로그램은 데이터를 변환한다. 코드에만 집중하여 핵심을 놓치지말고, 프로그램이란 입력을 출력으로 바꾸는 것이라고 생각하라. 이렇게하면 프로그램 구조는 명확해지고, 결합도 대폭 줄어들 것이다.</p> + +<blockquote> + <p>프로그래밍은 코드에 관한 것이지만, 프로그램은 데이터에 관한 것이다.</p> +</blockquote> + +<h3 id="변환-찾기">변환 찾기</h3> + +<p>요구사항에서 입력과 출력이 무엇인지 찾으면 전체 프로그램을 나타내는 함수가 정해짐. 이후에는 입력을 출력으로 바꾸는 단계를 차례대로 찾으면 됨. (top-down 방식)</p> + +<h3 id="변환-모델의-좋은점">변환 모델의 좋은점</h3> + +<p>객체지향프로그래밍을 하다보면 데이터를 숨기고, 객체 안에 캡슐화해야한다고 생각한다. 이런 객체들이 서로 대화하며 서로의 상태를 변경하는데, 이런 방식은 결합을 많이 만들어내고, 이는 이후 유지보수가 어려운 코드를 만들게한다.</p> + +<blockquote> + <p>상태를 쌓아놓지말고 전달하라.</p> +</blockquote> + +<p>변환 모델은 데이터를 전체 시스템에 흩어놓는 대신, 데이터를 거대한 강으로 생각한다. 데이터는 기능과 동등해져서 파이프라인은 코드 -&gt; 데이터 -&gt; 코드 -&gt; 데이터의 연속이다. 데이터는 특정한 함수들과 묶이지 않는다. 대신 우리 애플리케이션이 입력을 출력으로 바꾸어 나가는 진행 상황을 데이터로 자유롭게 표현하여 결합을 크게 줄일 수 있다. 어떤 함수든 매개변수가 다른 함수의 출력결과와 맞기만 하면 어디서나 재사용할 수 있다.</p> + +<p>ex) 보통 객체지향에서는 두번째처럼하는데, 두번째는 앞단계에서 반환하는 객체에 다음 함수가 구현되어있어야한다. 만약 새로운 요구사항이 생겼을때, 두번째는 해당 객체에서 함수를 변경해야하는데 다른 코드에 영향이 갈까 두려움이 생길 것이다. 첫번째 방식은 그냥 한단계를 추가하면 된다.</p> + +<ul> + <li>데이터 변환 + <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +</pre></td><td class="rouge-code"><pre><span class="kd">const</span> <span class="nx">content</span> <span class="o">=</span> <span class="nx">File</span><span class="p">.</span><span class="nf">read</span><span class="p">(</span><span class="nx">fileName</span><span class="p">);</span> +<span class="kd">const</span> <span class="nx">lines</span> <span class="o">=</span> <span class="nf">findMatchingLines</span><span class="p">(</span><span class="nx">content</span><span class="p">,</span> <span class="nx">pattern</span><span class="p">);</span> +<span class="kd">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="nf">truncateLines</span><span class="p">(</span><span class="nx">lines</span><span class="p">);</span> +</pre></td></tr></tbody></table></code></pre></div> </div> + </li> + <li>메서드 연결 + <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +</pre></td><td class="rouge-code"><pre><span class="kd">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="nf">contentOf</span><span class="p">(</span><span class="nx">fileName</span><span class="p">)</span> + <span class="p">.</span><span class="nf">findMatchingLines</span><span class="p">(</span><span class="nx">pattern</span><span class="p">)</span> + <span class="p">.</span><span class="nf">truncateLines</span><span class="p">();</span> +</pre></td></tr></tbody></table></code></pre></div> </div> + </li> +</ul> + +<h3 id="오류-처리는-어떻게-하나">오류 처리는 어떻게 하나?</h3> + +<p>공통적으로 연쇄 변환 도중 변환 사이에 값을 날것으로 넘기지 않는 관례가 있다. Wrapper 역할을 하는 자료구조로 값을 싸서 넘긴다.</p> + +<p>이런 개념을 이용하여 오류 검사를 변환 안에서 하거나, 변환 바깥에서 할 수 있다.</p> + +<hr /> + +<h2 id="topic-31--상속세">Topic 31 : 상속세</h2> + +<h3 id="코드를-공유하기-위해-상속을-쓸때의-문제">코드를 공유하기 위해 상속을 쓸때의 문제</h3> + +<p>상속도 일종의 결합이다. 만약 최상위 클래스만 바꾸고 싶을때도 하위 클래스에서 어떻게 사용하고 있는지에따라 코드가 망가질 수 있다. 최상위 클래스의 담당자가 자기는 한 클래스의 변수명을 변경하였는데, 코드 전체가 망가지면 당황할 것이다.</p> + +<h3 id="타입을-정의하기-위해-상속을-쓸때의-문제">타입을 정의하기 위해 상속을 쓸때의 문제</h3> + +<p>상속을 타입을 정의하기 위해 사용하는 경우도 있다. 이때 클래스 계층도 같은 것을 사용할 수 있다. 하지만 클래스 사이의 미묘한 차이를 위해 계층을 계속 더하다보면 클래스 계층도는 매우 복잡해진다.</p> + +<p>다중 상속을 지원하지 않는 것도 문제가 될 수 있는데, 어떤 클래스를 제대로 모델링하려면 다중 상속이 필요해질 수 있기 때문이다.</p> + +<h2 id="더-나은-대안">더 나은 대안</h2> + +<p><strong>인터페이스</strong></p> + +<p>자바의 인터페이스 같은 것을 사용할 수 있다. 인터페이스는 단순한 선언이고, 아무런 코드도 만들지 않으며 단순히 선언된 메서드를 구현해야한다고 지시할 뿐이다. 또한 이들을 타입으로 사용할 수 있고, 해당 인터페이스를 구현한 클래스라면 무엇이든 그 타입과 호환된다.</p> + +<blockquote> + <p>다형성은 인터페이스로 표현하는 것이 좋다.</p> +</blockquote> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +</pre></td><td class="rouge-code"><pre><span class="kd">public</span> <span class="kd">interface</span> <span class="nc">Locatable</span> <span class="o">{</span> + <span class="nc">Coordinate</span> <span class="nf">getLocation</span><span class="o">();</span> + <span class="kt">boolean</span> <span class="nf">locationIsValid</span><span class="o">();</span> +<span class="o">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p><strong>위임</strong></p> + +<p>상속을 사용하게 되면 딱 2개의 메서드만 필요해도 부모의 20개의 메서드가 한번에 딸려온다. 클래스가 자신의 인터페이스를 제어할 수 없게 되는 것이다.</p> + +<p>대신에 위임을 사용하면 클래스는 필요한 것만 노출할 수 있다.</p> + +<blockquote> + <p>서비스에 위임하라. Has-A 가 Is-A보다 낫다.</p> +</blockquote> + +<p><strong>믹스인, 트레이트, 카테고리, 프로토콜 확장 등</strong></p> + +<p>언어마다 이름은 다르지만, 클래스나 객체에 상속을 사용하지 않고 새로운 기능을 추가하여 확장하고 싶을 떄 사용하는 기능이다.</p> + +<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +</pre></td><td class="rouge-code"><pre><span class="n">mixin</span> <span class="no">CommonFinders</span> <span class="p">{</span> + <span class="k">def</span> <span class="nf">find</span><span class="p">(</span><span class="nb">id</span><span class="p">)</span> <span class="p">{</span><span class="o">...</span><span class="p">}</span> + <span class="k">def</span> <span class="nf">findAll</span><span class="p">()</span> <span class="p">{</span><span class="o">...</span><span class="p">}</span> +<span class="p">}</span> + +<span class="k">class</span> <span class="nc">AccountRecord</span> <span class="n">extends</span> <span class="no">BasicRecord</span> <span class="n">with</span> <span class="no">CommonFinders</span> +<span class="k">class</span> <span class="nc">OrderRecord</span> <span class="n">extends</span> <span class="no">BasicRecord</span> <span class="n">with</span> <span class="no">CommonFinders</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<blockquote> + <p>믹스인으로 기능을 공유하라</p> +</blockquote> + +<p>예를들어 검증을 적용할 수 있는 여러 계층이 있을때, 각 상황에 맞는 전문화된 클래스를 믹스인을 사용하여 만들 수 있다.</p> +<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +</pre></td><td class="rouge-code"><pre><span class="k">class</span> <span class="nc">AccountForCustomer</span> <span class="n">extends</span> <span class="no">Account</span> <span class="n">with</span> <span class="no">AccountValidations</span><span class="p">,</span> <span class="no">AccountCustomerValidations</span> + +<span class="k">class</span> <span class="nc">AccountForAdmin</span> <span class="n">extends</span> <span class="no">Account</span> <span class="n">with</span> <span class="no">AccountValidations</span><span class="p">,</span> <span class="no">AccountAdminValidations</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<h3 id="상속이-답인-경우는-드물다">상속이 답인 경우는 드물다</h3> + +<ul> + <li>인터페이스</li> + <li>위임</li> + <li>믹스인과 트레이트</li> +</ul> + +<p>상황에 따라 상속보다 더 나은 방법이 있을 것이다. 타입 정보를 교환하고 싶은건지, 기능을 더하고 싶은건지, 메서드를 공유하고 싶은건지에 따라 다르다. 우리의 목표는 의도를 가장 잘 드러내는 기법을 사용하는 것이다. 그리고 정글 전체를 끌어들이지 않도록 조심하다</p> + +<hr /> + +<h2 id="topic-32--설정">Topic 32 : 설정</h2> + +<p>프로그램 출시 이후 변경될 것이라 생각되는 값들은 프로그램 외부에서 관리하라. 이렇게하면 프로그램을 조정할 수 있게 된다.</p> + +<blockquote> + <p>외부 설정으로 애플리케이션을 조정할 수 있게하라.</p> +</blockquote> + +<p>소스 코드 본체 바깥에 표현할 수 있는 것을 찾아라.</p> + +<h3 id="정적-설정">정적 설정</h3> + +<p>대부분의 프레임워크에서는 설정을 일반 파일이나 데이터베이스 테이블로 관리한다. 어떤 형태로 사용하든 설정을 자료구조 형태로 불러온다. 보통 처음 애플리케이션이 실행 될 때 읽어올 것이다.</p> + +<p>보통 이 설정을 전역에서 접근할 수 있도록 하는데, 그것보다는 API 뒤로 숨기는 것이 좋다. 그러면 설정을 표현하는 세부 사항으로부터 코드를 떼어놓을 수 있다</p> + +<h3 id="서비스형-설정">서비스형 설정</h3> + +<p>설정을 외부에서 관리하긴하지만, 일반 파일이나 데이터베이스가 아니라 서비스 API 뒤에서 관리하는 방식이다. 여기에는 몇가지 장점이 있다.</p> +<ul> + <li>여러 애플리케이션이 설정 정보를 공유할 수 있다.</li> + <li>여러 인스턴스에 걸쳐서 전체 설정을 한번에 바꿀 수 있다.</li> + <li>설정 데이터를 전용 UI로 관리할 수 있다.</li> + <li>설정 데이터를 동적으로 계속 바꿀 수 있다. -&gt; 새로이 빌드-배포가 필요없다.</li> +</ul> + +<p>설정 정보를 바꾸기 위해 코드 빌드가 필요없는 방식이다.</p> + +<h3 id="지나치게-하지는-말라">지나치게 하지는 말라</h3> + +<p>지나치게 모든 변수를 설정할 수 있게하지는 말라.</p> + +<p><strong>여담</strong></p> + +<p>실무를 경험해본 결과 서비스형 설정이 확실히 좋았다. 이러한 값들은 빌드가 필요없이 바로 참조가 되어 이슈가 발생했을 경우 곧바로 대응을 할 수가 있기 때문이다.</p> + +<p>물론 스프링에서 properties 파일과 같은 정적 설정도 장점이 있다. 설정 정보의 성격에 따라 적절히 분리해서 관리하면 될 거 같다.</p> +<ul> + <li>정적 설정 : 자주 변경되지 않지만 애플리케이션에 거쳐 많이 사용되거나 외부로 뺄 필요가 있어보이는 값들. 급하게 변경하지 않아도 이슈가 없는 값들</li> + <li>서비스형 설정 : 자주 변경될거라 예상되는 값들. 급하게 변경이 발생할 수 있는 값들.</li> +</ul> + +<hr /> + +<h2 id="후기">후기</h2> + +<p>이번 장은 전체적으로 결합도를 줄이는 것에 집중한 장이다.</p> + +<p>topic 28 : <strong>책임을 분명히하고, 자신이 직접적으로 아는 것만 다루는 방식</strong>을 통해 결합도를 줄일 수 있다. 전역 변수도 일종의 결합인데, 전역변수 변경 시 필요한 부분을 모두 바꾸었는지 확인하기 어렵기 때문이다.</p> + +<p>이 토픽에서는 데메테르 법칙에 대해서도 얘기하였는데, 쉽게 풀이한 “무언가에 접근할 때 <code class="language-plaintext highlighter-rouge">.</code>을 두번 이상 사용하지 마라”라는 팁은 이해하기가 쉬웠다.</p> + +<p>topic 29 : 이벤트를 구현하는 방식에 네가지 방식(유한 상태 기계, 감시자 패턴, 게시 - 구독, 반응형 프로그래밍과 스트림)에 대해 설명하였다. 유한상태기계를 작성하면 이벤트를 어떻게 다뤄야하는지 알기 쉽다는 팁이 도움될거 같다.</p> + +<p>topic 30 : 프로그램은 결국 데이터를 변환하는 것이고, 데이터를 중심으로 구조를 생각하면 어떻게 파이프라인을 구성해야할지 한눈에 보인다.</p> + +<p>topic 31 : 상속은 결합을 만들어내기에 사용을 지양해야한다는 파트이다. 상속 대신 인터페이스, 위임, 믹스인 등을 통해 기능을 추가하는 것이 좋다고 한다. 개인적으로 동감하는 파트이고, 상속은 프레임워크에서 제공하는 것 외에는 사용을 자제하는 것이 좋다고 생각한다.</p> + +<p>topic 32 : 설정을 소스 본체 바깥에 저장하는 방식을 설명한다. 개인적으로 서비스형 설정이 변화에 대응이 쉬워 더 선호한다.</p> + + Sun, 28 Jul 2024 20:07:50 +0900 + https://seongil-shin.github.io/posts/%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8-5%EC%9E%A5-%EA%B5%AC%EB%B6%80%EB%9F%AC%EC%A7%80%EA%B1%B0%EB%82%98-%EB%B6%80%EB%9F%AC%EC%A7%80%EA%B1%B0%EB%82%98/ + https://seongil-shin.github.io/posts/%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8-5%EC%9E%A5-%EA%B5%AC%EB%B6%80%EB%9F%AC%EC%A7%80%EA%B1%B0%EB%82%98-%EB%B6%80%EB%9F%AC%EC%A7%80%EA%B1%B0%EB%82%98/ + + #study + + + study, + + 실용주의프로그래머 + + + + + 실용주의프로그래머 4장 실용주의 편집증 + <blockquote> + <p>여러분은 완벽한 소프트웨어를 만들 수 없다</p> +</blockquote> + +<p>실용주의 프로그래머는 자신의 실수에 대비한 방어책을 마련한다.</p> +<ul> + <li>Topic 23 : 코드의 공급자와 사용자는 권리와 책임에 대해 동의해야한다</li> + <li>Topic 24 : 버그상황에서 헤어나오는 도중에 어떤 손상도 입히지 않도록 보장해야한다.</li> + <li>Topic 25 : 확인을 쉽게하는 방법. 가정(assumption)을 적극적으로 검증하는 코드 작성</li> + <li>Topic 26 : 여러 시스템 리소스를 다룰때 실수 하지 않는 법</li> + <li>Topic 27 : 언제나 작은 단계를 고수해야한다.</li> +</ul> + +<h2 id="topic-23--계약에-의한-설계">Topic 23 : 계약에 의한 설계</h2> + +<h3 id="dbcdesign-by-contract-계약에-의한-설계">DBC(Design By Contract, 계약에 의한 설계)</h3> +<p>정확한 프로그램이란 자신이 하는 일을 적지도 많지도 않게 그만큼만 수행하는 프로그램이다. 이 주장을 문서화하고 검증하는 것이 DBC 이다.</p> + +<p>선행조건</p> +<ul> + <li>루틴이 호출되기 전에 참이어야하는 조건. 이 선행조건이 위반된 경우 루틴이 호출되어서는 안된다. +후행조건</li> + <li>루틴이 자기가 할 것이라고 보장하는 것. 루틴은 종료되어야하며 무한 반복은 안된다. +클래스 불변식</li> + <li>호출자의 입장에서 이 조건이 언제나 참인 것을 클래스가 보장한다. 루틴 도중에는 참이 아닐 수 있지만, 루틴 종료 후에는 불변식이 참이 되어야한다.</li> +</ul> + +<p>만약 호출자가 모든 선행조건을 충족하면, 루틴은 종료 시 모든 후행조건과 불변식이 참이 되는 것을 보장한다. 따라서 만약 호출자나 루틴 중 어느 한쪽이라도 계약을 지키지 못하면 예외가 발생한다. 그렇기에 선행 조건을 이용해서 사용자 입력값을 검증한다거나 해서는 안된다.</p> + +<p>호출자, 루틴 모두 입력값을 검증하지 않음. 호출자는 그냥 호출하고, 루틴은 실행 도중 예외를 발생시킴. 선행 조건은 호출자가 루틴을 부른 뒤, 루틴 자체로 들어가기 전에 보이지 않는 곳에서 검사됨.</p> + +<p>클로져, 엘릭서 등이 이러한 DBC를 잘 지원한다.</p> + +<h3 id="클래스-불변식과-함수형-언어">클래스 불변식과 함수형 언어</h3> + +<p>이 용어가 의미하는 일반적인 개념은 <code class="language-plaintext highlighter-rouge">상태</code>이다. 함수형 언어에서는 보통 상태를 함수에 넘긴 후 바뀐 상태를 결과로 받는다.</p> + +<blockquote> + <p>DBC는 방어적 프로그래밍보다 더 효율적이고 DRY하다. 방어적 프로그램은 아무도 데이터를 검증하지 않는 상황에 대비하여 모든 사람이 데이터를 검증한다.</p> +</blockquote> + +<h3 id="dbc-구현">DBC 구현</h3> + +<p>코드를 작성하기 전에 유효한 입력범위가 무엇인지, 경계조건이 무엇인지, 루틴이 뭘 전달한다고 약속하는지, 무엇을 약속하지 않는지 나열하는 것만으로도 더 나은 코드를 작성할 수 있다. +코드에서 DBC를 지원하지 않으면 이정도가 최선이다. DBC는 설계 기법이고, 자동 검사가 없더라도 계약을 코드에 주석이나 단위 테스트로 넣어둘 수 있다</p> + +<p><strong>단정문</strong></p> + +<p>컴파일러가 계약을 검사하도록 하면 큰 도움이 된다. 몇몇 언어에서는 <code class="language-plaintext highlighter-rouge">단정문</code>으로 부분적으로 흉내낼 수 있다. 다만 단정문은 객체지향에서는 재정의 할때마다 일일이 작성하여야하고, 어떤 환경에서는 단정문에서 발생하는 예외를 무시하기에 완전한 방법은 아니다.</p> + +<h3 id="dbc와-일찍-멈추기">DBC와 일찍 멈추기</h3> + +<p>단정문이나 DBC 방식을 사용하여 선행조건, 후행조건, 불변식을 검증하면 더 일찍 멈추고 문제에 대한 정확한 정보를 얻을 수 있다.</p> + +<h3 id="의미론적-불변식">의미론적 불변식</h3> + +<p>시스템에서 영원히 변경되지 않는 요구사항. 비즈니스 정책처럼 유동적으로 변하는 것에 영향을 받으면 안된다.</p> + +<p>ex)</p> +<ul> + <li>같은 거래를 두번 처리하지 않는다</li> + <li>오류 발생 시 소비자의 입장을 우선하라</li> +</ul> + +<hr /> +<h2 id="topic-24--죽은-프로그램은-거짓말을-하지-않는다">Topic 24 : 죽은 프로그램은 거짓말을 하지 않는다.</h2> + +<p>프로그램에서 잘못된 것이 있을때 라이브러리나 프레임워크 루틴에서 먼저 잡아낼 수 있다. 빈 데이터를 받았거나, switch-case 문에서 default가 실행되었거나 하는 일이 발생할 수 있다. 이런 <code class="language-plaintext highlighter-rouge">있을 수 없는 일</code>이 발생했을때 우리는 그 사실을 알아야한다.</p> + +<p>모든 오류는 정보를 준다. 오류 메시지 좀 읽어라.</p> + +<h3 id="잡은-후-놓아주는건-물고기뿐">잡은 후 놓아주는건 물고기뿐</h3> + +<p>어떤 개발자는 예외를 잡은 후 로그 좀 찍고 다시 예외를 올려보낸다. 하지만 실용주의 프로그래머는 굳이 잡지 않는다.</p> +<ul> + <li>애플리케이션 코드가 오류 처리 코드 사이에 묻히지 않는다.</li> + <li>메서드에서 새로운 예외가 추가되었을때 코드를 수정하지 않아도 된다.</li> +</ul> + +<blockquote> + <p>일찍 작동을 멈춰라</p> +</blockquote> + +<h3 id="망치지-말고-멈춰라">망치지 말고 멈춰라</h3> + +<p>문제를 빨리 발견하면 가능한 빨리 시스템을 멈출 수 있다.</p> + +<p>얼랭과 엘릭서는 “방어적 프로그램은 시간낭비고, 그냥 멈추는 게 낫다”라는 철학을 가지고 있다. 프로그램이 실패하면 멈추고, 이런 실패는 슈퍼바이저가 관리한다. 슈퍼바이저는 코드를 실행하고, 실패 시 처리할 책임이 있다. 슈퍼파이저가 실패하면 슈퍼바이저의 슈퍼바이저가 처리한다. 이러한 <code class="language-plaintext highlighter-rouge">슈퍼바이저 트리</code> 기법은 효과적이어서 고가영성, 결함 감내 시스템에서 얼랭이나 엘릭서를 채택하는 요인이 된다.</p> + +<p>어떤 환경에서는 멈추는게 적절하지 않을 수 있다. 그러나 있을 수 없는 일이 발생했다면 프로그램은 더이상 유효하지 않다고 할 수 있기에 가능한 빨리 종료되는 것이 좋을 수 있다.</p> + +<p>일반적으로 죽은 프로그램이 끼치는 피해는 이상한 상태의 프로그램이 끼치는 피해보다 적다.</p> + +<hr /> + +<h2 id="topic-25--단정적-프로그래밍">Topic 25 : 단정적 프로그래밍</h2> + +<blockquote> + <p>단정문으로 불가능한 상황을 예방하라.</p> +</blockquote> + +<p>코딩할때 절대 일어나지 않을 일은 없다. 그런 생각이 든다면, 그것을 확인하는 코드를 추가하라. +다만, 오류처리를 해야하는 곳에서 단정을 대신 사용하지는 말라. 단정은 결코 일어나면 안되는 것들을 검사한다.</p> + +<p>ex) 아래와 간은 코드는 작성해서는 안된다.</p> +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +</pre></td><td class="rouge-code"><pre>puts("Y나 N을 입력하세요") +ch = gets[0] +assert((ch == 'Y') || (ch == 'N')) +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>단정이 실패한다고 바로 프로세스를 종료시킬 필요는 없다. 예외를 잡고 오류처리 루틴을 실행할 수도 있다. 다만, 단정을 실패하게 한 잘못된 정보를 사용하지는 말라.</p> + +<h3 id="단정-기능을-켜둬라">단정 기능을 켜둬라</h3> + +<p>테스트는 모든 버그를 발견할 수 없고, 일어날 수 없는 일은 일어날 수 있다. 따라서 단정기능을 실제 환경에서 끄는 것은 문제가 될 수 있다.</p> + +<p>성능상 이슈가 있다면 정말 문제가 되는 단정문만 꺼라.</p> + +<hr /> + +<h2 id="topic-26--리소스-사용의-균형">Topic 26 : 리소스 사용의 균형</h2> + +<p>리소스 사용을 위한 간단한 팁들이 있다.</p> + +<blockquote> + <p>자신이 시작한 것은 자신이 끝내라</p> +</blockquote> + +<p>리소스 사용을 시작한 곳에서 리소스 할당을 해제하여야한다.</p> + +<blockquote> + <p>지역적으로 행동하라</p> +</blockquote> + +<p>잘 모르겠으면 스코프를 줄여라.</p> + +<h3 id="중첩할당">중첩할당</h3> + +<ul> + <li>리소스를 할당한 순서의 역순으로 해제하라. 그래야 한 리소스가 다른 리소스를 참조하는 경우에도 참조를 망가트리지 않는다</li> + <li>코드의 여러 곳에서 같은 구성의 리소스를 참조하면 항상 같은 순서로 할당하라. 그래야 교착상태에 빠지지 않는다. + <ul> + <li>ex) resource1, resource2가 필요할때 항상 1 -&gt; 2로 할당하라.</li> + </ul> + </li> +</ul> + +<h3 id="균형잡기와-예외">균형잡기와 예외</h3> + +<p>예외를 처리할때 리소스 해제는 다루기 어려울 수 있다. 이때는 두가지 방법을 사용할 수 있다.</p> + +<ol> + <li>변수 스코프를 사용한다. ex) c++, 러스트의 스택변수</li> + <li>try-catch 블록에서 finally 절 사용</li> +</ol> + +<hr /> + +<h2 id="topic-27--헤드라이트를-앞서가지-말라">Topic 27 : 헤드라이트를 앞서가지 말라</h2> + +<p>우리는 너무 먼 미래를 내다볼 수 없고 정면에서 벗어난 곳일수록 더 어둡다</p> + +<blockquote> + <p>작은 단계들을 밟아라. 언제나.</p> +</blockquote> + +<p>언제나 신중하게 작은 단계들을 밟아라. 더 진행하기 전에 피드백을 확인하고 조정하라. 너무 큰 단계나 작업은 하지 않게 될 것이다.</p> + +<p>ex) 피드백이란?</p> +<ul> + <li>REPL의 결과</li> + <li>단위 테스트</li> + <li>사용자 데모 및 사용자와의 대화</li> +</ul> + +<p>너무 큰 작업이란 예언을 하는 모든 작업을 뜻한다. 불확실한 미래에 대비한 설계를 하기보단 언제나 교체 가능한 코드를 작성하여 대비하여라.</p> + +<hr /> + +<h2 id="후기">후기</h2> + +<p>이번 장은 코딩할 때 발생할 수 있는 이슈들에 대비하기 위한 팁들을 소개하는 장이었다.</p> + +<p>일어날 수 없는 일이란 없고, 이에 대비하기 위한 단정문에 대한 얘기가 많이 나왔다. 개인적으로 단정문을 사용한 적은 없어서 내용이 잘 이해가 되지 않았다. 평소에는 if 문을 통해 조건을 확인하고 거짓이면 함수를 종료하는 식으로 하였는데, 이 방법과 단정문을 사용한 방법에 어떤 차이가 있는지 확실히 이해가 되지는 않았다.</p> + +<p>다만 몇가지 얻어갈 것은 있었다.</p> + +<ol> + <li>DBC의 개념은 함수를 설계할때 도움이 될거 같다. 선행조건, 후행조건, 불변식을 지키며 계약 이상의 것을 하지않는 함수라는 개념으로 예측 가능한 함수를 설계가능할거 같다.</li> + <li>문제가 발생하면 가능한 빨리 멈추는 것이 좋다라는 내용도 적용하기 좋다.</li> + <li>예외 발생 시 catch 한 후 로그찍고 다시 그대로 올려보내는 것보다, 잡지 않고 올려보내거나 이왕 잡았으면 적절히 처리하여 다른 예외를 발생 시키는 것이 좋아보인다. (이 부분은 책에서 말하는 바와 다를 수 있다)</li> + <li>리소스 사용에 관한 팁들도 도움이 될거 같다.</li> + <li>작은 작업을 한 후 피드백(테스트든 사용자든)을 받는 과정을 반복하여야하고, 너무 큰 작업을 한번에 하지 말라는 팁도 도움이 될거 같다.</li> +</ol> + + + Sat, 27 Jul 2024 21:04:15 +0900 + https://seongil-shin.github.io/posts/%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8-4%EC%9E%A5-%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98-%ED%8E%B8%EC%A7%91%EC%A6%9D/ + https://seongil-shin.github.io/posts/%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8-4%EC%9E%A5-%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98-%ED%8E%B8%EC%A7%91%EC%A6%9D/ + + #study + + + study, + + 실용주의프로그래머 + + + + + 실용주의프로그래머 3장 기본도구 + <h2 id="topic-16---일반-텍스트의-힘">Topic 16 - 일반 텍스트의 힘</h2> + +<h3 id="일반-텍스트란">일반 텍스트란?</h3> + +<p>인쇄 가능한 문자로 이루어지고, 정보를 전달하기에 적합한 형식을 갖추어야한다. 또한 사람이 이해할 수 있어야한다.</p> + +<ul> + <li>일반 텍스트 + <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +</pre></td><td class="rouge-code"><pre>우유 +커피 +</pre></td></tr></tbody></table></code></pre></div> </div> + </li> + <li>일반 텍스트가 아님 + <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +</pre></td><td class="rouge-code"><pre>hlj;uijn bfjxrrctvh jkni’pio6p7gu;vh bjxrdi5rgvhj +Field19=467abe +</pre></td></tr></tbody></table></code></pre></div> </div> + </li> +</ul> + +<p>형식은 HTML, JSON, YAML 등 다양하다.</p> + +<p>일반 텍스트는 데이터를 그 자체로만 이해할 수 있다. 이진포맷은 데이터를 읽는데 필요한 문맥과 데이터가 분리되어있어, 데이터를 읽는 문맥(애플리케이션 등)이 없으면 데이터는 무의미하다.</p> + +<h3 id="텍스트의-힘">텍스트의 힘</h3> + +<ul> + <li>지원 중단에 대한 보험 + <ul> + <li>일반 텍스트는 이진형식과 달리 그 데이터를 읽는 문맥(시스템)이 없어져도 파싱이 가능하다.</li> + </ul> + </li> + <li>기존 도구의 활용 + <ul> + <li>새로운 도구가 나오더라도 텍스트형식은 항상 지원한다</li> + </ul> + </li> + <li>더 쉬운 테스트 + <ul> + <li>시스템 테스트에 사용할 합성 데이터를 일반 텍스트로 표현하면 특별한 도구 없이도 테스트 데이터를 편집할 수 있다.</li> + </ul> + </li> +</ul> + +<h3 id="최소-공통분모">최소 공통분모</h3> + +<p>데이터를 서로 교환하는 시스템 간에 일반 텍스트는 하나의 표준이 된다.</p> + +<hr /> + +<h2 id="topic-17---셸-가지고-놀기">Topic 17 - 셸 가지고 놀기</h2> + +<p>셸 프롬프트를 통해 모든 도구를 불러다 쓸 수 있다. 도구를 조합하여 자동화 시스템을 만들 수도 있으며, 복잡한 명령어 수행도 가능하다. GUI 환경에서는 제공된 기능만 사용가능하지만, 셸에서는 원하는 기능을 만들 수 있다.</p> + +<blockquote> + <p>명령어 셸의 힘을 사용하라.</p> +</blockquote> + +<p>셸에 익숙해지면 생산성은 급상승한다.</p> + +<p>평소 GUI 에서 수동으로 하는 작업이 있다면 셸로 자동화 해보자</p> + +<hr /> + +<h2 id="topic-18---파워-에디팅">Topic 18 - 파워 에디팅</h2> + +<p>에디터에 유창해지면 작업속도를 빠르게 할 수 있을 뿐 아니라, 에디터 사용법을 생각하지 않고도 작업할 수 있게 된다. 그러면 생각이 자유롭게 흘러갈 것이고 이는 프로그래밍에 큰 도움이 될 것이다.</p> + +<p><strong>유창해지기</strong></p> + +<p>에디터를 사용하면서 무언가 같은 일을 반복하는 경우가 있을 것이다. 이 작업을 발견하면 더 나은 방법(단축키)가 있는지 찾아봐라. 찾았다면 그것을 의식적으로 사용해봐라. 시간이 지나면 무의식적으로 사용할 수 있게 된다.</p> + +<hr /> + +<h2 id="topic-19---버전-관리">Topic 19 - 버전 관리</h2> + +<p>VCS은 시스템의 거대한 <code class="language-plaintext highlighter-rouge">실행 취소</code> 키와 같은 것으로, 정상적으로 컴파일 되는 지난 버전으로 돌려놓을 수 있도록 해준다. 이외에도 공동 작업, 배포 파이프라인, 이슈 추적, 팀 상호작용까지 아우를 수 있다.</p> + +<hr /> + +<h2 id="topic-20---디버깅">Topic 20 - 디버깅</h2> + +<p>디버깅에 관련된 몇몇 문제들을 살펴보고 버그를 찾아내는 일반적인 전략 몇가지를 알아보자</p> + +<h3 id="디버깅의-심리">디버깅의 심리</h3> + +<blockquote> + <p>비난 대신 문제를 해결하라</p> +</blockquote> + +<p>버그가 누구의 잘못인지는 중요하지 않다. 다만 해결해야할 사람은 나라는 걸 명심하자</p> + +<h3 id="디버깅-사고방식">디버깅 사고방식</h3> + +<blockquote> + <p>당황하지 마라</p> +</blockquote> + +<ul> + <li>‘그건 불가능해’ 란 생각을 하지마라. 실제로 일어났기 때문이다.</li> + <li>근시안에 빠지지마라. 표면에 드러난 증상만 고치지 말고, 실제 원인을 찾으려고 노력하라</li> +</ul> + +<h3 id="실마리-찾기">실마리 찾기</h3> + +<ul> + <li>작업중인 코드가 경고 없이 깨끗하게 빌드되는지 확인하라.</li> + <li>관련 자료를 모두 모아라. + <ul> + <li>실제 버그인지, 버그라면 자세한 현상이 무엇인지 자료를 요구하라</li> + <li>ex) 실제 시연되는 영상 등</li> + </ul> + </li> +</ul> + +<h3 id="디버깅-전략">디버깅 전략</h3> + +<p><strong>버그 재현하기</strong></p> + +<p>버그를 고치는 가장 좋은 방법은 버그를 재현할 수 있게 하는 것이다. 버그를 명령하나로 재현할 수 있으면 더 좋다.</p> + +<blockquote> + <p>코드를 고치기 전 실패하는 테스트부터</p> +</blockquote> + +<p>버그가 발생하는 상황을 다른 것들로부터 분리하면 버그를 고칠 힌트를 얻을 수도 있다.</p> + +<h3 id="미지의-세계에-온-프로그래머">미지의 세계에 온 프로그래머</h3> + +<blockquote> + <p>그놈의 오류 메세지 좀 읽어라</p> +</blockquote> + +<p>프로그램이 죽었다면 빨간색 예외 메시지를 무시하지마라</p> + +<p>프로그램이 죽는 것이 아니라 결과가 이상한 상황이면 어떨까? +디버거를 붙인 다음 실패하는 테스트를 이용하여 문제를 재현하라. +그리고 먼저 디버거에 잘못된 값이 나타나는 지부터 확인하라.</p> + +<h3 id="이진-분할">이진 분할</h3> + +<p>거대한 스택 트레이스가 눈앞에 있을때, 이중에 정확히 어떤 부분에서 문제가 되는지 찾기 위해서는 중간부터 이진 분할 기법으로 찾을 수 있다.</p> + +<p>입력값이 따라 버그가 발생하는 경우에도 정확히 어떤 입력값이 범인인지 이진분할을 통해 찾을 수 있다.</p> + +<p>어떤 릴리스에서 버그가 발생하는지 찾고 싶은 경우에도 문제가 없던 릴리스와 현재 버전 사이에서 이진분할을 시도할 수 있다.</p> + +<p><strong>로깅과 트레이싱</strong></p> + +<p>트레이싱 구문은 ‘여기까지 도달’이나 ‘x값 = 2’ 같은 정보를 화면이나 파일에 출력하는 작은 진단용 메시지이다. 트레이싱은 여러 프로세스가 동시에 작동하는 경우, 특히 시간이 중요한 시스템에서는 중요한 정보가 된다.</p> + +<p>트레이싱 구문으로 남기는 메세지는 규칙적이어야하며 일관된 형식이어야한다.</p> + +<p><strong>고무 오리</strong></p> + +<p>누군가(고무 오리도 괜찮다)에게 문제를 설명함으로써 원인을 찾을 수도 있다. 차근차근 코드를 설명함으로써 당연히 여기고 넘어갔을 부분을 명시적으로 이야기하게 되고, 그러면 문제가 보일 수 있다.</p> + +<p><strong>소거법</strong></p> + +<p>OS, 외부 라이브러리, 외부 제품에서 오류를 찾지 마라. 대개 외부 제품을 잘못 사용하고 있다고 생각하는 것이 더 낫다. 설사 외부 제품에 문제가 있다하더라도 우리 애플리케이션 코드에 문제가 없다는 걸 확인은 해야한다.</p> + +<p>만약 하나만 변경했는데 시스템의 작동이 멈춘다면, 관련이 없어보여도 변경된 그 하나에 책임이 있다.</p> + +<p>때로는 외부 제품이 새로운 버전으로 바뀌면서 버그가 생길 수도 있다. 따라서 이럴때는 새 조건하에서 시스템을 다시 테스트해봐야한다.</p> + +<h3 id="놀라운-구석">놀라운 구석</h3> + +<p>당연하다고 생각되는, 버그가 없을거라 생각되는 부분에서도 버그가 생길 수 있다. 버그가 발생한 데이터로, 맥락으로 다시 그 코드를 테스트하라. 이 경계 조건하에서 증명하라.</p> + +<blockquote> + <p>가정하지 말라. 증명하라.</p> +</blockquote> + +<p>이러한 놀라운 버그를 발견했을때는 단순히 고치는 것을 넘어 어떻게하면 미리 잡을 수 있었을지를 생각해보라. +그리고 버그를 고치는 김에 동일한 버그가 있을 법한 다른 코드도 확인하고, 지금 고쳐라.</p> + +<p>버그가 누군가의 잘못된 가정으로 발생했다면 이 문제에대해 전체 팀과 함께 토론하라. 한 사람이 오해했다면 다른 사람도 그럴 수 있다.</p> + +<h3 id="디버깅-체크리스트">디버깅 체크리스트</h3> + +<ul> + <li>보고된 문제가 내재하는 버그의 직접적 결과인가 아니면 단순히 증상일 뿐인가?</li> + <li>버그가 정말로 여러분이 사용하는 프레임워크에 있나? OS에? 아니면 여러분 코드에 있나?</li> + <li>이 문제를 동료에게 상세히 설명한다면 어떻게 말하겠는가?</li> + <li>의심가는 코드가 단위 테스트를 통과한다면 테스트는 충분히 갖춰진 것인가? 이 데이터로 테스트를 돌리면 무신일이 생가는가?</li> + <li>이 버그를 야기한 조건이 시스템의 다른 곳에도 존재하는가?</li> +</ul> + +<hr /> + +<h2 id="topic-21-텍스트-처리">Topic 21 텍스트 처리</h2> + +<p>텍스트에 특화되어 처리해주는 도구가 있다 ex) sed, awk, 파이썬, 루비 등</p> + +<p>이러한 텍스트 처리 도구는 중요한 기반 기술이며, 이러한 도구를 사용하면 유틸리티를 쉽게 만들거나, 아이디어를 프로토타입화 해볼 수 있다.</p> + +<blockquote> + <p>텍스트 처리 언어를 익혀라</p> +</blockquote> + +<hr /> + +<h2 id="topic-22-엔지니어링-일지">Topic 22 엔지니어링 일지</h2> + +<p>회의에서 메모하거나, 작업 내용을 쓸때, 디버깅하다가 변수의 값을 적을때, 무엇을 어디 두었는지, 엉뚱한 생각을 기록할때 등 사용한다.</p> + +<p>일지를 쓰면 좋은 점이 있다</p> +<ul> + <li>기억보다 믿을만하다</li> + <li>진행 중인 작업과 직접적 관계가 없는 발상을 일단 쌓아놓을 수 있다.</li> + <li>무엇가를 쓰면서 환기를 시킬 수 있다.</li> +</ul> + + Sun, 21 Jul 2024 20:49:29 +0900 + https://seongil-shin.github.io/posts/%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8-3%EC%9E%A5-%EA%B8%B0%EB%B3%B8%EB%8F%84%EA%B5%AC/ + https://seongil-shin.github.io/posts/%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8-3%EC%9E%A5-%EA%B8%B0%EB%B3%B8%EB%8F%84%EA%B5%AC/ + + #study + + + study, + + 실용주의프로그래머 + + + + + 실용주의프로그래머 2장 실용주의 접근법 + <h2 id="topic-8---좋은-설계의-핵심">Topic 8 - 좋은 설계의 핵심</h2> + +<blockquote> + <p>좋은 설계는 나쁜 설계보다 바꾸기 쉽다.</p> +</blockquote> + +<p><strong>ETC(Easy To Change)는 규칙이 아니라 가치</strong></p> + +<p>가치란 결정을 내릴때 도움을 주는 것이다. ETC는 가치로서 내제화되어야하며, 의식적으로 방금 한 일이 ETC 한지 항상 물어봐야한다. +일반적으로 상식선에서 추측이 가능하다. 하지만 결정하기 어려울때가 있는데, 이럴땐 다음 두가지 방법이 있다.</p> +<ul> + <li>코드가 교체되기 쉬워야한다. (결합도를 낮추고 응집도를 높여라)</li> + <li> + <h2 id="직관적으로-선택하고-그-이유에-대해-기록해둬라-그리고-추후-변경이-실제로-발생했을때-다시보며-피드백하라">직관적으로 선택하고, 그 이유에 대해 기록해둬라. 그리고 추후 변경이 실제로 발생했을때 다시보며 피드백하라.</h2> + </li> +</ul> + +<h2 id="topic-9---dry--중복의-해악">Topic 9 - DRY : 중복의 해악</h2> + +<p>소프트웨어를 신뢰성 높게 개발하는 유일한 길, 유지보수를 쉽게 만드는 유일한 길은 DRY 원칙을 따르는 것이다.</p> + +<blockquote> + <p>DRY : 반복하지 말라</p> +</blockquote> + +<p>DRY를 따르지 않으면 똑같은 것이 두 군데 이상 표현될 것이다. 그러면 하나를 바꾸면 나머지를 바꿔야하는데, 언제 잊어버릴지 모른다.</p> + +<p><strong>1) DRY는 코드 밖에서도</strong></p> + +<p>DRY는 똑같은 개념을 다른 곳에서 표현하면 안된다는 것이다. 코드의 어떤 부분을 바꿔야할때 문서도 바꿔야하는가? 그럼 DRY를 위반한 것이다.</p> + +<p><strong>2) 모든 코드 중복이 지식의 중복은 아니다</strong></p> + +<p>요구조건) 연령과 주문 수량은 모두 숫자이고, 0보다 커야한다</p> +<pre><code class="language-pseudo">def validate_age(value): + validate_type(value, :integer) + validate_min_integer(value, 1) + +def validate_quantity(value): + validate_type(value, :integer) + validate_min_integer(value, 1) +</code></pre> + +<p>이때 두 함수가 표현하는 지식은 다르다. 다만 우연히 규칙이 같은 것일 뿐이다. 이건 우연이지 중복이 아니다.</p> + +<p><strong>3) 문서화 중복</strong></p> + +<p>과도하게 많은 주석은 함수의 의도를 두번 설명하게 한다. 이는 추후 함수를 변경해야할때 주석도 변경해야한다. 따라서 함수명, 변수명 등으로 코드로서 의도를 표현하는 것이 제일 좋다.</p> + +<p><strong>4) 데이터의 DRY 위반</strong></p> + +<p>자료구조 내에서 중복된 의미의 데이터가 포함될 수 있다.</p> +<ul> + <li>ex) line 이라는 클래스에 <code class="language-plaintext highlighter-rouge">start</code>, <code class="language-plaintext highlighter-rouge">end</code>, <code class="language-plaintext highlighter-rouge">length</code> 변수가 있는 경우 -&gt; <code class="language-plaintext highlighter-rouge">length</code>를 계산하는 메서드를 만들어라</li> +</ul> + +<p>하지만 계산로직이 복잡한 경우 캐싱을 위해 포함할 수도 있는데, 중요한 것은 자료구조 외부로 드러내지 않는 것이다.</p> +<ul> + <li>ex) line 클래스 생성 시 <code class="language-plaintext highlighter-rouge">start</code>, <code class="language-plaintext highlighter-rouge">end</code>만 받고, <code class="language-plaintext highlighter-rouge">length</code>는 자동으로 계산되게</li> +</ul> + +<p><strong>5) 표현상의 중복</strong></p> + +<p>다른 라이브러리나 API와 연결할때 해당 API에 표현된 지식들(스키마, 에러코드 등)을 우리 코드도 알아야한다. 이러한 중복은 다음과 같이 완화될 수 있다.</p> + +<ul> + <li>내부 API에서 생기는 중복 + <ul> + <li>언어나 기술에 중립적인 형식으로 내부 API를 정의할 수 있는 도구를 찾아라 (ex : swagger 등)</li> + <li>모든 API 정의를 하나의 중앙 저장소에 넣어두고 여러 팀이 공유할 수 있으면 좋다.</li> + </ul> + </li> + <li>외부 API에서 생기는 중복 + <ul> + <li>외부 API는 엄밀한 문서화가 되어있는 경우가 많다.</li> + </ul> + </li> + <li>데이터 저장소와의 중복 + <ul> + <li>영속성 프레임워크 등을 통해 데이터 스키마로부터 객체를 바로 생성해낼 수 있도록 한다.</li> + <li>또는 JS 객체처럼 키-값 데이터 구조에 밀어넣는 것도 좋다. (이때는 간단한 데이터 검증 도구는 필요하다)</li> + </ul> + </li> +</ul> + +<p><strong>6) 개발자 간의 중복</strong></p> + +<p>같은 기능을 여러 개발자가 각자 개발할 수도 있다. 이러한 것을 발견하려면 소통과 코드리뷰가 중요하다.</p> + +<blockquote> + <p>재사용하기 쉽게 만들어라</p> +</blockquote> + +<p>기존의 것을 찾아내고, 재사용하기 쉬운 환경을 조성해야한다. 재사용이 어려우면 중복의 위험을 감수해야한다.</p> + +<hr /> +<h2 id="topic-10---직교성">Topic 10 - 직교성</h2> + +<p>컴퓨터 과학에서 직교성은 결합도 줄이기를 의미한다. 하나가 바뀌어도 나머지에 어떤 영향도 주지 않으면 서로 직교한다고 할 수 있다. 잘 설계가 되어있다면 데이터베이스 코드가 바뀌어도 사용자 인터페이스는 바뀌지 않을 것이다.</p> + +<p><strong>직교성의 장점</strong></p> + +<blockquote> + <p>관련 없는 것들 간에 서로 영향이 없도록 하라</p> +</blockquote> + +<p>컴포넌트들이 각기 격리되어있으면 어느 하나를 바꿀 때 나머지를 걱정하지 않아도 된다. 직교성은 다음 두가지 큰 장점이 있다.</p> + +<ul> + <li>생산성 향상 + <ul> + <li>변화를 국소화해서 개발 시간과 테스트 시간을 줄일 수 있다.</li> + <li>직교성은 재사용하기도 쉽다</li> + <li>직교적인 컴포넌트들을 결합하면 생산성 향상을 얻을 수 있다. 하나의 컴포넌트로 다양한 일을 할 수 있기 때문이다.</li> + </ul> + </li> + <li>리스크 감소 + <ul> + <li>직교성은 감염된 코드를 격리시켜준다.</li> + <li>변경이 발생해도 시스템이 잘 깨지지 않는다.</li> + <li>각각의 컴포넌트들을 테스트하기가 쉬워진다</li> + <li>특정 제품, 플랫폼에 덜 종속적이 된다.</li> + </ul> + </li> +</ul> + +<p><strong>설계</strong></p> + +<p>설계가 직교적인지 확인하는 쉬운 방법은 ‘특정 기능에 대한 요구사항이 대폭 변경되었을때 몇개의 모듈이 영향을 받는가?’를 자문하는 것이다. 정답은 하나여야한다.</p> + +<p>또한 현실 세계의 변화와 설계 사이의 결합도를 얼마나 줄였는지도 확인해야한다. 자신의 힘으로 제어할 수 없는 속성에 의존하면 안된다.</p> +<ul> + <li>ex) 전화번호를 고객 식별자로 사용할 경우 -&gt; 법이 변경되면 식별자를 모두 바꿔야한다.</li> +</ul> + +<p><strong>툴킷과 라이브러리</strong></p> + +<p>같은 팀이 작성한 것이라도 라이브러리의 도입이 코드에 수용해서는 안될 변화를 가져오고, 직교성을 해친다면 도입하면 안된다.</p> + +<p>EJB의 데코레이터 패턴이 좋은 라이브러리 예시이다.</p> + +<p><strong>코딩</strong></p> + +<p>코딩을 할 때 직교성을 유지할 수 있는 몇가지 기법이 있다.</p> + +<ul> + <li>코드의 결합도를 줄여라 + <ul> + <li>불필요한 것은 다른 모듈에 보여주지말고, 다른 모듈의 구현에 의존하지 않는 코드를 작성하라</li> + <li><code class="language-plaintext highlighter-rouge">데메테르 법칙</code>을 따르려 노력해보자</li> + <li>객체의 상태를 바꿔야하면, 객체가 직접 상태를 바꾸도록 해보자. (상태를 변경하는 메서드를 만들라는 뜻인듯…?)</li> + </ul> + </li> + <li>전역 데이터를 피하라 + <ul> + <li>전역 데이터를 읽기 전용으로 사용한다해도 전역 데이터가 변경되면 사용하는 모든 모듈에 영향이 간다</li> + <li>필요한 컨텍스트를 모듈에 전달하는 식으로 해야 유지보수가 쉽다.</li> + <li>싱글턴 객체를 일종의 전역 데이터로 남용하여 불필요한 결합을 만들 수 있다.</li> + </ul> + </li> + <li>유사한 함수를 피하라 + <ul> + <li>유사해보이는 함수를 여러개 구현해야할 수 있다. (ex : 시작과 끝은 같지만 중간이 다른 함수)</li> + <li>이러한 함수는 전략 패턴을 사용하여 더 낫게 구현할 수 없을지 확인하라</li> + </ul> + </li> +</ul> + +<p>기회가 있을 때마다 리팩터링하여 직교성을 개선하기 위해 노력하라.</p> + +<p><strong>테스트</strong></p> + +<p>직교적으로 설계된 시스템은 테스트하기가 더 쉽다. 통합 테스트보다 단위 테스트가 훨씬 쉽기 때문이다.</p> + +<p>단위테스트를 작성하는 행위 자체가 직교성을 테스트할 수 있는 기회이다. 단위 테스트를 작성할 때 많은 모듈을 불러와야하면 결합도가 높다는 뜻이다.</p> + +<p>버그 수정을 할 때에도, 하나의 모듈만 고치면 되는지 전체를 다 고쳐야하는지 점검해보아라.</p> + +<p><strong>더 알아보기</strong></p> + +<p>함수형 프로그래밍에서도 결합이 발생할 수 있는데, 한 함수의 결과를 다른 함수의 입력으로 사용하는 경우이다.</p> +<ul> + <li>이때는 함수 결과를 일반적인 포맷으로 하면 완화할 수 있을거 같음</li> + <li>타입스크립트 사용도 도움이 된다고 함</li> +</ul> + +<hr /> +<h2 id="topic-11---가역성">Topic 11 - 가역성</h2> + +<p>프로젝트에서 정답은 하나가 아니다. 중요한 결정이 추후 변경될 수 있고, 큰 비용을 치뤄야하는 경우가 발생한다.</p> + +<h3 id="가역성"><strong>가역성</strong></h3> +<p>이 책에서 추천하는 방법들 (DRY 원칙, 결합도 줄이기 등)을 따른다면 중요하면서도 되돌릴 수 없는 결정의 수를 가능한한 줄일 수 있다.</p> + +<blockquote> + <p>최종 결정이란 없다</p> +</blockquote> + +<h3 id="유연한-아키텍처">유연한 아키텍처</h3> +<p>코드 뿐만이 아니라 아키텍처, 배포, 외부 제품과의 통합 영역도 유연하게 유지하여야한다. +아키텍처는 항상 변화하며 할 수 있는 것은 바꾸기 쉽게 만드는 것 뿐이다. 외부의 API를 추상화 계층 뒤로 숨기고, 코드를 여러 컴포넌트로 쪼개라.</p> + +<blockquote> + <p>유행을 좇지 마라.</p> +</blockquote> + +<p>어떤 미래가 펼쳐질지는 알 수 없으며, 코드 스스로 유연하게 대처 가능하도록 해야한다.</p> + +<hr /> +<h2 id="topic-12---예광탄">Topic 12 - 예광탄</h2> + +<p>프로젝트를 진행할때 마치 예광탄처럼 즉각적인 피드백을 얻는 것은 매우 중요하다.</p> + +<p>프로젝트를 진행할때 우리는 미지의 것을 마주한다. 애매모호한 요구사항, 처음보는 라이브러리, 기술 등. 이럴때 전형적인 반응은 시스템을 극도로 명세화하는 것이다. 환경을 제약하고, 요구사항을 일일히 항목으로 만들어서 명세서를 만든 후 맞기를 기도한다.</p> + +<p>하지만 실용주의 프로그래머는 소프트웨어 판 예광탄을 선호한다.</p> + +<h3 id="어둠-속에서-빛을-내는-코드">어둠 속에서 빛을 내는 코드</h3> + +<p>예광탄이 효과적인 이유는 <strong>일반 탄환과 동일한 환경 및 제약 조건에서 발사</strong>되기 때문이다. 코딩에서 동일한 효과를 얻으려면 우리를 요구사항으로부터 최종 시스템의 일부 측면까지 빨리, 눈에 보이게 반복적으로 도달하게 해줄 무언가를 찾아야한다.</p> + +<p>시스템을 정의하는 중요한 요구사항을 찾아라. 의문이 드는 부분이나 가장 위험이 커 보이는 곳을 찾아라. 이런 부분의 코드를 가장 먼저 작성하도록 개발 우선순위를 정하라.</p> + +<blockquote> + <p>목표물을 찾기 위해 예광탄을 써라.</p> +</blockquote> + +<p>예시들</p> +<ul> + <li>프로젝트를 세팅할때 <code class="language-plaintext highlighter-rouge">hello world</code> 를 작성한 후 컴파일, 실행 해보는 것</li> + <li>프로젝트 전체 아키텍처를 거치는 간단한 로직을 먼저 구현해보는 것</li> +</ul> + +<p>예광탄 코드는 여러 장점이 있다.</p> +<ul> + <li>사용자가 뭔가 작동하는 것을 일찍부터 보게 된다. + <ul> + <li>사용자는 자신의 시스템에 진전이 있음을 보게 되어 기쁠 것이고, 점점 직접 기여하기 시작할 것이다.</li> + <li>각 반복 주기마다 얼마나 목표에 가까워졌는지도 알려줄 것이다.</li> + </ul> + </li> + <li>개발자가 들어가서 일할 수 있는 구조를 얻는다 + <ul> + <li>무에서 유를 만드는 것은 어렵다. 따라서 일단 애플리케이션의 모든 요소간 상호 작용을 만들면, 이후 추가하는 것은 쉽고 일관성도 촉진된다.</li> + </ul> + </li> + <li>통합 작업을 수행할 기반이 생긴다. + <ul> + <li>한번 시스템의 모든 요소를 연결하고 나면 새로 작성한 코드를 시스템에 붙이기가 쉬워진다.</li> + </ul> + </li> + <li>보여줄 것이 생긴다.</li> + <li>진행상황에 대해 더 정확하게 감을 잡을 수 있다.</li> +</ul> + +<h3 id="예광탄-코드-대-프로토타이핑">예광탄 코드 대 프로토타이핑</h3> + +<p>프토로타입은 최종 시스템의 어떤 특정한 측면을 탐사해보는 것이 목표이다. 따라서 프로토타입 방식을 따른다면 실험 이후 모두 버려야한다. 그리고 프로토타입을 통해 얻은 교훈으로 코드를 새로 작성한다.</p> + +<p>프로토타입은 일종의 예시를 사용자에게 제시하고, 이후 최종 목표환경을 위한 코드로 다시 작성한다.</p> + +<p>예광탄 코드는 애플리케이션이 전체적으로 어떻게 연결되는지 알려주고, 사용자에게는 애플리케이션의 요소들이 어떻게 상호작용하는지 보여주고 개발자에게는 아키텍쳐의 골격을 제시한다.</p> + +<p>정리하면 프로토타입은 나중에 버리는 코드를 만들고, 예광탄 코드는 기능은 별로 없지만 완결된 코드이며 최종 시스템 골격 중 일부가 된다. 프로토타입은 예광탄을 발사하기 전에 먼저 수행하는 정찰 같은 것이다.</p> + +<hr /> +<h2 id="topic-13---프로토타입과-포스트잇">Topic 13 - 프로토타입과 포스트잇</h2> + +<p>프로토타입은 반드시 코드로 만들 필요는 없다. 그림이나 인터페이스 빌더 등을 통해 만들어 볼 수 있다.</p> + +<p>프로토타입은 제한된 몇가지 질문에 답하기 위한 것이기에 세부 사항을 무시하고 실제 제품보다 매우 적은 비용으로 만들 수 있다. 만약 세부 사항을 포기하지 못하는 상황이라면 프로토타입을 만들고 있는 것이 맞는지 자문하라. 이럴때는 예광탄 코드가 적합할 수 있다.</p> + +<h3 id="프로토타입-대상">프로토타입 대상</h3> + +<p>프로토타입으로 조사할 대상은 <strong>위험을 수반하는 모든 것</strong>이다. 이전에 해본 적 없는 것, 최종 시스템에 매우 중요한 것, 의심이 가는 것 등이 모두 대상이다.</p> + +<ul> + <li>아키텍처</li> + <li>기존 시스템에 추가할 새로운 기능</li> + <li>외부 데이터의 구조 혹은 내용</li> + <li>외부에서 가져온 도구나 컴포넌트</li> + <li>성능 문제</li> + <li>사용자 인터페이스 설계</li> +</ul> + +<p>프로토타입의 가치는 프로토타입 자체가 아니라 이를 통해 배우는 교훈에 있다.</p> + +<blockquote> + <p>프로토타이핑으로 학습하라</p> +</blockquote> + +<h3 id="프로토타입을-어떻게-사용할-것인가">프로토타입을 어떻게 사용할 것인가?</h3> + +<p>프로토타입을 만들 때 무시해도 좋은 세부사항은 무엇일까?</p> + +<ul> + <li>정확성 : 적절히 가짜 데이터 사용 가능</li> + <li>완전성 : 제한된 방식으로만 동작해도 됨</li> + <li>안정성 : 오류 검사를 무시해도 됨</li> + <li>스타일 : 주석이나 문서가 많지 않아야함</li> +</ul> + +<hr /> + +<h2 id="topic-14---도메인-언어">Topic 14 - 도메인 언어</h2> + +<p>프로그래밍 언어로 사고방식이 제한될 수 있다. 따라서 도메인의 어휘를 사용하여 프로그래밍 하는 방식으로 좀 더 그 도메인에 가깝게 설계할 수 있다</p> + +<blockquote> + <p>문제 도메인에 가깝게 프로그래밍하라.</p> +</blockquote> + +<p>예시 :</p> +<ul> + <li>내부 도메인 언어 : RSpec, 피닉스라우터 + <ul> + <li>컴파일하고 실행할 수 있는 코드로, 실제 코드 안에 들어감</li> + <li>호스트언어의 기능을 쓸 수 있다는 장점이 있지만, 호스트 언어의 문법과 의미론을 따라야한다</li> + </ul> + </li> + <li>외부 도메인 언어 : 큐컴버, 앤서블 + <ul> + <li>다른 언어나 데이터 구조로 변환하여 실행함</li> + <li>호스트 언어에따른 제약은 없지만, 파서를 만들기에 리소스가 소비된다.</li> + </ul> + </li> +</ul> + +<p>하지만 우리가 도메인 언어로 코드를 짠다해도 기획자는 읽지 않는다. 스스로 무엇을 원하는지 정확히 모르기 때문이다. 이때는 실제로 동작하는 것을 보여주면 스스로 원하는 것을 말할 것이다.</p> + +<p>도메인언어를 사용하며 절약되는 시간보다 더 많은 시간을 쏟지마라. 기본적으로는 통용되는 yaml, json, csv 같은 외부 언어를 사용하라. 다만 외부언어 도입은 사용자가 직접 도메인 언어로 코드를 작성하는 경우에만 추천한다.</p> + +<hr /> +<h2 id="topic-15---추정">Topic 15 - 추정</h2> + +<h3 id="얼마나-정확해야-충분히-정확한가">얼마나 정확해야 충분히 정확한가?</h3> + +<p>모든 답은 추정치이다. 따라서 질문자의 원하는 것이 정확한 답을 요구하는지 큰 그림을 요구하는지 먼저 파악해야한다.</p> + +<p>사용하는 단위에 따라서 답의 정확도 해석이 달라질 수 있다. 예를들어 130일이 걸린다고 하면 정확히 130일이라는 느낌이 들지만, 대략 6달이라고하면 5~6개월로 해석된다. 아래 단위를 추천한다. (ex : 125일이 걸릴 거 같으면 대략 6달이라고 말하자)</p> + +<ul> + <li>1 ~ 15일 -&gt; 일</li> + <li>3~6주 -&gt; 주</li> + <li>8 ~ 20주 -&gt; 달</li> + <li>20주 이상 -&gt; 추정치를 말하기 전에 다시 한번 생각해보자</li> +</ul> + +<h3 id="추정치는-어디에서-나오는가">추정치는 어디에서 나오는가?</h3> + +<p>모든 추정치는 문제의 모델에 기반한다. 그런데 모델을 작성하기 전에 그 일을 해본 사람에게 물어봐라. 다른 사람의 경험을 바탕으로 성공적인 추정치를 낼 수 있다.</p> + +<p><strong>무엇을 묻고있는지 이해하라</strong></p> + +<p>어떤 추정을 하건 상대방이 무엇을 묻고 있는지 먼저 이해해야한다. 도메인에 존재하는 조건에 대해서도 알아야한다. 상대방이 말해줄 때도 있지만 말하지 않더라도 미리 조건을 생각해보는 습관이 있어야한다.</p> + +<p><strong>시스템의 모델을 만들어라</strong></p> + +<p>의뢰인의 요청을 이해한 후에는 간단하게 모델을 만들어보라. 모델을 만듦으로써 표현에 드러나지 않은 패턴이나 프로세스를 발견할 수 있고, 초기 질문과는 다른 제안을 할 수도 있다.</p> + +<p>모델이 정교해질수록 추정은 정확해지지만, 정확히 비례하진 않는다. 따라서 적절한 수준을 골라야한다.</p> + +<p><strong>모델을 컴포넌트로 나눠라</strong></p> + +<p>다 만든 모델을 컴포넌트로 분해하여 서로 어떻게 상호작용하는지 수식으로 기술해야한다. 각 노드 별로 전체 시스템에 얼마나 기여하는지 매개변수를 찾을 수 있을 것이다.</p> + +<p><strong>각 매개 변수에 값을 할당하라</strong></p> + +<p>가장 큰 영향을 미치는 매개변수를 찾아서 이 매개변수의 값을 최대한 정확하게 산출해내라. +ex) 대기열시스템에서는 실제 시간당 요청수를 측정하거나, 비슷한 시스템을 찾아봐라</p> + +<p><strong>답을 계산하라</strong></p> + +<p>주요 매개변수들의 값을 변경시켜가며 여러 번 계산해보고 어느 것이 가장 잘 들어맞는지 찾아내라. 이때 너무 정확한 값이 나오면 아마 잘못계산한 것이다.</p> + +<p><strong>여러분의 추정 실력을 기록하라</strong></p> + +<p>계산한 추정치를 기록하고 나중에 얼마나 잘 들어맞는지 평가해봐라.</p> + +<h3 id="프로젝트-일정-추정하기">프로젝트 일정 추정하기</h3> + +<p><strong>PERT 기법</strong></p> + +<blockquote> + <p>아무 문제 없다면 10시간, 현실적으로는 18시간, 날씨가 나쁘면 30시간 정도 걸릴거 같습니다.</p> +</blockquote> + +<p>낙관적 추정치와 가장 가능성 높은 추정치, 비관적 추정치를 갖는다. 과업은 의존성에 따라 네트워크 형태로 배열한 후, 간단한 통계 기법으로 전체 프로젝트의 예상 최소 및 최대 소요 시간을 계산한다.</p> + +<p>단순히 값을 부풀리는 것보다, 범위로 추정하는 것이 오류를 피할 수 있는 좋은 방법이다. PERT 속의 통계가 범위로 표현한 불확실성을 분산시켜주고, 전체 프로젝트에 대하여 더 나은 추정치를 얻을 수 있다.</p> + +<p><strong>코끼리 먹기</strong></p> + +<p>기능을 매우 작은 단위로 나누어 다음과 같은 방식으로 개발하면 추정치는 점점 더 정확해질 것이다.</p> + +<ul> + <li>요구사항 확인하기</li> + <li>위험 분석 후 위험도가 높은 부분을 우선 하기</li> + <li>설계, 구현, 통합</li> + <li>사용자와 함께 검증하기</li> +</ul> + +<p>한 iteration이 끝났을때 리뷰를 거쳐 총 몇번의 iteration이 필요할 지, 각 iteration 마다 뭘할지 추측할 수 있을 것이다.</p> + +<p>이 방법은 경영진에게 인기는 없지만 더 정확한 일정을 전달할 수 있다.</p> + + + Mon, 15 Jul 2024 21:15:49 +0900 + https://seongil-shin.github.io/posts/%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8-2%EC%9E%A5-%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98-%EC%A0%91%EA%B7%BC%EB%B2%95/ + https://seongil-shin.github.io/posts/%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8-2%EC%9E%A5-%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98-%EC%A0%91%EA%B7%BC%EB%B2%95/ + + #study + + + study, + + 실용주의프로그래머 + + + + + 실용주의프로그래머 1장 실용주의 철학 + <h2 id="topic-1---당신의-인생이다">Topic 1 - 당신의 인생이다</h2> + +<p>변화를 피하지 말고 불만이 있는 것이 있으면 고치기 위해 노력하라</p> +<ul> + <li>업무환경, 적성이 안맞으면 바꾸되, 너무 오래 노력하지는 말기</li> + <li>뒤쳐지는 기분이 들면 여가 시간에 공부하기</li> +</ul> + +<p>기회는 많고 적극적으로 행동해 그 기회를 잡아라</p> + +<h2 id="topic-2---고양이가-내-소스-코드를-삼켰어요">Topic 2 - 고양이가 내 소스 코드를 삼켰어요</h2> + +<p>실용주의 철학의 초석 중 하나는 자신과 자신의 행동에 대해 책임을 지는 것이다. 아무리 잘해도 발생하는 문제는 있고, 이를 정직하고 솔직하게 인정하고, 처리하려고 노력해야한다.</p> + +<p><strong>팀 내 신뢰</strong> +팀이 나를 믿고 의지할 수 있어야하고, 나도 팀을 의지할 수 있어야한다. 신뢰를 바탕에 둔 환경에서는 안전하게 아이디어를 교류할 수 있다.</p> + +<p><strong>책임지기</strong> +책임을 지지 않을 권리는 있지만, 지기로 결정했다면 결과를 감당해야한다.</p> + +<blockquote> + <p>어설픈 변명 말고 대안을 제시하라.</p> +</blockquote> + +<ul> + <li>나쁜 소식을 전하기 전에 시도해볼만한 것은 없는지.</li> + <li>해결을 위해 필요한 것이 있고, 그 이유를 설명할 수 있을지</li> + <li>“잘 모르겠어요” 다음에는 “알아보겠습니다”</li> +</ul> + +<h2 id="topic-3---소프트웨어-엔트로피">Topic 3 - 소프트웨어 엔트로피</h2> + +<p>소프트웨어의 무질서도가 증가하면 이를 ‘소프트웨어의 부패’ (기술 부채)라고 부른다.</p> + +<blockquote> + <p>깨진 창문을 내버려두지 마라</p> +</blockquote> + +<ul> + <li>나쁜 설계, 잘못된 코드 등이 모두 깨진 창문이다.</li> + <li>고칠 시간이 없다면, 주석처리 / Not implemented 등의 메시지를 표시하여 상황을 관리하고 있음을 보여줘라</li> +</ul> + +<p>방치는 다른 어떤 요인보다 부패를 가속화시킨다. 만약 고칠 시간이 없다면 아예 버리거나 새롭게 대체하라</p> + +<p><strong>우선 망가트리지 말라</strong> +상황을 냉정하게 평가하여 어떤 위기가 왔다고 필요이상의 피해를 주지마라. (= 깨진 창문을 더 만들지 말라)</p> + +<h2 id="topic-4---돌멩이-수프와-삶은-개구리">Topic 4 - 돌멩이 수프와 삶은 개구리</h2> + +<blockquote> + <p>변화의 촉매가 되라</p> +</blockquote> + +<ul> + <li>어떤 시스템을 개발하려고할 때 <code class="language-plaintext highlighter-rouge">시작 피로</code>에 빠지기 쉽다. 이때는 개발에 필요한 자원을 얻기 위해 사람들을 설득하는 것은 쉽지 않다.</li> + <li>하지만 작은 것을 잘 개발하여 사람들에게 보여주고, 사람들로 하여금 조금씩 추가해달라고 할때까지 기다려라</li> + <li>계속되는 성공에 합류하기는 쉽고, 미래를 살짝이라도 보여주면 사람들은 도와주기 위해 모여들 것이다</li> +</ul> + +<blockquote> + <p>큰 그림을 기억하라</p> +</blockquote> + +<ul> + <li>소프트웨어 참사는 대부분 작은 문제들이 쌓이다가 어느날 갑자기 폭주한다.</li> + <li>큰 그림에 늘 주의를 기울여 참사가 발생하지 않도록 하자.</li> + <li>당장의 일에만 정신을 쏟지말고 주변에서 벌어지는 일을 살펴보라</li> +</ul> + +<h2 id="topic-5---적당히-괜찮은-소프트웨어">Topic 5 - 적당히 괜찮은 소프트웨어</h2> + +<p>너무 완벽한 소프트웨어를 만들려하지말고, 적당히 괜찮게 만드는 것이 중요하다. 하지만 이때 <code class="language-plaintext highlighter-rouge">적당히 괜찮은</code>은 기본적인 성능, 보안, 요구조건을 만족해야한다.</p> + +<p><strong>타협과정에 사용자를 참여시켜라</strong> +소프트웨어가 얼마나 좋아야하는지 사용자에게 물어라. 좋은 소프트웨어를 위해 요구조건을 무시하거나, 요구조건을 위해 기본적인 것을 빼는 것 모두 전문가답지 못하다.</p> + +<blockquote> + <p>품질을 요구사항으로 만들어라.</p> +</blockquote> + +<p>우리는 적당한 타협이 필요한 상황에 자주 처한다. 사용자는 완벽한 내일의 소프트웨어보다 적당한 오늘의 소프트웨어를 원한다. 사용자에게 직접 만질 수 있는 기회를 주면 피드백을 통해 더 나은 해결책에 도달할 수 있다.</p> + +<p><strong>멈춰야할 때를 알라</strong> +완벽하게 훌륭한 프로그램을 만드느라 망치지마라.</p> + +<blockquote> + <p>‘기능 블로트’ +소프트웨어가 실제로 사용되는 기능에 비패 훨씬 더 많은 기능을 가지고 있고, 그만큼 버그나 보안 취약점이 생길 가능성이 높은 것을 말함</p> +</blockquote> + +<h2 id="topic-6---지식-포트폴리오">Topic 6 - 지식 포트폴리오</h2> + +<p>새로운 것을 배우는 능력은 가장 중요한 전략 자산이다. 그런데 배우는 방법은 어떻게 배워야하고, 무엇을 어떻게 배워야할까?</p> + +<p><strong>지식 포트폴리오</strong></p> +<ol> + <li>주기적인 투자 + <ul> + <li>소량으로라도 주기적으로 지식 포트폴리오에 투자해야한다.</li> + <li>습관 자체가 중요하며, 방해받지 않을 수 있는 시간과 장소를 정기적으로 이용할 계획을 마련하라</li> + </ul> + </li> + <li>다각화 + <ul> + <li>기본적으로 현재 작업에 사용하는 기술에 관해서는 속속들이 알아야한다.</li> + <li>추가적으로 더 많은 기술에 익숙하다면 변화에 더 잘 적응할 수 있다.</li> + <li>기술 외의 분야도 잊지말라</li> + </ul> + </li> + <li>. 리스크 관리 + <ul> + <li>위험하지만 잠재적으로 보상이 높은 것에서 리스크가 낮고 보상도 낮은 것 모두 적절히 담아라</li> + </ul> + </li> + <li>싸게 사서 비싸게 팔기 + <ul> + <li>새롭게 떠오르는 기술이 인기를 끌기 전에 미리 알고 학습하면 보상도 크다.</li> + </ul> + </li> + <li>검토 및 재조정 + <ul> + <li>소프트웨어 업계는 매우 동적이기에 주기적으로 어떤 걸 배워야할지 검토하여 재조정하라</li> + </ul> + </li> +</ol> + +<p><strong>목표</strong></p> +<ol> + <li>매년 새로운 언어를 최소 하나는 배워라</li> + <li>기술 서적을 한달에 한 권씩 읽어라 + <ul> + <li>현재 프로젝트와 관련이 있는 흥미로운 주제의 기술 서적을 읽어라</li> + <li>현재 사용하는 기술을 일단 완전히 익혔다면, 가지를 쳐서 다른 분야까지 공부 범위를 넓혀라</li> + </ul> + </li> + <li>기술 서적이 아닌 책도 읽어라</li> + <li>수업을 들어라</li> + <li>지역 사용자 단체나 모임에 참여하라 + <ul> + <li>회사 밖에서 사람들이 어떤 일을 하는지 알아보라</li> + </ul> + </li> + <li>다른 환경에서 실험해보라 + <ul> + <li>윈도우에서만 일했다면 리눅스도 사용해봐라.</li> + <li>IDE에서만 작업했다면 makefile/텍스트 에디터로 작업해봐라</li> + </ul> + </li> + <li>요즘 흐름을 놓치지 마라 + <ul> + <li>현재 사용하는 것과는 다른 기술을 다루는 뉴스와 게시물을 읽어라.</li> + </ul> + </li> +</ol> + +<p>한 기술에 익숙해지면 다음으로 나아가라. 직접 사용할일은 없어도, 학습과정에서 사고가 확장될 것이다.</p> + +<p><strong>학습의 기회</strong></p> +<ul> + <li>무엇인가 모르는게 생기면 답을 찾도록 노력하라</li> + <li>비는 시간을 위한 읽을거리를 준비하라</li> +</ul> + +<p><strong>비판적 사고</strong></p> +<blockquote> + <p>읽고 듣는 것을 비판적으로 분석하라</p> +</blockquote> + +<ul> + <li>‘왜’냐고 다섯번 묻기</li> + <li>누구에게 이익이 되나?</li> + <li>어떤 맥락인가?</li> + <li>언제 혹은 어디서 효과가 있을까?</li> + <li>왜 이것이 문제인가?</li> +</ul> + +<h2 id="topic-7---소통하라">Topic 7 - 소통하라!</h2> + +<p><strong>청중을 알라</strong></p> +<ul> + <li>청중에 따라 포인트를 짚을 부분을 다르게 설정하라.</li> + <li>피드백을 모아라 + <ul> + <li>질문을 기다리지말고 먼저 물어보라</li> + </ul> + </li> +</ul> + +<p><strong>말하고 싶은 게 무언지 알라</strong> +무엇을 말할지 미리 계획하고, 개요를 작성하라. 그리고 자문하라.</p> + +<p><strong>때를 골라라</strong> +무언가를 말할때 그 때가 적절한지 생각해봐라</p> + +<p><strong>스타일을 골라라</strong> +청중에 어울리도록 전달하는 스타일을 조정하라</p> +<ul> + <li>청중이 사실만 전달하는 브리핑을 원하는가? 한담을 원하는가?</li> + <li>청중이 전문가인가?</li> +</ul> + +<p>하지만 조정할 수 없는 내용이라면, 사실이 그렇다고 전달하자</p> + +<p><strong>멋져보이게 하라</strong> +내용만 집중하기보단 멋져보이게 얘기하는 것도 중요하다</p> + +<p><strong>청중을 참여시켜라</strong> +가능하면 독자를 문서 초안에 참여하도록하여 피드백을 받아라</p> + +<p><strong>경청하라</strong> +회의를 대화로 바꾸면 생각을 더 효과적으로 전달할 수 있다</p> + +<p><strong>응답하라</strong> +“다음에 답해 드리겠습니다”라고 하더라도 사람들에게 응답해주면 더 긍정적으로 봐준다.</p> + +<p><strong>문서화</strong> +문서화는 꼭 필요한 작업이다.</p> +<blockquote> + <p>문서를 애초부터 포함하고, 나중에 집어넣으려고 하지 말라.</p> +</blockquote> + +<p>모듈과 외부로 노출하는 함수에는 주석을 다는 것을 추천한다. But 모든 함수나 자료구조에 주석을 남기는 것은 유지보수를 어렵게 만든다. +주석을 달 때는 코드의 용도와 목적을 논해야한다. 어떻게 동작하는지는 코드가 이미 보여주고 있기 때문이다.</p> + + Sun, 14 Jul 2024 16:13:54 +0900 + https://seongil-shin.github.io/posts/%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8-1%EC%9E%A5-%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98-%EC%B2%A0%ED%95%99/ + https://seongil-shin.github.io/posts/%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8-1%EC%9E%A5-%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98-%EC%B2%A0%ED%95%99/ + + #study + + + study, + + 실용주의프로그래머 + + + + + next.js 최적화과정 + <!-- @format --> + +<h2 id="발단">발단</h2> + +<p>서비스를 운영하던 도중 CPU/Memory 사용량은 낮은데 사용자 응답이 매우 느려지는 상황이 발생하였다. +서비스는 쿠버네티스로 띄워져있었고, HPA metric으로 CPU와 memory가 걸려있었는데, CPU와 memory가 올라가지 않으니 scale-out도 발생하지 않아 적은 pod으로 계속 서비스 되고 있었다. +수동으로 pod을 3~4배 가량 늘려 대응은 하였으나 추후에도 발생할 이슈일 것으로 보여 대응을 하기로 했다.</p> + +<h2 id="테스트">테스트</h2> + +<p>이 이슈는 next.js로 띄워져있는 페이지 중 하나에 페이지에 갑자기 트래픽이 몰려 발생한 이슈였다. +따라서 당시 리얼환경과 비슷하게 환경 세팅을 하고 <a href="https://naver.github.io/ngrinder/">ngrinder</a>를 사용하여 대규모 트래픽 테스트를 하였다. +이슈가 발생했던 페이지를 포함하여 다양한 페이지를 테스트한 결과는 다음과 같았다. (TPS가 낮음 -&gt; 성능이 낮음)</p> + +<ul> + <li>페이지 A (이슈가 발생했던 페이지) : TPS 매우 낮음.</li> + <li>페이지 B : TPS 낮음</li> + <li>페이지 C : TPS 중간</li> + <li>페이지 D : TPS 높음</li> + <li>페이지 E : TPS 매우 높음</li> +</ul> + +<p>해당 페이지들은 다음과 같은 특징을 가지고 있다.</p> + +<ul> + <li>페이지 A (TPS 매우 낮음.) + <ul> + <li>동적 페이지</li> + <li>API 소스 A’로의 요청 2개, 소스 B’로의 요청 1개</li> + <li>API 소스 A’는 C’로부터 데이터를 중개해주는 역할을 함.</li> + </ul> + </li> + <li>페이지 B (TPS 낮음) + <ul> + <li>동적페이지</li> + <li>API 소스 B’로의 요청 1개</li> + </ul> + </li> + <li>페이지 C (TPS 중간) + <ul> + <li>동적페이지</li> + <li>API 소스 A’로의 요청 2개</li> + <li>이 A’로 간 API 두개는 A’가 DB로부터 바로 데이터를 가져옴.</li> + </ul> + </li> + <li>페이지 D (TPS 높음) + <ul> + <li>동적페이지</li> + <li>API 요청 없음</li> + </ul> + </li> + <li>페이지 E (TPS 매우 높음) + <ul> + <li>정적페이지</li> + </ul> + </li> +</ul> + +<p>이 결과들을 보고 내린 결론은 다음과 같다.</p> + +<ul> + <li>성능 저하가 크지 않은 경우 + <ul> + <li>next.js 내에서만 연산이 이루어지는 경우 (페이지 D, E)</li> + <li>A’로 보내는 API 중 A’가 스스로 처리하는 API를 사용하는 경우 (페이지 C)</li> + </ul> + </li> + <li>성능 저하가 큰 경우 + <ul> + <li>A’로 보내는 API 중 A’가 중개 API 로써 동작하는 경우 (페이지 A)</li> + <li>B’로 API를 요청하는 경우 (페이지 A, B)</li> + </ul> + </li> +</ul> + +<h2 id="최적화">최적화</h2> + +<p>아래 두 전략을 가지고 최적화를 진행하였다.</p> + +<ol> + <li>API 요청을 최소화하기</li> + <li>요청량을 기준으로 HPA metric을 산정하기</li> +</ol> + +<p>자세한 내용은 다음과 같다.</p> + +<h3 id="api-요청-최소화하기">API 요청 최소화하기</h3> + +<p>기본적으로 API 요청이 발생하는 페이지에서 성능저하가 발생하였다. 따라서 API 요청을 최소화하는게 최우선 목표라고 생각하였다.</p> + +<h4 id="불필요한-호출-줄이기">불필요한 호출 줄이기</h4> + +<p>가장 성능이 안좋았던 페이지 A는 A’로 API 요청을 2개 보내고, B’로 1개의 요청을 보낸다. +A’로 보내는 두개의 요청은 기존에는 다음과 같이 코드가 짜여있었다.</p> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +</pre></td><td class="rouge-code"><pre><span class="k">export</span> <span class="kd">const</span> <span class="nx">getServerSideProps</span> <span class="o">=</span> <span class="k">async</span><span class="p">(</span><span class="nx">context</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> + <span class="p">...</span> + <span class="kd">const</span> <span class="nx">userProfileData</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">getUserProfileData</span><span class="p">();</span> + <span class="kd">const</span> <span class="nx">userContentData</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">getUserContentData</span><span class="p">();</span> + <span class="p">...</span> + <span class="k">return</span> <span class="p">{</span> + <span class="na">props</span> <span class="p">:</span> <span class="p">{</span> + <span class="nx">userData</span><span class="p">,</span> + <span class="nx">userContentData</span><span class="p">,</span> + <span class="p">}</span> + <span class="p">}</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p><code class="language-plaintext highlighter-rouge">getUserProfileData()</code> 와 <code class="language-plaintext highlighter-rouge">getUserContentData()</code>는 유저가 로그인 되어있든 안되어있든 항상 A’로 요청을 보낸다. 그리고 A’는 로그인 되어있지 않으면 null을 응답한다. +처음 이 코드를 짰을때는 성능에 대해 크게 걱정이 없어서 이렇게 항상 요청하는 식으로 짰다.(코드 조금 더 쓰기 귀찮았다..) 하지만 로그인 여부는 next.js 단에서도 API 요청 없이 알 수 있도록 설정이 되어있었다. 따라서 다음과 같이 next.js 에서 먼저 로그인 여부를 확인하고 로그인 되어있으면 요청하는 식으로 코드를 변경하였다.</p> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +</pre></td><td class="rouge-code"><pre><span class="k">export</span> <span class="kd">const</span> <span class="nx">getServerSideProps</span> <span class="o">=</span> <span class="k">async</span><span class="p">(</span><span class="nx">context</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> + <span class="kd">const</span> <span class="p">{</span> <span class="nx">isLoggedIn</span> <span class="p">}</span> <span class="o">=</span> <span class="nf">getAuthentication</span><span class="p">();</span> + <span class="p">...</span> + <span class="kd">let</span> <span class="nx">userProfileData</span> <span class="o">=</span> <span class="kc">null</span><span class="p">,</span> <span class="nx">userContentData</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span> + <span class="k">if</span><span class="p">(</span><span class="nx">isLoggedIn</span><span class="p">)</span> <span class="p">{</span> + <span class="nx">userProfileData</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">getUserProfileData</span><span class="p">();</span> + <span class="nx">userContentData</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">getUserContentData</span><span class="p">();</span> + + <span class="p">}</span> + <span class="p">...</span> + <span class="k">return</span> <span class="p">{</span> + <span class="na">props</span> <span class="p">:</span> <span class="p">{</span> + <span class="nx">userData</span><span class="p">,</span> + <span class="nx">userContentData</span><span class="p">,</span> + <span class="p">}</span> + <span class="p">}</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>이로써 미로그인 유저들이 불필요한 API 요청을 보내는 일이 없어졌다. 조금 더 개선을 하자면, <code class="language-plaintext highlighter-rouge">getUserProfileData()</code> 와 <code class="language-plaintext highlighter-rouge">getUserContentData()</code>를 하나의 API로 합칠 수도 있으나 이건 API 개발자의 작업이니 이 글에선 자세히 작성하진 않겠다.</p> + +<p>또 여기서 한번 더 최적화를 한다면 다음과 같이 <code class="language-plaintext highlighter-rouge">Promise.all</code>을 사용하여 요청을 동시에 보낼 수도 있다.</p> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +</pre></td><td class="rouge-code"><pre><span class="k">export</span> <span class="kd">const</span> <span class="nx">getServerSideProps</span> <span class="o">=</span> <span class="k">async</span><span class="p">(</span><span class="nx">context</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> + <span class="kd">const</span> <span class="p">{</span> <span class="nx">isLoggedIn</span> <span class="p">}</span> <span class="o">=</span> <span class="nf">getAuthentication</span><span class="p">();</span> + <span class="p">...</span> + <span class="kd">let</span> <span class="nx">userProfileData</span> <span class="o">=</span> <span class="kc">null</span><span class="p">,</span> <span class="nx">userContentData</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span> + <span class="k">if</span><span class="p">(</span><span class="nx">isLoggedIn</span><span class="p">)</span> <span class="p">{</span> + <span class="p">[</span><span class="nx">userProfileData</span><span class="p">,</span> <span class="nx">userContentData</span><span class="p">]</span> <span class="o">=</span> <span class="k">await</span> <span class="nb">Promise</span><span class="p">.</span><span class="nf">all</span><span class="p">([</span><span class="nf">getUserProfileData</span><span class="p">(),</span> <span class="nf">getUserContentData</span><span class="p">()])</span> + <span class="p">}</span> + <span class="p">...</span> + <span class="k">return</span> <span class="p">{</span> + <span class="na">props</span> <span class="p">:</span> <span class="p">{</span> + <span class="nx">userData</span><span class="p">,</span> + <span class="nx">userContentData</span><span class="p">,</span> + <span class="p">}</span> + <span class="p">}</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>기존에는 <code class="language-plaintext highlighter-rouge">getUserProfileData()</code>의 응답시간 + <code class="language-plaintext highlighter-rouge">getUserContentData()</code>의 응답시간으로 최종 응답시간이 정해졌으나, <code class="language-plaintext highlighter-rouge">Promise.all</code>을 사용하면 둘 중 더 느린 응답시간으로 최종 응답시간이 결정되기 때문이다.</p> + +<h4 id="캐시-사용">캐시 사용</h4> + +<p>다음은 B’로 보내는 요청을 최적화한 방법이다. B’는 콘텐츠를 가져오는 API로 기존에는 매요청마다 호출하고 있었다.</p> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +</pre></td><td class="rouge-code"><pre><span class="k">export</span> <span class="kd">const</span> <span class="nx">getServerSideProps</span> <span class="o">=</span> <span class="k">async</span><span class="p">(</span><span class="nx">context</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> + <span class="p">...</span> + <span class="kd">const</span> <span class="nx">contentData</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">getContentData</span><span class="p">(</span><span class="dl">'</span><span class="s1">page-a</span><span class="dl">'</span><span class="p">);</span> + <span class="p">...</span> + <span class="k">return</span> <span class="p">{</span> + <span class="na">props</span> <span class="p">:</span> <span class="p">{</span> + <span class="nx">contentData</span> + <span class="p">}</span> + <span class="p">}</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>콘텐츠의 내용이 변경가능하여 항상 호출하였으나, 변경 사항을 즉시 반영해야할 심각도를 가진 콘텐츠는 아니었다. 즉 캐시를 사용하여 콘텐츠 변경의 반영이 어느정도 늦어져도 문제는 없었다. 따라서 <a href="https://www.npmjs.com/package/memory-cache">memory-cache</a>라는 in-memory cache 을 쉽게 사용할 수 있게 도와주는 라이브러리를 사용하기로 하였다.</p> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +</pre></td><td class="rouge-code"><pre><span class="k">import</span> <span class="nx">cacheData</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">memory-cache</span><span class="dl">"</span><span class="p">;</span> + +<span class="k">export</span> <span class="kd">const</span> <span class="nx">getServerSideProps</span> <span class="o">=</span> <span class="k">async</span><span class="p">(</span><span class="nx">context</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> + <span class="p">...</span> + <span class="kd">const</span> <span class="nx">key</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">page-a</span><span class="dl">"</span> + <span class="kd">let</span> <span class="nx">contentData</span> <span class="o">=</span> <span class="nx">cacheData</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="nx">key</span><span class="p">);</span> + <span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="nx">contentData</span><span class="p">)</span> <span class="p">{</span> + <span class="nx">contentData</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">getContentData</span><span class="p">(</span><span class="nx">key</span><span class="p">);</span> + <span class="nx">cacheData</span><span class="p">.</span><span class="nf">put</span><span class="p">(</span><span class="nx">key</span><span class="p">,</span> <span class="nx">contentData</span><span class="p">,</span> <span class="mi">60000</span><span class="p">);</span> + <span class="p">}</span> + <span class="p">...</span> + <span class="k">return</span> <span class="p">{</span> + <span class="na">props</span> <span class="p">:</span> <span class="p">{</span> + <span class="nx">contentData</span> + <span class="p">}</span> + <span class="p">}</span> + +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>이렇게 두가지만 적용한 후, 다시 페이지 A를 ngrinder로 테스트한 결과는 다음과 같았다.</p> + +<ul> + <li>미로그인 유저 -&gt; TPS 15배 상승</li> + <li>로그인 유저 -&gt; TPS 2~3배 상승 +로그인 유저의 경우 미로그인 유저에 비해 상승량은 적었지만 기존보다는 크게 상승했다.</li> +</ul> + +<h3 id="hpa-metric을-요청량-기준으로-변경">HPA metric을 요청량 기준으로 변경</h3> + +<p>이번 이슈를 겪으면서 알게 된 것은 CPU, Memory 사용량이 많아져서 응답이 느리게 갈 가능성은 적다는 것이었다. 정적페이지 및 API 요청 없는 동적페이지의 경우 매우 빠르게 처리하고 있었고, pod 개수도 많아서 예상되는 최대치의 트래픽이 들어오더라도 CPU, Memory 사용량이 많이 점유되는 가능성은 적었다. 반대로 API 요청이 발생하는 페이지에서는 예상보다 훨씬 적은 트래픽이 들어오더라도 터지는 일이 발생하였다.</p> + +<p>따라서 서버 응답 장애가 발생한다면, CPU/Memory 사용량은 낮지만 API 요청이 발생하는 페이지에 요청이 몰려 발생할 확률이 훨씬 컸다.</p> + +<p>이에따라 기존에는 HPA metric을 다음과 같이 CPU/Memory 기준으로 작성하였는데, network 요청량이 일정 수준이상이 되면 발생하도록 변경하기로 하였다. (아래 yaml 파일들은 실제 파일이 아닌 예시)</p> + +<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +</pre></td><td class="rouge-code"><pre><span class="c1"># 기존 hpa 설정</span> +<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">autoscaling/v1</span> +<span class="na">kind</span><span class="pi">:</span> <span class="s">HorizontalPodAutoscaler</span> +<span class="na">metadata</span><span class="pi">:</span> + <span class="na">name</span><span class="pi">:</span> <span class="s">next-app-hpa</span> + <span class="na">namespace</span><span class="pi">:</span> <span class="s">default</span> +<span class="na">spec</span><span class="pi">:</span> + <span class="na">scaleTargetRef</span><span class="pi">:</span> + <span class="na">apiVersion</span><span class="pi">:</span> <span class="s">apps/v1</span> + <span class="na">kind</span><span class="pi">:</span> <span class="s">Deployment</span> + <span class="na">name</span><span class="pi">:</span> <span class="s">next-app</span> + <span class="na">minReplicas</span><span class="pi">:</span> <span class="m">1</span> + <span class="na">maxReplicas</span><span class="pi">:</span> <span class="m">10</span> + <span class="na">targetCPUUtilizationPercentage</span><span class="pi">:</span> <span class="m">50</span> <span class="c1"># CPU가 50%로 맞춰지도록 스케일링 발생</span> + <span class="na">targetmemoryutilizationpercentage</span><span class="pi">:</span> <span class="m">50</span> <span class="c1"># Memory가 50%로 맞춰지도록 스케일링 발생</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +</pre></td><td class="rouge-code"><pre><span class="c1"># 변경한 hpa 설정</span> +<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">autoscaling/v2beta2</span> +<span class="na">kind</span><span class="pi">:</span> <span class="s">HorizontalPodAutoscaler</span> +<span class="na">metadata</span><span class="pi">:</span> + <span class="na">name</span><span class="pi">:</span> <span class="s">next-app-hpa</span> + <span class="na">namespace</span><span class="pi">:</span> <span class="s">default</span> +<span class="na">spec</span><span class="pi">:</span> + <span class="na">scaleTargetRef</span><span class="pi">:</span> + <span class="na">apiVersion</span><span class="pi">:</span> <span class="s">apps/v1</span> + <span class="na">kind</span><span class="pi">:</span> <span class="s">Deployment</span> + <span class="na">name</span><span class="pi">:</span> <span class="s">next-app</span> + <span class="na">minReplicas</span><span class="pi">:</span> <span class="m">1</span> + <span class="na">maxReplicas</span><span class="pi">:</span> <span class="m">10</span> + <span class="na">metrics</span><span class="pi">:</span> + <span class="pi">-</span> <span class="na">object</span><span class="pi">:</span> + <span class="na">describedObject</span><span class="pi">:</span> + <span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span> + <span class="na">kind</span><span class="pi">:</span> <span class="s">Service</span> + <span class="na">name</span><span class="pi">:</span> <span class="s">test</span> + <span class="na">metric</span><span class="pi">:</span> + <span class="na">name</span><span class="pi">:</span> <span class="s">test_requests_per_second</span> + <span class="na">target</span><span class="pi">:</span> + <span class="na">type</span><span class="pi">:</span> <span class="s">Value</span> + <span class="na">value</span><span class="pi">:</span> <span class="m">200</span> <span class="c1"># 초당 200번 이상 요청이 들어올 시 scale-out 발생</span> + <span class="na">type</span><span class="pi">:</span> <span class="s">Object</span> + <span class="pi">-</span> <span class="na">resource</span><span class="pi">:</span> + <span class="na">name</span><span class="pi">:</span> <span class="s">cpu</span> <span class="c1"># CPU 설정도 혹시 모르니 남겨둠(CPU가 터지는 상항이 생길 수 있으니..)</span> + <span class="na">target</span><span class="pi">:</span> + <span class="na">averageUtilization</span><span class="pi">:</span> <span class="m">50</span> + <span class="na">type</span><span class="pi">:</span> <span class="s">Utilization</span> + <span class="na">type</span><span class="pi">:</span> <span class="s">Resource</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p><a href="https://tech.scatterlab.co.kr/kubernetes-hpa-custom-metric/">참고</a></p> + +<p>다시 ngrinder로 테스트를 하니 scale-out 이 정상적으로 동작하였다. +이 두가지 방법을 적용하여 최적화를 완료하였고, 이후 트래픽이 많이 발생하여도 정상적으로 대응 가능하였다.</p> + + Sun, 22 Oct 2023 16:00:00 +0900 + https://seongil-shin.github.io/posts/next.js-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%B5%9C%EC%A0%81%ED%99%94-%EA%B3%BC%EC%A0%95/ + https://seongil-shin.github.io/posts/next.js-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%B5%9C%EC%A0%81%ED%99%94-%EA%B3%BC%EC%A0%95/ + + next.js + + + study + + next.js + + + + + next.js caching + <p>next.js app router의 <a href="https://nextjs.org/docs/app/building-your-application/caching">caching</a>에 관해 공부한 내용입니다.</p> + +<h2 id="overview">Overview</h2> + +<p>next.js 에서 제공하는 caching mechanism에는 다음과 같은 것들이 있다.</p> + +<table> + <thead> + <tr> + <th>Mechanism</th> + <th>What</th> + <th>Where</th> + <th>Purpose</th> + <th>Duration</th> + </tr> + </thead> + <tbody> + <tr> + <td><a href="https://nextjs.org/docs/app/building-your-application/caching#request-memoization">Request Memoization</a></td> + <td>Return values of functions</td> + <td>Server</td> + <td>Re-use data in a React Component tree</td> + <td>Per-request lifecycle</td> + </tr> + <tr> + <td><a href="https://nextjs.org/docs/app/building-your-application/caching#data-cache">Data Cache</a></td> + <td>Data</td> + <td>Server</td> + <td>Store data across user requests and deployments</td> + <td>Persistent (can be revalidated)</td> + </tr> + <tr> + <td><a href="https://nextjs.org/docs/app/building-your-application/caching#full-route-cache">Full Route Cache</a></td> + <td>HTML and RSC payload</td> + <td>Server</td> + <td>Reduce rendering cost and improve performance</td> + <td>Persistent (can be revalidated)</td> + </tr> + <tr> + <td><a href="https://nextjs.org/docs/app/building-your-application/caching#router-cache">Router Cache</a></td> + <td>RSC Payload</td> + <td>Client</td> + <td>Reduce server requests on navigation</td> + <td>User session or time-based</td> + </tr> + </tbody> +</table> + +<p>next.js는 가능한 많은 캐싱을 적용하려고 하고, 따라서 따로 opt-out하지 않는한 정적으로 렌더링되고, 요청은 캐싱된다. +각 mechanism은 다음 도식처럼 동작한다.</p> + +<p><img src="/assets/img/2023-08-07-next-caching/image?url=%2Fdocs%2Fdark%2Fcaching-overview.png" alt="Diagram showing the default caching behavior in Next.js for the four mechanisms, with HIT, MISS and SET at build time and when a route is first visited." /></p> + +<p><br /></p> + +<h2 id="request-memoization">Request Memoization</h2> + +<p>React에서 확장한 <a href="https://nextjs.org/docs/app/building-your-application/caching#fetch">fetch API</a>가 자동으로 같은 URL과 옵션으로 보내진 요청의 결과를 기억한다. 따라서 한 route를 렌더링할때 같은 요청이 여러번 발생하면, 딱 한번만 요청을 보내고 그 결과를 여러 곳에서 사용한다.</p> + +<ul> + <li>Duration : React Component tree의 렌더링이 종료될때까지만 유지. 렌더링 종료시 초기화됨.</li> + <li>Revalidating : server requests 사이에서 공유되지않으므로 revalidating할 이유가 없음</li> + <li>Opting-out : <code class="language-plaintext highlighter-rouge">fetch</code> API에 <code class="language-plaintext highlighter-rouge">AbortController signal</code>을 줄 수 있음. <a href="https://nextjs.org/docs/app/building-your-application/caching#react-cache-function">예시</a></li> + <li>기타 + <ul> + <li>next.js가 아닌 React 기능임</li> + <li><code class="language-plaintext highlighter-rouge">fetch</code> 함수의 <code class="language-plaintext highlighter-rouge">GET</code> method에서만 동작함</li> + <li>React Component tree에서만 동작하므로, route handler에서는 동작하지 않음.</li> + <li><code class="language-plaintext highlighter-rouge">fetch</code>를 쓸 수 없는 환경에서는 <a href="https://nextjs.org/docs/app/building-your-application/caching#react-cache-function">React cache function</a>을 사용할 수 있음.</li> + </ul> + </li> +</ul> + +<p><br /></p> + +<h2 id="data-cache">Data Cache</h2> + +<p>여러 server requests와 deployments 사이에서 공유되는 캐시로, next.js에서 확장한 <code class="language-plaintext highlighter-rouge">fetch API</code>를 사용하면 적용된다. <code class="language-plaintext highlighter-rouge">fetch API</code>의 <code class="language-plaintext highlighter-rouge">cache</code>, <code class="language-plaintext highlighter-rouge">next.revalidate</code> 옵션으로 설정할 수 있으며 자세한 내용은 <a href="https://nextjs.org/docs/app/api-reference/functions/fetch">fetch API references</a>에서 볼 수 있다.</p> + +<ul> + <li>Duration : 따로 revlidate하거나 opt-out하지 않는한 계속 유지된다.</li> + <li>Revalidating + <ul> + <li>Time-based Revalidation : 특정한 시간마다 revalidate 할 수 있음. + <ul> + <li>동작방식 : <code class="language-plaintext highlighter-rouge">{ next: { revalidate: 60 } }</code> 일 때, + <ul> + <li>최초 요청 시 : data source로 API 요청 후, 결과를 캐싱함.</li> + <li>60초 이전 재요청 : 캐시된 데이터 리턴</li> + <li>60초 이후 재요청 : 캐시된 데이터(stale 상태) 리턴, 새로운 데이터 fetch 후 결과 캐싱. <strong>요청이 실패할 경우 이전 데이터(stale)를 계속 유지함.</strong></li> + </ul> + </li> + </ul> + </li> + <li>On-demand Revalidation : 원할때 revalidate 할 수 있음. + <ul> + <li>동작방식 + <ul> + <li>최초 요청 시 : data source로 API 요청 후, 결과를 캐싱함.</li> + <li>on-demand revalidation 시 : 캐시된 데이터를 <code class="language-plaintext highlighter-rouge">PURGE</code> 시킴</li> + <li>재요청 시 : data source로 API 요청 후, 결과를 캐싱함.</li> + </ul> + </li> + </ul> + </li> + </ul> + </li> + <li>Oping-out + <ul> + <li><code class="language-plaintext highlighter-rouge">fetch API</code> 옵션 : <code class="language-plaintext highlighter-rouge">{ cache: 'no-store' }</code>와 같이 <code class="language-plaintext highlighter-rouge">fetch API</code> 옵션으로 Data Cache를 사용하지 않을 수 있음.</li> + <li><a href="https://nextjs.org/docs/app/building-your-application/caching#segment-config-options">Route Segment Config options</a> : 특정 route segment를 기준으로 opt-out 할 수 있음. 해당 route segment에 포함된 모든 data request에 적용된다. + <ul> + <li><code class="language-plaintext highlighter-rouge">export const dynamic = 'force-dynamic'</code></li> + <li><code class="language-plaintext highlighter-rouge">export const revalidate = 0</code></li> + </ul> + </li> + </ul> + </li> + <li>기타 + <ul> + <li><code class="language-plaintext highlighter-rouge">{ cache : 'no-store'}</code>와 같이 캐시를 사용하지 않도록 설정해도 <code class="language-plaintext highlighter-rouge">Request Memoization</code>은 일어난다. (리액트 동작이기에)</li> + </ul> + </li> +</ul> + +<p><br /></p> + +<h2 id="full-route-cache">Full Route Cache</h2> + +<p>next.js는 빌드는 각 route를 렌더링하고 결과를 캐싱한다. 따라서 매 요청마다 렌더링하지 않고 캐시된 route를 응답하여 페이지 로딩을 빠르게 마칠 수 있게한다.</p> + +<h3 id="동작방식">동작방식</h3> + +<ol> + <li>React Rendering on the Server + <ul> + <li>next.js는 React API를 사용하여 렌더링을 오케스트레이션한다. 이때 각 route segments와 Suspense baoundaries를 chunks로 나눈다.</li> + <li>각 chunk는 두 단계로 렌더링된다. + <ol> + <li>React가 Server Component를 React Server Component Payload라는 스트리밍에 최적화된 특수한 데이터 포맷으로 렌더한다.</li> + <li>next.js는 React Server Component Payload와 Client Component JS instruction을 사용하여 HTML을 서버에서 렌더링한다.</li> + </ol> + </li> + <li>이로써 작업을 캐싱하거나 응답을 보내기 전에 모든 것이 렌더링될 때까지 기다리지 않고, 작업 완료 시 응답을 스트리밍 할 수 있다.</li> + <li>참고) React Server Component Payload + <ul> + <li>렌더된 React Server Components tree의 압축 이진 표현. 클라이언트에서 browser’s DOM을 업데이트하기 위해 사용된다.</li> + <li>아래 것들을 포함 + <ul> + <li>Server Components의 렌더링 결과</li> + <li>Client Component가 렌더되고 참조될 위치에 대한 placeholder</li> + <li>Server Component에서 Client Component로 내려줄 props</li> + </ul> + </li> + </ul> + </li> + </ul> + </li> + <li>Next.js Caching on the Server (Full Route Server) + <ul> + <li>기본적으로 next.js는 렌더링 결과 (React Server Component Payload, HTML)을 캐싱한다. 빌드타임 또는 revalidation 시 생성되는 결과 캐싱</li> + </ul> + </li> + <li>React Hydration and Reconciliation on the Client : 요청 시 클라이언트에서의 동작 + <ol> + <li>HTML 렌더링. 상호작용 불가능</li> + <li>React Server Component Payload를 사용하여 Client와 렌더된 Server Component tree를 조정하고 DOM을 업데이트한다.</li> + <li>JS instruction으로 Client Components를 hydration하고 상호작용을 가능하게 한다.</li> + </ol> + </li> + <li>Next.js Caching on the Client (Router Cache) + <ul> + <li>React Server Component Payload는 클라이언트에 Router Cache로서 저장된다.</li> + <li>Router Cache는 navigation 시 이전 방문했던 페이지를 빠르게 렌더링하거나, 다음 route를 prefetching한다.</li> + </ul> + </li> + <li>Subsequent Navigations + <ul> + <li>prefetching이나 subsequent navigation 도중 next.js는 React Server Component Payload가 Router Cache에 있는지 검사하고, 있으면 새로운 요청을 server로 보내지 않는다.</li> + </ul> + </li> +</ol> + +<h3 id="static-and-dynamic-rendering">Static and Dynamic Rendering</h3> + +<p>route가 빌드타임에 캐시 되는지 안되는지는 그 route가 정적인지 동적으로 렌더링되는지에 달려있다. Static route는 기본적으로 캐시되며, dynamic route는 요청시 렌더링되고 캐시되지 않는다.</p> + +<h3 id="기타">기타</h3> + +<ul> + <li>Duration : 기본적으로 영구적이며 여러 request에서 재사용된다.</li> + <li>Invalidations + <ul> + <li>Revalidating Data : Data Cache를 revalidaing하면 컴포넌트를 서버에서 리렌더링하고, 새로운 렌더링 결과를 캐싱하여 Router Cache도 invalidation 된다.</li> + <li>Redeploying : Data cache와는 달리, Full Route Cache는 deployments 사이에서 유지되지 않는다.</li> + </ul> + </li> + <li>Opting out + <ul> + <li><a href="https://nextjs.org/docs/app/building-your-application/caching#dynamic-functions">Dynamic Function</a> 사용 시 Full Route Cache는 opt-out 되고 요청시 렌더링된다. Data Cache는 유지된다.</li> + <li><code class="language-plaintext highlighter-rouge">dynamic = 'force-dynamic'</code>, <code class="language-plaintext highlighter-rouge">revalidate=0</code> route segment config 옵션 사용 + <ul> + <li>Full Route Cache, Data Cache 모두 opting-out 한다.</li> + <li>Router Cache는 클라이언트 동작이므로 유지된다.</li> + </ul> + </li> + <li>Opting out Data Cache : <code class="language-plaintext highlighter-rouge">fetch API</code>를 캐시하지 않으면 Full Route Cache도 opt-out 된다. 매번 새로운 요청을 보내기 때문이다.</li> + </ul> + </li> +</ul> + +<p><br /></p> + +<h2 id="router-cache">Router Cache</h2> + +<p>next.js가 클라이언트 사이드에서 가지고 있는 in-memory 캐시이다. React Server Component Payload를 저장하며 각 route segments 별로 분리되어있다. 유저가 routes 사이를 이동할 떄마다 prefetch되거나 방문한 route segment를 저장한다. browser의 bfcache와는 다른 것이지만 비슷한 결과를 낸다.</p> + +<ul> + <li>Duration : 브라우저 임시 메모리에 저장되며 다음 두 가지 요인에 의해 duration이 결정된다. + <ul> + <li>Session : Navigation 사이에서 유지되지만, 새로고침 시 사라진다.</li> + <li>Automatic Invalidation Period : 개별 segment의 캐시는 특정 시간 이후에 invalidation 된다. + <ul> + <li>동적 렌더링 결과물 : 30초</li> + <li>정적 렌더링 결과물 : 5분</li> + <li>prefetching 된 동적 렌더링 : 5분까지 저장하도록 설정가능</li> + </ul> + </li> + </ul> + </li> + <li>Invalidation + <ul> + <li><a href="https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions">Server Action</a>에서</li> + <li>On-demand revalidaing 사용 : <code class="language-plaintext highlighter-rouge">revalidatePath</code>, <code class="language-plaintext highlighter-rouge">revalidateTag</code></li> + <li><code class="language-plaintext highlighter-rouge">cookies.set</code>, <code class="language-plaintext highlighter-rouge">cookies.delete</code> 사용 (쿠키를 사용하는 route가 stale 되는 것들 방지하기 위해)</li> + <li>클라이언트에서 : <code class="language-plaintext highlighter-rouge">router.refresh</code>를 사용하면 Router Cache가 전부 clear 됨</li> + </ul> + </li> + <li>Opting out : Router Cache는 opt-out이 불가능하다. prefetch의 경우는 <code class="language-plaintext highlighter-rouge">&lt;Link&gt;</code> 컴포넌트에서 props로 opt-out할 수 있지만 방문한 페이지에 대한 캐시는 여전히 남는다.</li> +</ul> + +<p><br /></p> + +<h2 id="cache-interactions">Cache Interactions</h2> + +<h3 id="data-cache-and-full-route-cache">Data Cache and Full Route Cache</h3> + +<ul> + <li>Data Cache를 opt-out하거나 revalidating 하면 Full Route Cache도 마찬가지로 적용된다.</li> + <li>Full Route Cache를 opt-out 한다고 Data Cache는 opt-out 되지 않는다. 한 route 안에 Data Cache가 적용된 요청과 opt-out 된 요청이 공존할 수 있다.</li> +</ul> + +<h3 id="data-cache-and-client-side-router-cache">Data Cache and Client-side Router Cache</h3> + +<ul> + <li><code class="language-plaintext highlighter-rouge">Route Handler</code>에서 Data Cache를 revalidating하여도 Router Cache가 바로 invalidate 되지 않는다. Router Handler는 특정 route와 묶여있지 않기 때문이다. Router Cache는 유저가 새로고침하거나 <code class="language-plaintext highlighter-rouge">Automatic Invalidation Period</code>가 지날때까지 유지된다.</li> + <li>즉각적으로 Router Cache를 무효화시키고 싶으면, <a href="https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions">Server Action</a>에서 <code class="language-plaintext highlighter-rouge">revalidatePath</code>, <code class="language-plaintext highlighter-rouge">revalidateTag</code>를 사용할 수 있다.</li> +</ul> + +<p><br /></p> + +<h2 id="apis">APIs</h2> + +<p>각 next.js API가 영향을 끼치는 캐시정리</p> + +<table> + <thead> + <tr> + <th>API</th> + <th>Router Cache</th> + <th>Full Route Cache</th> + <th>Data Cache</th> + <th>React Cache</th> + </tr> + </thead> + <tbody> + <tr> + <td><a href="https://nextjs.org/docs/app/building-your-application/caching#link"><code class="language-plaintext highlighter-rouge">&lt;Link prefetch&gt;</code></a></td> + <td>Cache</td> + <td> </td> + <td> </td> + <td> </td> + </tr> + <tr> + <td><a href="https://nextjs.org/docs/app/building-your-application/caching#routerprefetch"><code class="language-plaintext highlighter-rouge">router.prefetch</code></a></td> + <td>Cache</td> + <td> </td> + <td> </td> + <td> </td> + </tr> + <tr> + <td><a href="https://nextjs.org/docs/app/building-your-application/caching#routerrefresh"><code class="language-plaintext highlighter-rouge">router.refresh</code></a></td> + <td>Revalidate</td> + <td> </td> + <td> </td> + <td> </td> + </tr> + <tr> + <td><a href="https://nextjs.org/docs/app/building-your-application/caching#fetch"><code class="language-plaintext highlighter-rouge">fetch</code></a></td> + <td> </td> + <td> </td> + <td>Cache</td> + <td>Cache</td> + </tr> + <tr> + <td><a href="https://nextjs.org/docs/app/building-your-application/caching#fetch-optionscache"><code class="language-plaintext highlighter-rouge">fetch options.cache</code></a></td> + <td> </td> + <td> </td> + <td>Cache or Opt out</td> + <td> </td> + </tr> + <tr> + <td><a href="https://nextjs.org/docs/app/building-your-application/caching#fetch-optionsnextrevalidate"><code class="language-plaintext highlighter-rouge">fetch options.next.revalidate</code></a></td> + <td> </td> + <td>Revalidate</td> + <td>Revalidate</td> + <td> </td> + </tr> + <tr> + <td><a href="https://nextjs.org/docs/app/building-your-application/caching#fetch-optionsnexttag-and-revalidatetag"><code class="language-plaintext highlighter-rouge">fetch options.next.tags</code></a></td> + <td> </td> + <td>Cache</td> + <td>Cache</td> + <td> </td> + </tr> + <tr> + <td><a href="https://nextjs.org/docs/app/building-your-application/caching#fetch-optionsnexttag-and-revalidatetag"><code class="language-plaintext highlighter-rouge">revalidateTag</code></a></td> + <td> </td> + <td>Revalidate</td> + <td>Revalidate</td> + <td> </td> + </tr> + <tr> + <td><a href="https://nextjs.org/docs/app/building-your-application/caching#revalidatepath"><code class="language-plaintext highlighter-rouge">revalidatePath</code></a></td> + <td> </td> + <td>Revalidate</td> + <td>Revalidate</td> + <td> </td> + </tr> + <tr> + <td><a href="https://nextjs.org/docs/app/building-your-application/caching#segment-config-options"><code class="language-plaintext highlighter-rouge">const revalidate</code></a></td> + <td> </td> + <td>Revalidate or Opt out</td> + <td>Revalidate or Opt out</td> + <td> </td> + </tr> + <tr> + <td><a href="https://nextjs.org/docs/app/building-your-application/caching#segment-config-options"><code class="language-plaintext highlighter-rouge">const dynamic</code></a></td> + <td> </td> + <td>Cache or Opt out</td> + <td>Cache or Opt out</td> + <td> </td> + </tr> + <tr> + <td><a href="https://nextjs.org/docs/app/building-your-application/caching#cookies"><code class="language-plaintext highlighter-rouge">cookies</code></a></td> + <td>Revalidate</td> + <td>Opt out</td> + <td> </td> + <td> </td> + </tr> + <tr> + <td><a href="https://nextjs.org/docs/app/building-your-application/caching#dynamic-functions"><code class="language-plaintext highlighter-rouge">headers</code>, <code class="language-plaintext highlighter-rouge">useSearchParams</code>, <code class="language-plaintext highlighter-rouge">searchParams</code></a></td> + <td> </td> + <td>Opt out</td> + <td> </td> + <td> </td> + </tr> + <tr> + <td><a href="https://nextjs.org/docs/app/building-your-application/caching#generatestaticparams"><code class="language-plaintext highlighter-rouge">generateStaticParams</code></a></td> + <td> </td> + <td>Cache</td> + <td> </td> + <td> </td> + </tr> + <tr> + <td><a href="https://nextjs.org/docs/app/building-your-application/caching#react-cache-function"><code class="language-plaintext highlighter-rouge">React.cache</code></a></td> + <td> </td> + <td> </td> + <td> </td> + <td>Cache</td> + </tr> + <tr> + <td><a href="https://nextjs.org/docs/app/building-your-application/caching#unstable_cache"><code class="language-plaintext highlighter-rouge">unstable_cache</code></a> (Coming Soon)</td> + <td> </td> + <td> </td> + <td> </td> + <td> </td> + </tr> + </tbody> +</table> + +<p><br /></p> + +<h2 id="현재-존재하는-이슈">현재 존재하는 이슈</h2> + +<h3 id="route-segment-config로-opt-out해도-캐시가-적용되는-이슈">route segment config로 opt-out해도 캐시가 적용되는 이슈</h3> + +<p>현재 <a href="https://nextjs.org/docs/app/building-your-application/caching#segment-config-options">공식문서</a>에 따르면 다음 두 설정은 Data Cache과 Full Route Cache를 opt-out 한다고 한다.</p> + +<ul> + <li><code class="language-plaintext highlighter-rouge">const dynamic = 'force-dynamic'</code></li> + <li><code class="language-plaintext highlighter-rouge">const revalidate = 0</code></li> +</ul> + +<p>하지만 다음과 같은 이슈를 찾을 수 있었다.</p> + +<ul> + <li><a href="https://github.com/vercel/next.js/issues/51788">Next13 caching fetched data even with the ‘revalidate = 0’ route segment </a></li> +</ul> + +<p><code class="language-plaintext highlighter-rouge">revalidate = 0</code>을 주어도 캐시가 적용된다는 이슈이다. 해결방법으로는 다음 두가지가 제시되었지만, 공식적인 답변은 없었다.</p> + +<ul> + <li>데이터 변경 후 <code class="language-plaintext highlighter-rouge">router.refresh()</code></li> + <li><code class="language-plaintext highlighter-rouge">&lt;Link&gt;</code> 대신 <code class="language-plaintext highlighter-rouge">&lt;a&gt;</code> 사용</li> +</ul> + +<h3 id="route-segment-config와-fetch-api의-revalidate-설정-충돌-문제">route segment config와 fetch API의 revalidate 설정 충돌 문제</h3> + +<p><a href="https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#revalidate">next.js 공식문서</a>에 따르면, <code class="language-plaintext highlighter-rouge">revalidate</code> 속성은 한 route에서 가장 낮은 값으로 설정된다고 한다. 그리고 위 설명에서 <code class="language-plaintext highlighter-rouge">revalidate = 0</code>은 data cache를 opt-out 한다고 하였으니 route segment config로 <code class="language-plaintext highlighter-rouge">revalidate =0</code>을 설정하면, fetch API에서 <code class="language-plaintext highlighter-rouge">revalidate : 60</code>을 설정해도 data cache가 무시될 줄 알았다.</p> + +<p>하지만 실제로 다음과 같은 코드로 적용해본 결과 data cache가 opt-out 되지 않았다. 캐시가 계속 이루어지고 있었다.</p> + +<ul> + <li>코드</li> +</ul> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +</pre></td><td class="rouge-code"><pre><span class="c1">// app/layout.js</span> +<span class="k">export</span> <span class="kd">const</span> <span class="nx">dynamic</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">force-dynamic</span><span class="dl">"</span> + +<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">Layout</span><span class="p">({</span> <span class="nx">children</span> <span class="p">})</span> <span class="p">{</span> + <span class="k">return </span><span class="p">(</span> + <span class="o">&lt;</span><span class="nx">div</span><span class="o">&gt;</span> + <span class="p">{</span><span class="nx">children</span><span class="p">}</span> + <span class="o">&lt;</span><span class="sr">/div</span><span class="err">&gt; +</span> <span class="p">)</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +</pre></td><td class="rouge-code"><pre><span class="c1">// app/post/page.js</span> +<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">Page</span><span class="p">()</span> <span class="p">{</span> + <span class="kd">const</span> <span class="nx">posts</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">fetch</span><span class="p">(</span><span class="dl">"</span><span class="s2">http://localhost:8080/api/post</span><span class="dl">"</span><span class="p">,</span> <span class="p">{</span> <span class="na">next</span> <span class="p">:</span> <span class="p">{</span><span class="na">revalidate</span><span class="p">:</span><span class="mi">60</span><span class="p">}</span> <span class="p">});</span> + <span class="k">return </span><span class="p">(</span> + <span class="o">&lt;</span><span class="nx">div</span><span class="o">&gt;</span> + <span class="p">{</span><span class="nx">posts</span><span class="p">.</span><span class="nf">map</span><span class="p">(</span><span class="nx">p</span> <span class="o">=&gt;</span> <span class="o">&lt;</span><span class="nx">div</span><span class="o">&gt;</span><span class="p">{</span><span class="nx">p</span><span class="p">.</span><span class="nx">content</span><span class="p">}</span><span class="o">&lt;</span><span class="sr">/div&gt;</span><span class="se">)</span><span class="err">} +</span> <span class="o">&lt;</span><span class="sr">/div</span><span class="err">&gt; +</span> <span class="p">)</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<ul> + <li>next.js 콘솔 결과물</li> +</ul> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +</pre></td><td class="rouge-code"><pre> GET /post 200 in 281ms + │ + ├──── GET http://localhost:8080/api/post 200 in 7ms (cache: HIT) +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>이것이 next.js의 의도된 동작인지, 이슈인지는 좀 더 살펴봐야할 것 같다.</p> + + Sun, 13 Aug 2023 22:00:00 +0900 + https://seongil-shin.github.io/posts/next-caching/ + https://seongil-shin.github.io/posts/next-caching/ + + next.js + + cache + + + study + + next.js + + + + + next.js props drilling + <h1 id="data-sharing-solving-props-drilling">Data sharing (Solving props drilling)</h1> + +<p>next.js 13.4 이상 App router 기준으로 작성하였습니다.</p> + +<h2 id="props-drilling-예시">props drilling 예시</h2> + +<p>next.js에서 주로 발생하는 props drilling 형태는 다음과 같습니다.</p> + +<ul> + <li>먼저 필요한 데이터를 서버컴포넌트에서 fetch 합니다. API 노출 방지, 최적화 등 다양한 이유로 서버 컴포넌트(서버 사이드)에서 API를 호출합니다.</li> +</ul> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +</pre></td><td class="rouge-code"><pre><span class="c1">// Server Component A</span> +<span class="k">import</span> <span class="nx">ServerComponentB</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./ServerComponentB</span><span class="dl">"</span> + +<span class="k">export</span> <span class="k">default</span> <span class="k">async</span> <span class="kd">function</span> <span class="nf">ServerComponentA</span><span class="p">()</span> <span class="p">{</span> + <span class="kd">const</span> <span class="nx">initialCount</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">fetch</span><span class="p">(...);</span> + + <span class="k">return</span> <span class="o">&lt;&gt;</span> + <span class="o">&lt;</span><span class="nx">h3</span><span class="o">&gt;</span><span class="nx">I</span> <span class="nx">got</span> <span class="p">{</span><span class="nx">initialCount</span><span class="p">}</span><span class="o">&lt;</span><span class="sr">/h3</span><span class="err">&gt; +</span> <span class="o">&lt;</span><span class="nx">ServerComponentB</span> <span class="nx">initialCount</span><span class="o">=</span><span class="p">{</span><span class="nx">initialCount</span><span class="p">}</span> <span class="sr">/</span><span class="err">&gt; +</span> <span class="o">&lt;</span><span class="sr">/</span><span class="err">&gt; +</span><span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<ul> + <li>데이터를 fetch한 서버컴포넌트에서 하위 서버컴포넌트로 데이터를 넘겨줍니다. <code class="language-plaintext highlighter-rouge">ServerComponentB</code>에서는 initialCount가 필요없지만, 하위 컴포넌트에서 필요하기에 받고, 넘겨줍니다.</li> +</ul> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +</pre></td><td class="rouge-code"><pre><span class="c1">// Server Component B</span> +<span class="k">import</span> <span class="nx">ServerComponentC</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./ServerComponentC</span><span class="dl">"</span> + +<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">ServerComponentB</span><span class="p">({</span> <span class="nx">initialCount</span> <span class="p">})</span> <span class="p">{</span> + <span class="k">return</span> <span class="o">&lt;</span><span class="nx">ServerComponentC</span> <span class="nx">initialCount</span><span class="o">=</span><span class="p">{</span><span class="nx">initialCount</span><span class="p">}</span><span class="sr">/</span><span class="err">&gt; +</span><span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<ul> + <li><code class="language-plaintext highlighter-rouge">ServerComponentC</code>에서는 <code class="language-plaintext highlighter-rouge">initialCount</code>가 필요합니다. 필요한 작업을 수행 후 하위 컴포넌트를 불러옵니다. 그리고 그 하위 컴포넌트에도 <code class="language-plaintext highlighter-rouge">initialCount</code>가 필요하기에 props로 받은 데이터를 넘겨줍니다.</li> +</ul> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +</pre></td><td class="rouge-code"><pre><span class="c1">// Server Component C</span> +<span class="k">import</span> <span class="nx">ClientComponent</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./ClientComponent</span><span class="dl">"</span> + +<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">ServerComponentC</span><span class="p">({</span> <span class="nx">initialCount</span> <span class="p">})</span> <span class="p">{</span> + <span class="k">return </span><span class="p">(</span> + <span class="o">&lt;</span><span class="nx">div</span><span class="o">&gt;</span> + <span class="o">&lt;</span><span class="nx">h3</span><span class="o">&gt;</span><span class="nx">initial</span> <span class="nx">count</span> <span class="nx">is</span> <span class="p">:</span> <span class="p">{</span><span class="nx">initialCount</span><span class="p">}</span><span class="o">&lt;</span><span class="sr">/h3</span><span class="err">&gt; +</span> <span class="o">&lt;</span><span class="nx">ClientComponent</span> <span class="nx">initialCount</span><span class="o">=</span><span class="p">{</span><span class="nx">initialCount</span><span class="p">}</span><span class="sr">/</span><span class="err">&gt; +</span> <span class="o">&lt;</span><span class="sr">/div</span><span class="err">&gt; +</span> <span class="p">)</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<ul> + <li>최종적으로 최하위 컴포넌트에 도달하였습니다.</li> +</ul> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +</pre></td><td class="rouge-code"><pre><span class="c1">// Client Component</span> +<span class="dl">'</span><span class="s1">use client</span><span class="dl">'</span> + +<span class="k">import</span> <span class="p">{</span><span class="nx">useState</span><span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">react</span><span class="dl">"</span> + +<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">ClientComponent</span><span class="p">({</span><span class="nx">initialCount</span><span class="p">})</span> <span class="p">{</span> + <span class="kd">const</span> <span class="p">[</span><span class="nx">count</span><span class="p">,</span> <span class="nx">setCount</span><span class="p">]</span> <span class="o">=</span> <span class="nf">useState</span><span class="p">(</span><span class="nx">initialCount</span><span class="p">);</span> + + <span class="k">return </span><span class="p">(</span> + <span class="o">&lt;&gt;</span> + <span class="p">{</span><span class="nx">count</span><span class="p">}</span> + <span class="o">&lt;</span><span class="nx">button</span> <span class="nx">onClick</span><span class="o">=</span><span class="p">{()</span> <span class="o">=&gt;</span> <span class="nf">setCount</span><span class="p">(</span><span class="nx">prev</span> <span class="o">=&gt;</span> <span class="nx">prev</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)}</span><span class="o">&gt;+</span><span class="mi">1</span><span class="o">&lt;</span><span class="sr">/button</span><span class="err">&gt; +</span> <span class="o">&lt;</span><span class="nx">button</span> <span class="nx">onClick</span><span class="o">=</span><span class="p">{()</span> <span class="o">=&gt;</span> <span class="nf">setCount</span><span class="p">(</span><span class="nx">prev</span> <span class="o">=&gt;</span> <span class="nx">prev</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)}</span><span class="o">&gt;-</span><span class="mi">1</span><span class="o">&lt;</span><span class="sr">/button</span><span class="err">&gt; +</span> <span class="o">&lt;</span><span class="sr">/</span><span class="err">&gt; +</span> <span class="p">)</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>이처럼 상위 컴포넌트에 있는 데이터를 하위 컴포넌트에서도 가져야하고 이것을 props로 넘겨줄때, props drilling이 발생합니다. 중간에 <code class="language-plaintext highlighter-rouge">ServerComponentB</code>에서는 <code class="language-plaintext highlighter-rouge">initialCount</code>가 필요없었지만 하위 컴포넌트에서 필요하기에 받고 넘겨줘야했습니다.</p> + +<p>만약 일반적인 React 앱처럼 클라이언트 컴포넌트로만 구성되어있다면 글로벌 상태관리 라이브러리(redux, recoil..)나, Context API를 사용할 수 있습니다. 하지만 서버컴포넌트에서는 상태를 사용할 수 없기에 위와 같은 방법은 사용하기 어렵습니다.</p> + +<p>여기서는 서버컴포넌트가 있는 구조에서 props drilling 문제 없이 데이터를 공유할 수 있는 방법을 작성하였습니다.</p> + +<p><br /></p> + +<h2 id="server-component---server-component">Server Component -&gt; Server Component</h2> + +<p>next.js에서 기본으로 제공해주는 기능인 <code class="language-plaintext highlighter-rouge">Automatic fetch() Requst Deduping</code>을 사용할 수 있습니다. +<a href="https://nextjs.org/docs/app/building-your-application/data-fetching#automatic-fetch-request-deduping">next.js 공식문서</a>에서의 설명에 따르면, 서버에서 <code class="language-plaintext highlighter-rouge">fetch</code>로 <code class="language-plaintext highlighter-rouge">GET</code>한 데이터는 rendering pass 동안 캐시된다고 합니다. 그리고 이 캐시는 server request 생명주기동안 유지되며 렌더링 과정이 끝날때까지 지속된다고 합니다.</p> + +<p>따라서 서버컴포넌트끼리는 별도의 data sharing 기법이 필요하지 않고, 매번 필요한 데이터를 불러오면 됩니다.</p> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +</pre></td><td class="rouge-code"><pre><span class="c1">// Server Component A</span> +<span class="k">import</span> <span class="nx">ServerComponentB</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./ServerComponentB</span><span class="dl">"</span> + +<span class="k">export</span> <span class="k">default</span> <span class="k">async</span> <span class="kd">function</span> <span class="nf">ServerComponentA</span><span class="p">()</span> <span class="p">{</span> + <span class="kd">const</span> <span class="nx">initialCount</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">fetch</span><span class="p">(...);</span> + + <span class="k">return</span> <span class="o">&lt;&gt;</span> + <span class="o">&lt;</span><span class="nx">h3</span><span class="o">&gt;</span><span class="nx">I</span> <span class="nx">got</span> <span class="p">{</span><span class="nx">initialCount</span><span class="p">}</span><span class="o">&lt;</span><span class="sr">/h3</span><span class="err">&gt; +</span> <span class="o">&lt;</span><span class="nx">ServerComponentB</span><span class="o">/&gt;</span> + <span class="o">&lt;</span><span class="sr">/</span><span class="err">&gt; +</span><span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +</pre></td><td class="rouge-code"><pre><span class="c1">// Server Component B</span> +<span class="k">import</span> <span class="nx">ServerComponentC</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./ServerComponentC</span><span class="dl">"</span> + +<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">ServerComponentB</span><span class="p">()</span> <span class="p">{</span> + <span class="k">return</span> <span class="o">&lt;</span><span class="nx">ServerComponentC</span><span class="o">/&gt;</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +</pre></td><td class="rouge-code"><pre><span class="c1">// Server Component C</span> +<span class="k">import</span> <span class="nx">ClientComponent</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./ClientComponent</span><span class="dl">"</span> + +<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">ServerComponentC</span><span class="p">()</span> <span class="p">{</span> + <span class="kd">const</span> <span class="nx">initialCount</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">fetch</span><span class="p">(...);</span> + <span class="k">return </span><span class="p">(</span> + <span class="o">&lt;</span><span class="nx">div</span><span class="o">&gt;</span> + <span class="o">&lt;</span><span class="nx">h3</span><span class="o">&gt;</span><span class="nx">initial</span> <span class="nx">count</span> <span class="nx">is</span> <span class="p">:</span> <span class="p">{</span><span class="nx">initialCount</span><span class="p">}</span><span class="o">&lt;</span><span class="sr">/h3</span><span class="err">&gt; +</span> <span class="o">&lt;</span><span class="nx">ClientComponent</span> <span class="nx">initialCount</span><span class="o">=</span><span class="p">{</span><span class="nx">initialCount</span><span class="p">}</span><span class="sr">/</span><span class="err">&gt; +</span> <span class="o">&lt;</span><span class="sr">/div</span><span class="err">&gt; +</span> <span class="p">)</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>이제 <code class="language-plaintext highlighter-rouge">ServerComponentB</code>에서는 <code class="language-plaintext highlighter-rouge">initialCount</code>를 받을 필요도, 넘겨줄 필요도 없습니다. <code class="language-plaintext highlighter-rouge">fetch()</code> 동안 결과가 캐시되어있기 때문에 두번이상 API 요청을 보내지 않습니다.</p> + +<h3 id="한계점">한계점</h3> + +<p>저는 개발을 하면서 서버컴포넌트에서 API 요청을 보낼때 주로 searchParams에 들어온 값을 기준으로 API를 호출했습니다.</p> + +<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +</pre></td><td class="rouge-code"><pre><span class="c1">// ex : 클라이언트 요청 주소 -&gt; /notice?categoryId=4&amp;searchValue=test</span> +<span class="c1">// categoryId=4, searchValue=test 로 데이터를 받아옴</span> +<span class="k">export</span> <span class="k">default</span> <span class="k">async</span> <span class="kd">function</span> <span class="nf">NoticePage</span><span class="p">({</span> <span class="na">searchParams</span> <span class="p">:</span> <span class="p">{</span><span class="nx">categoryId</span><span class="p">,</span> <span class="nx">searchValue</span> <span class="p">}</span> <span class="p">})</span> <span class="p">{</span> + <span class="kd">const</span> <span class="nx">notices</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">getNotices</span><span class="p">({</span> <span class="nx">categoryId</span><span class="p">,</span> <span class="nx">searchValue</span> <span class="p">})</span> + <span class="p">...</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>하지만 next.js app router에서 <code class="language-plaintext highlighter-rouge">page.js</code>가 아닌 서버컴포넌트에서 <code class="language-plaintext highlighter-rouge">searchParams</code>를 얻어올 수 있는 공식적으로 제공하는 방법은 없습니다. 따라서 위에서 설명한, 데이터가 필요한 서버컴포넌트에서 api를 요청하는 건 어렵습니다. API 요청에 필요한 <code class="language-plaintext highlighter-rouge">searchParams</code>를 얻을 수 없기 때문입니다.</p> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +</pre></td><td class="rouge-code"><pre><span class="c1">// page.js가 아닌 어딘가의 서버컴포넌트</span> +<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">NoticeList</span><span class="p">()</span> <span class="p">{</span> + <span class="c1">// error! categoryId, searvhValue를 알아낼 수 없음</span> + <span class="kd">const</span> <span class="nx">notices</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">getNotices</span><span class="p">({</span> <span class="nx">categoryId</span><span class="p">,</span> <span class="nx">searchValue</span> <span class="p">})</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>따라서 <code class="language-plaintext highlighter-rouge">Server Component -&gt; Server Component data sharing</code>을 좀 더 잘 활용하려면, 하나의 Request가 살아있는 동안 여러 서버컴포넌트에서 그 Request의 searchParams에 접근가능해야합니다.</p> + +<p>공식적인 방법은 아니나 몇가지 생각해본/찾아본 방법은 다음과 같습니다.</p> + +<ol> + <li> + <p>웹서버 활용</p> + + <p>next.js에서는 서버컴포넌트에 <a href="https://nextjs.org/docs/app/api-reference/functions/headers">headers()</a>라는 함수를 제공합니다. 요청이 온 Request의 header을 제공해주는 함수입니다. 이 함수는 꼭 <code class="language-plaintext highlighter-rouge">page.js</code>만이 아닌 곳에서도 사용가능하기에, header에 searchParams가 들어있다면 <code class="language-plaintext highlighter-rouge">page.js</code>가 아닌 서버컴포넌트에서도 searchParams에 접근 가능합니다.</p> + + <div class="language-nginx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +</pre></td><td class="rouge-code"><pre><span class="c1"># nginx 예시</span> +<span class="k">server</span> <span class="p">{</span> + <span class="kn">location</span> <span class="n">/</span> <span class="p">{</span> + <span class="kn">proxy_set_header</span> <span class="s">X-Search-Params</span> <span class="nv">$args</span><span class="p">;</span> + <span class="kn">proxy_pass</span> <span class="s">http://localhost:3000</span><span class="p">;</span> + <span class="p">}</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div> </div> + + <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +</pre></td><td class="rouge-code"><pre><span class="k">import</span> <span class="p">{</span> <span class="nx">headers</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">next/headers</span><span class="dl">'</span> + +<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">NoticeList</span><span class="p">()</span> <span class="p">{</span> + <span class="kd">const</span> <span class="nx">header</span> <span class="o">=</span> <span class="nf">headers</span><span class="p">()</span> + <span class="kd">const</span> <span class="p">{</span> <span class="nx">categoryId</span><span class="p">,</span> <span class="nx">searchValue</span> <span class="p">}</span> <span class="o">=</span> <span class="nf">parseSearchParams</span><span class="p">(</span><span class="nx">header</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="dl">"</span><span class="s2">X-Search-Params</span><span class="dl">"</span><span class="p">))</span> + + <span class="kd">const</span> <span class="nx">notices</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">getNotices</span><span class="p">({</span> <span class="nx">categoryId</span><span class="p">,</span> <span class="nx">searchValue</span> <span class="p">})</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div> </div> + </li> + <li> + <p>searchParams 정도는 props로 내려보내기</p> + + <p><code class="language-plaintext highlighter-rouge">page.js</code>에서 searchParams가 필요한 서버컴포넌트까지 내려줘야하지만, <code class="language-plaintext highlighter-rouge">searchParams</code> 객체 하나만 내려주면 되기에 props drilling을 하나의 객체만 내려보내는 수준으로 유지할 수 있습니다.</p> + + <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +</pre></td><td class="rouge-code"><pre><span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">NoticeList</span><span class="p">({</span><span class="na">searchParams</span> <span class="p">:</span> <span class="p">{</span><span class="nx">categoryId</span><span class="p">,</span> <span class="nx">searchValue</span> <span class="p">}})</span> <span class="p">{</span> + <span class="kd">const</span> <span class="nx">notices</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">getNotices</span><span class="p">({</span> <span class="nx">categoryId</span><span class="p">,</span> <span class="nx">searchValue</span> <span class="p">})</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div> </div> + + <p>관련해서 next.js 깃허브 discussions에서 논의된 <a href="https://github.com/vercel/next.js/discussions/44005">얘기</a>가 있는데, searchParams에 대한 validation/parsing/error handling 없이 하위 컴포넌트로 searchParams를 넘겨줄 가능성은 적다는 댓글이 있었습니다. 가능성이 적은지는 모르겠으나, 확실히 파싱, 유효성 검증이 필요한 searchParams의 경우 <code class="language-plaintext highlighter-rouge">page.js</code>에서 먼저 받아 처리를 한 후, 하위 컴포넌트로 props로 넘겨주는게 더 좋은 방법으로 보입니다.</p> + </li> + <li> + <p>서버단에서 data sharing 툴 사용</p> + + <p>꼭 searchParams에 국한되지 않더라도, 서버컴포넌트들 사이에서 데이터 or context를 공유하면 좋은 경우가 많습니다. (i18n, theme 등). <a href="https://github.com/vercel/next.js/discussions/42301">next.js discussions</a></p> + + <p>공식적으로 제공해주는 방법은 없지만, 서드파티 툴을 만드려는 시도는 존재합니다(<a href="https://www.npmjs.com/package/server-only-context">server-only-context</a>, <a href="https://nextjs.org/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating#react-cache-function">react cache function</a> 활용). 공식문서에서는 db와 같이 네이티브 JS 패턴을 사용하는 방법을 추천해주고 있습니다. <a href="https://nextjs.org/docs/getting-started/react-essentials#sharing-data-between-server-components">링크</a></p> + </li> +</ol> + +<p><br /></p> + +<h2 id="client-component---client-component">Client Component -&gt; Client Component</h2> + +<p>자주 있는 상황으로, 어떤 클라이언트 컴포넌트와 디렉터리 상 먼 곳에 있는 클라이언트 컴포넌트가 데이터 또는 상태를 공유해야하는 경우가 있을 수 있습니다. 이럴때는 일반적으로 리액트 앱을 개발할때 사용하는 전역 상태 관리 방법을 채택할 수 있습니다. next.js 공식문서에서도 Context API 및 서드파티 라이브러리에 대한 설명에 있습니다. <a href="https://nextjs.org/docs/getting-started/react-essentials#using-context-in-client-components">링크</a></p> + +<p>예시로는 Context API를 사용하였습니다.</p> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +</pre></td><td class="rouge-code"><pre><span class="c1">// layout.tsx</span> +<span class="k">import</span> <span class="p">{</span> <span class="nx">AppProvider</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../lib/AppProvider</span><span class="dl">'</span><span class="p">;</span> + +<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">RootLayout</span><span class="p">({</span> <span class="nx">children</span> <span class="p">})</span> <span class="p">{</span> + <span class="k">return </span><span class="p">(</span> + <span class="o">&lt;</span><span class="nx">html</span> <span class="nx">lang</span><span class="o">=</span><span class="dl">"</span><span class="s2">en</span><span class="dl">"</span><span class="o">&gt;</span> + <span class="o">&lt;</span><span class="nx">body</span> <span class="nx">className</span><span class="o">=</span><span class="p">{</span><span class="nx">inter</span><span class="p">.</span><span class="nx">className</span><span class="p">}</span><span class="o">&gt;</span> + <span class="o">&lt;</span><span class="nx">AppProvider</span><span class="o">&gt;</span><span class="p">{</span><span class="nx">children</span><span class="p">}</span><span class="o">&lt;</span><span class="sr">/AppProvider</span><span class="err">&gt; +</span> <span class="o">&lt;</span><span class="sr">/body</span><span class="err">&gt; +</span> <span class="o">&lt;</span><span class="sr">/html</span><span class="err">&gt; +</span> <span class="p">);</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +</pre></td><td class="rouge-code"><pre><span class="c1">// lib/AppProvider.tsx</span> +<span class="dl">'</span><span class="s1">use client</span><span class="dl">'</span><span class="p">;</span> +<span class="k">import</span> <span class="nx">React</span><span class="p">,</span> <span class="p">{</span> <span class="nx">createContext</span><span class="p">,</span> <span class="nx">useState</span><span class="p">,</span> <span class="nx">useContext</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">react</span><span class="dl">'</span><span class="p">;</span> + +<span class="kd">const</span> <span class="nx">AppContext</span> <span class="o">=</span> <span class="nf">createContext</span><span class="p">();</span> + +<span class="k">export</span> <span class="kd">const</span> <span class="nx">AppProvider</span> <span class="o">=</span> <span class="p">({</span> <span class="nx">children</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">{</span> + <span class="kd">const</span> <span class="p">[</span><span class="nx">state</span><span class="p">,</span> <span class="nx">setState</span><span class="p">]</span> <span class="o">=</span> <span class="nf">useState</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span> + <span class="kd">const</span> <span class="nx">value</span> <span class="o">=</span> <span class="p">{</span> + <span class="nx">state</span><span class="p">,</span> + <span class="nx">setState</span><span class="p">,</span> + <span class="p">};</span> + <span class="k">return</span> <span class="o">&lt;</span><span class="nx">AppContext</span><span class="p">.</span><span class="nx">Provider</span> <span class="nx">value</span><span class="o">=</span><span class="p">{</span><span class="nx">value</span><span class="p">}</span><span class="o">&gt;</span><span class="p">{</span><span class="nx">children</span><span class="p">}</span><span class="o">&lt;</span><span class="sr">/AppContext.Provider&gt;</span><span class="err">; +</span><span class="p">}</span> +<span class="k">export</span> <span class="kd">const</span> <span class="nx">useAppContext</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="nf">useContext</span><span class="p">(</span><span class="nx">AppContext</span><span class="p">);</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +</pre></td><td class="rouge-code"><pre><span class="c1">// page.tsx</span> +<span class="k">import</span> <span class="nx">Counter</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../components/counter</span><span class="dl">'</span><span class="p">;</span> +<span class="k">import</span> <span class="nx">ServerComp</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../components/server</span><span class="dl">'</span><span class="p">;</span> + +<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">Home</span><span class="p">()</span> <span class="p">{</span> + <span class="k">return </span><span class="p">(</span> + <span class="o">&lt;</span><span class="nx">main</span> <span class="nx">className</span><span class="o">=</span><span class="p">{</span><span class="nx">styles</span><span class="p">.</span><span class="nx">main</span><span class="p">}</span><span class="o">&gt;</span> + <span class="o">&lt;</span><span class="nx">ServerComp</span> <span class="o">/&gt;</span> + <span class="o">&lt;</span><span class="nx">Counter</span> <span class="o">/&gt;</span> + <span class="o">&lt;</span><span class="sr">/main</span><span class="err">&gt; +</span> <span class="p">);</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +</pre></td><td class="rouge-code"><pre><span class="c1">// components/counter.tsx</span> +<span class="dl">'</span><span class="s1">use client</span><span class="dl">'</span><span class="p">;</span> + +<span class="k">import</span> <span class="p">{</span> <span class="nx">useAppContext</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../lib/AppProvider</span><span class="dl">'</span><span class="p">;</span> + +<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">Counter</span><span class="p">()</span> <span class="p">{</span> + <span class="kd">const</span> <span class="p">{</span> <span class="nx">state</span><span class="p">,</span> <span class="nx">setState</span> <span class="p">}</span> <span class="o">=</span> <span class="nf">useAppContext</span><span class="p">();</span> + + <span class="kd">const</span> <span class="nx">increaseHandler</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> + <span class="nf">setState</span><span class="p">(</span><span class="nx">state</span> <span class="o">+</span> <span class="mi">1</span><span class="p">);</span> + <span class="p">};</span> + + <span class="kd">const</span> <span class="nx">decreaseHandler</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> + <span class="nf">setState</span><span class="p">(</span><span class="nx">state</span> <span class="o">-</span> <span class="mi">1</span><span class="p">);</span> + <span class="p">};</span> + <span class="c1">// Use the state and setState as needed</span> + <span class="k">return </span><span class="p">(</span> + <span class="o">&lt;&gt;</span> + <span class="o">&lt;</span><span class="nx">div</span><span class="o">&gt;</span><span class="p">{</span><span class="nx">state</span><span class="p">}</span><span class="o">&lt;</span><span class="sr">/div</span><span class="err">&gt; +</span> <span class="o">&lt;</span><span class="nx">a</span> <span class="nx">href</span><span class="o">=</span><span class="dl">"</span><span class="s2">#</span><span class="dl">"</span> <span class="nx">onClick</span><span class="o">=</span><span class="p">{</span><span class="nx">increaseHandler</span><span class="p">}</span><span class="o">&gt;</span> + <span class="nx">increase</span> + <span class="o">&lt;</span><span class="sr">/a&gt;{' '</span><span class="err">} +</span> <span class="o">&amp;</span><span class="nx">nbsp</span><span class="p">;</span> + <span class="o">&lt;</span><span class="nx">a</span> <span class="nx">href</span><span class="o">=</span><span class="dl">"</span><span class="s2">#</span><span class="dl">"</span> <span class="nx">onClick</span><span class="o">=</span><span class="p">{</span><span class="nx">decreaseHandler</span><span class="p">}</span><span class="o">&gt;</span> + <span class="nx">decrease</span> + <span class="o">&lt;</span><span class="sr">/a</span><span class="err">&gt; +</span> <span class="o">&lt;</span><span class="sr">/</span><span class="err">&gt; +</span> <span class="p">);</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<ul> + <li><strong>오해</strong> : Context Provider는 클라이언트 컴포넌트이고, 이것을 가장 상위에 두면 그 아래는 전부 클라이언트 번들에 포함될거라고 오해하는 경우가 있습니다. 그 이유는 next.js 공식문서에 아래와 같은 문구가 있기 때문인데요.</li> +</ul> + +<blockquote> + <p>Once <code class="language-plaintext highlighter-rouge">"use client"</code> is defined in a file, <strong>all other modules imported into it,</strong> <strong>including child components</strong>, are considered part of the client bundle</p> + + <p>https://nextjs.org/docs/getting-started/react-essentials#the-use-client-directive</p> +</blockquote> + +<p>클라이언트 컴포넌트의 자식 컴포넌트들은 클라이언트 번들로 간주된다는 내용입니다. 따라서 Context API Provider를 상위에 두면 그 하위 서버컴포넌트들도 모두 클라이언트 번들에 포함되어 서버컴포넌트를 사용하는 이유가 없어질것만 같습니다.</p> + +<p>하지만 실험해본 결과, 클라이언트 번들에 포함되지 않았습니다. 이유는 ServerComponent가 ClientComponent(Context Provider)에 직접 import 되지 않고, props로 넘어갔기 때문입니다</p> + +<p>next.js 공식 문서에 따르면 서버컴포넌트를 클라이언트 컴포넌트 밑에 두려면, import 하지 말고 prop(children)으로 넘겨줘야한다고 되어있습니다. <a href="https://nextjs.org/docs/getting-started/react-essentials#nesting-server-components-inside-client-components">링크</a></p> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +</pre></td><td class="rouge-code"><pre><span class="c1">// Unsupported Pattern: Importing Server Components into Client Components</span> + +<span class="dl">'</span><span class="s1">use client</span><span class="dl">'</span> + +<span class="c1">// This pattern will **not** work!</span> +<span class="c1">// You cannot import a Server Component into a Client Component.</span> +<span class="k">import</span> <span class="nx">ExampleServerComponent</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./example-server-component</span><span class="dl">'</span> + +<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">ExampleClientComponent</span><span class="p">({</span> + <span class="nx">children</span><span class="p">,</span> +<span class="p">}:</span> <span class="p">{</span> + <span class="nl">children</span><span class="p">:</span> <span class="nx">React</span><span class="p">.</span><span class="nx">ReactNode</span> +<span class="p">})</span> <span class="p">{</span> + <span class="kd">const</span> <span class="p">[</span><span class="nx">count</span><span class="p">,</span> <span class="nx">setCount</span><span class="p">]</span> <span class="o">=</span> <span class="nf">useState</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> + + <span class="k">return </span><span class="p">(</span> + <span class="o">&lt;&gt;</span> + <span class="o">&lt;</span><span class="nx">button</span> <span class="nx">onClick</span><span class="o">=</span><span class="p">{()</span> <span class="o">=&gt;</span> <span class="nf">setCount</span><span class="p">(</span><span class="nx">count</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)}</span><span class="o">&gt;</span><span class="p">{</span><span class="nx">count</span><span class="p">}</span><span class="o">&lt;</span><span class="sr">/button</span><span class="err">&gt; +</span> <span class="o">&lt;</span><span class="nx">ExampleServerComponent</span> <span class="o">/&gt;</span> + <span class="o">&lt;</span><span class="sr">/</span><span class="err">&gt; +</span> <span class="p">)</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +</pre></td><td class="rouge-code"><pre><span class="c1">// Recommended Pattern: Passing Server Components to Client Components as Props</span> + +<span class="dl">'</span><span class="s1">use client</span><span class="dl">'</span> + +<span class="k">import</span> <span class="p">{</span> <span class="nx">useState</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">react</span><span class="dl">'</span> + +<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">ExampleClientComponent</span><span class="p">({</span> + <span class="nx">children</span><span class="p">,</span> +<span class="p">}:</span> <span class="p">{</span> + <span class="nl">children</span><span class="p">:</span> <span class="nx">React</span><span class="p">.</span><span class="nx">ReactNode</span> +<span class="p">})</span> <span class="p">{</span> + <span class="kd">const</span> <span class="p">[</span><span class="nx">count</span><span class="p">,</span> <span class="nx">setCount</span><span class="p">]</span> <span class="o">=</span> <span class="nf">useState</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> + + <span class="k">return </span><span class="p">(</span> + <span class="o">&lt;&gt;</span> + <span class="o">&lt;</span><span class="nx">button</span> <span class="nx">onClick</span><span class="o">=</span><span class="p">{()</span> <span class="o">=&gt;</span> <span class="nf">setCount</span><span class="p">(</span><span class="nx">count</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)}</span><span class="o">&gt;</span><span class="p">{</span><span class="nx">count</span><span class="p">}</span><span class="o">&lt;</span><span class="sr">/button</span><span class="err">&gt; +</span> + <span class="p">{</span><span class="nx">children</span><span class="p">}</span> + <span class="o">&lt;</span><span class="sr">/</span><span class="err">&gt; +</span> <span class="p">)</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>공식 문서에 설명되어 있는대로, import 대신 props(children)으로 넘겨주면 클라이언트 컴포넌트는 자식 컴포넌트를 위한 slot을 남기고, 서버컴포넌트 렌더가 종료되면 그 slot에 서버 컴포넌트가 렌더링 됩니다. 이 방식대로 한다면 서버컴포넌트는 클라이언트 번들에 포함되지 않고, 서버컴포넌트로써 동작합니다.</p> + +<p><br /></p> + +<h2 id="server-component---client-component">Server Component -&gt; Client Component</h2> + +<p>“Server Component -&gt; Server Component”에서 했던 것처럼, 한번 부른 fetch의 결과는 같은 요청이 살아있는 동안 캐시된다는 것을 활용할 수 있습니다. 데이터가 필요한 클라이언트 컴포넌트를 서버컴포넌트로 한번 감싸서 props drilling을 완화할 수 있습니다. <a href="https://www.youtube.com/watch?v=rbTzTXHkXA8&amp;ab_channel=BogdanAdrian">관련 링크(유튜브 영상, 소리주의)</a></p> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +</pre></td><td class="rouge-code"><pre><span class="c1">// Server Component A</span> +<span class="k">import</span> <span class="nx">ServerComponentB</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./ServerComponentB</span><span class="dl">"</span> + +<span class="k">export</span> <span class="k">default</span> <span class="k">async</span> <span class="kd">function</span> <span class="nf">ServerComponentA</span><span class="p">()</span> <span class="p">{</span> + <span class="kd">const</span> <span class="nx">initialCount</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">fetch</span><span class="p">(...);</span> + + <span class="k">return</span> <span class="o">&lt;&gt;</span> + <span class="o">&lt;</span><span class="nx">h3</span><span class="o">&gt;</span><span class="nx">I</span> <span class="nx">got</span> <span class="p">{</span><span class="nx">initialCount</span><span class="p">}</span><span class="o">&lt;</span><span class="sr">/h3</span><span class="err">&gt; +</span> <span class="o">&lt;</span><span class="nx">ServerComponentB</span> <span class="o">/&gt;</span> + <span class="o">&lt;</span><span class="sr">/</span><span class="err">&gt; +</span><span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +</pre></td><td class="rouge-code"><pre><span class="c1">// Server Component B</span> +<span class="k">import</span> <span class="nx">ServerComponentC</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./ServerComponentC</span><span class="dl">"</span> + +<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">ServerComponentB</span><span class="p">()</span> <span class="p">{</span> + <span class="k">return</span> <span class="o">&lt;</span><span class="nx">ServerComponentC</span><span class="o">/&gt;</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +</pre></td><td class="rouge-code"><pre><span class="c1">// Server Component C</span> +<span class="k">import</span> <span class="nx">ClientComponent</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./ClientComponent</span><span class="dl">"</span> + +<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">ServerComponentC</span><span class="p">()</span> <span class="p">{</span> + <span class="kd">const</span> <span class="nx">initialCount</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">fetch</span><span class="p">(...);</span> + <span class="k">return</span> <span class="o">&lt;</span><span class="nx">ClientComponent</span> <span class="nx">initialCount</span><span class="o">=</span><span class="p">{</span><span class="nx">initialCount</span><span class="p">}</span><span class="sr">/</span><span class="err">&gt; +</span><span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +</pre></td><td class="rouge-code"><pre><span class="c1">// Client Component</span> +<span class="dl">'</span><span class="s1">use client</span><span class="dl">'</span> + +<span class="k">import</span> <span class="p">{</span><span class="nx">useState</span><span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">react</span><span class="dl">"</span> + +<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">ClientComponent</span><span class="p">({</span><span class="nx">initialCount</span><span class="p">})</span> <span class="p">{</span> + <span class="kd">const</span> <span class="p">[</span><span class="nx">count</span><span class="p">,</span> <span class="nx">setCount</span><span class="p">]</span> <span class="o">=</span> <span class="nf">useState</span><span class="p">(</span><span class="nx">initialCount</span><span class="p">);</span> + + <span class="k">return </span><span class="p">(</span> + <span class="o">&lt;&gt;</span> + <span class="p">{</span><span class="nx">count</span><span class="p">}</span> + <span class="o">&lt;</span><span class="nx">button</span> <span class="nx">onClick</span><span class="o">=</span><span class="p">{()</span> <span class="o">=&gt;</span> <span class="nf">setCount</span><span class="p">(</span><span class="nx">prev</span> <span class="o">=&gt;</span> <span class="nx">prev</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)}</span><span class="o">&gt;+</span><span class="mi">1</span><span class="o">&lt;</span><span class="sr">/button</span><span class="err">&gt; +</span> <span class="o">&lt;</span><span class="nx">button</span> <span class="nx">onClick</span><span class="o">=</span><span class="p">{()</span> <span class="o">=&gt;</span> <span class="nf">setCount</span><span class="p">(</span><span class="nx">prev</span> <span class="o">=&gt;</span> <span class="nx">prev</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)}</span><span class="o">&gt;-</span><span class="mi">1</span><span class="o">&lt;</span><span class="sr">/button</span><span class="err">&gt; +</span> <span class="o">&lt;</span><span class="sr">/</span><span class="err">&gt; +</span> <span class="p">)</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p><br /></p> + +<h2 id="client-component---server-component">Client Component -&gt; Server Component</h2> + +<p>props로 넘겨주는 방법이나, 다음과 같이 <code class="language-plaintext highlighter-rouge">cloneElement</code>를 이용한 방법이 있습니다.</p> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +</pre></td><td class="rouge-code"><pre><span class="k">import</span> <span class="nx">React</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">react</span><span class="dl">"</span><span class="p">;</span> +<span class="k">import</span> <span class="nx">ClientComponent</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./ClientComponent</span><span class="dl">"</span> + +<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">ParentComponent</span><span class="p">({</span> <span class="nx">children</span> <span class="p">})</span> <span class="p">{</span> + <span class="k">return </span><span class="p">(</span> + <span class="o">&lt;</span><span class="nx">ClientComponent</span><span class="o">&gt;</span> + <span class="o">&lt;</span><span class="nx">ServerComponent</span><span class="o">/&gt;</span> + <span class="o">&lt;</span><span class="sr">/ClientComponent</span><span class="err">&gt; +</span> <span class="p">)</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +</pre></td><td class="rouge-code"><pre><span class="dl">'</span><span class="s1">use client</span><span class="dl">'</span> + +<span class="k">import</span> <span class="nx">React</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">react</span><span class="dl">"</span><span class="p">;</span> + +<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">ClientComponent</span><span class="p">({</span> <span class="nx">children</span> <span class="p">})</span> <span class="p">{</span> + <span class="k">return </span><span class="p">(</span> + <span class="o">&lt;</span><span class="nx">div</span><span class="o">&gt;</span> + <span class="p">{</span><span class="nx">React</span><span class="p">.</span><span class="nf">cloneElement</span><span class="p">(</span><span class="nx">children</span><span class="p">,</span> <span class="p">{</span><span class="na">name</span><span class="p">:</span><span class="dl">"</span><span class="s2">test props</span><span class="dl">"</span><span class="p">})}</span> + <span class="o">&lt;</span><span class="sr">/div</span><span class="err">&gt; +</span> <span class="p">)</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +</pre></td><td class="rouge-code"><pre><span class="k">import</span> <span class="nx">React</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">react</span><span class="dl">"</span><span class="p">;</span> + +<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">ServerComponent</span><span class="p">({</span> <span class="nx">name</span> <span class="p">})</span> <span class="p">{</span> + <span class="k">return </span><span class="p">(</span> + <span class="o">&lt;</span><span class="nx">div</span><span class="o">&gt;</span> + <span class="p">{</span><span class="nx">name</span><span class="p">}</span> + <span class="o">&lt;</span><span class="sr">/div</span><span class="err">&gt; +</span> <span class="p">)</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>하지만 가능한 것과는 별개로 크게 효용성이 있을지는 모르겠습니다. 이 방법은 클라이언트 컴포넌트의 바로 한단계 밑 서버컴포넌트로만 전달이 가능하기에, 클라이언트 컴포넌트에서 서버컴포넌트를 변경하려면 URL 쿼리를 변경하여 새로 페이지를 fetch하는게 나을 수도 있습니다. URL 쿼리를 변경하는 경우 한단계 밑 서버컴포넌트뿐만 아니라 관련있는 모든 서버컴포넌트들도 변경이 가능하기 떄문입니다.</p> + + + Sat, 05 Aug 2023 16:00:00 +0900 + https://seongil-shin.github.io/posts/next-props-drilling/ + https://seongil-shin.github.io/posts/next-props-drilling/ + + next.js + + + study + + next.js + + + + + React 새로고침/뒤로가기 막기 + <p>웹페이지에서 사용자가 무언가를 입력하고 뒤로가기를 눌렀을때 다음과 같은 경고창을 띄우는 걸 본적이 있다.</p> + +<p><img src="/assets/img/2023-07-29-react-뒤로가기-막기/Chrome_-_이_사이트에서_나가시겠습니까.png" alt="HTML DOM beforeunload 이벤트 - 제타위키" /></p> + +<p>이러한 스펙을 개발하기위해서는 다음과 같이 두가지 상황으로 나누어야한다.</p> + +<ul> + <li>새로고침/창닫기/링크이동(외부페이지 이동 or 링크 버튼 클릭)</li> + <li>뒤로가기</li> +</ul> + +<p>그렇다면 리액트에서는 구체적으로 어떻게 이 스펙을 구현할 수 있을지 알아보자</p> + +<p><br /></p> + +<h2 id="새로고침창닫기링크이동">새로고침/창닫기/링크이동</h2> + +<p>새로고침/창닫기/링크이동의 경우는 간단하다. 해당 이벤트들이 발생할때 window 객체에서 발생시키는 <code class="language-plaintext highlighter-rouge">beforeunload</code> 이벤트를 취소시키면 된다. +리액트에서 구현하려면 다음과 같이 <code class="language-plaintext highlighter-rouge">useEffect</code> 안에 <code class="language-plaintext highlighter-rouge">window.onbeforeunload</code> 함수를 등록해주면 된다.</p> + +<div class="language-react highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +</pre></td><td class="rouge-code"><pre><span class="nf">useEffect</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span> + <span class="nb">window</span><span class="p">.</span><span class="nx">onbeforeunload</span> <span class="o">=</span> <span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> + <span class="nx">e</span><span class="p">.</span><span class="nf">preventDefault</span><span class="p">();</span> + <span class="k">return</span> <span class="dl">""</span> + <span class="p">}</span> + + <span class="k">return </span><span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> + <span class="nb">window</span><span class="p">.</span><span class="nx">onbeforeunload</span> <span class="o">=</span> <span class="kc">null</span> + <span class="p">}</span> +<span class="p">},</span> <span class="p">[])</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>하지만, 이 경우에는 페이지에 접속했을 경우 무조건 이벤트가 등록이 된다. 사용자가 입력했을때만 경고창을 띄우고 싶다면 다음과 같이 할 수 있다.</p> + +<div class="language-react highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +</pre></td><td class="rouge-code"><pre><span class="kd">const</span> <span class="p">[</span><span class="nx">hasInputFilled</span><span class="p">,</span> <span class="nx">setHasInputFilled</span><span class="p">]</span> <span class="o">=</span> <span class="nf">useState</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span> + +<span class="nf">useEffect</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span> + <span class="nb">window</span><span class="p">.</span><span class="nx">onbeforeunload</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> + <span class="k">if </span><span class="p">(</span><span class="nx">hasInputFilled</span><span class="p">)</span> <span class="p">{</span> + <span class="k">return</span> <span class="dl">""</span> + <span class="p">}</span> + <span class="p">}</span> + + <span class="k">return </span><span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> + <span class="nb">window</span><span class="p">.</span><span class="nx">onbeforeunload</span> <span class="o">=</span> <span class="kc">null</span> + <span class="p">}</span> +<span class="p">},</span> <span class="p">[</span><span class="nx">hasInputFilled</span><span class="p">])</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p><br /></p> + +<h2 id="뒤로가기-막기">뒤로가기 막기</h2> + +<p>뒤로가기 이벤트는 <code class="language-plaintext highlighter-rouge">popstate</code> 이벤트가 발생한다. 하지만 <code class="language-plaintext highlighter-rouge">popstate</code> 이벤트는 취소가 불가능하다. 따라서 취소를 하지 않고 뒤로가기를 막아야하는데, 방법을 말하기 전에 먼저 <code class="language-plaintext highlighter-rouge">popstate</code> 이벤트에 대해 간략히 알아보자.</p> + +<p>우리가 브라우저에서 여러 웹 페이지를 오가다보면 방문기록이 쌓이게 된다. 이때 하나의 탭에서 여러 웹 페이지를 방문하면 그 기록이 차례로 쌓이고, 뒤로가기 시에는 해당 탭에서 마지막으로 방문했던 곳으로 이동하게 된다. 마치 스택처럼 동작한다. <code class="language-plaintext highlighter-rouge">popstate</code>는 뒤로가기 시 마지막으로 방문했던 페이지로 이동하기 위해 발생된다.</p> + +<p>여기서 뒤로가기를 막기 위한 한가지 아이디어를 찾을 수 있다. 만약 마지막으로 방문했던 페이지가 현재 페이지면, 뒤로가기를 하더라도 현재 페이지에 그대로 남아있을 것이라는 아이디어다. 이를 위해서는 <code class="language-plaintext highlighter-rouge">history.pushState()</code>를 사용하면 된다.</p> + +<div class="language-react highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +</pre></td><td class="rouge-code"><pre><span class="nf">useEffect</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span> + <span class="kd">const</span> <span class="nx">preventGoBack</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> + <span class="nx">history</span><span class="p">.</span><span class="nf">pushState</span><span class="p">(</span><span class="kc">null</span><span class="p">,</span> <span class="dl">""</span><span class="p">,</span> <span class="nx">location</span><span class="p">.</span><span class="nx">href</span><span class="p">);</span> + <span class="p">};</span> + + <span class="nx">history</span><span class="p">.</span><span class="nf">pushState</span><span class="p">(</span><span class="kc">null</span><span class="p">,</span> <span class="dl">""</span><span class="p">,</span> <span class="nx">location</span><span class="p">.</span><span class="nx">href</span><span class="p">);</span> + <span class="nb">window</span><span class="p">.</span><span class="nf">addEventListener</span><span class="p">(</span><span class="dl">"</span><span class="s2">popstate</span><span class="dl">"</span><span class="p">,</span> <span class="nx">preventGoBack</span><span class="p">);</span> + + <span class="k">return </span><span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> + <span class="nb">window</span><span class="p">.</span><span class="nf">removeEventListener</span><span class="p">(</span><span class="dl">"</span><span class="s2">popstate</span><span class="dl">"</span><span class="p">,</span> <span class="nx">preventGoBack</span><span class="p">);</span> + <span class="p">}</span> +<span class="p">},</span> <span class="p">[])</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<ol> + <li>마운트 시,<code class="language-plaintext highlighter-rouge">history.pushState()</code>로 현재 페이지를 스택 마지막에 추가</li> + <li><code class="language-plaintext highlighter-rouge">popstate</code> 이벤트 콜백 등록</li> + <li><code class="language-plaintext highlighter-rouge">popstate</code> 이벤트 발생시, 스택이 하나 줄어듦. but 콜백 함수에서 다시 스택을 추가함</li> +</ol> + +<p>하지만 이와같은 방법에는 단점이 있는데, 사용자가 아무리 뒤로가기를 눌러도 뒤로가기가 동작되지 않는 것처럼 보이게 만든다는 점이다. 이를 해소하기 위해 다음과 같이 콜백함수를 수정할 수 있다.</p> + +<div class="language-react highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +</pre></td><td class="rouge-code"><pre><span class="kd">const</span> <span class="nx">preventGoBack</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> + <span class="k">if</span><span class="p">(</span><span class="nf">confirm</span><span class="p">(</span><span class="dl">"</span><span class="s2">정말 뒤로 가시겠습니까?</span><span class="dl">"</span><span class="p">))</span> <span class="p">{</span> + <span class="nx">history</span><span class="p">.</span><span class="nf">back</span><span class="p">();</span> + <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> + <span class="nx">history</span><span class="p">.</span><span class="nf">pushState</span><span class="p">(</span><span class="kc">null</span><span class="p">,</span> <span class="dl">""</span><span class="p">,</span> <span class="nx">location</span><span class="p">.</span><span class="nx">href</span><span class="p">);</span> + <span class="p">}</span> +<span class="p">};</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>스택을 하나 추가하기 전에 사용자에게 경고창을 띄우고, 확인을 눌렀을 경우에는 한번 더 뒤로가기를 시도해 이전 페이지로 이동하는 방법이다.</p> + +<p>여기서 새로고침/창닫기/링크이동에서 했던 것처럼 사용자가 입력을 했을때만 뒤로가기를 막도록 하면 다음과 같다.</p> + +<div class="language-react highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +</pre></td><td class="rouge-code"><pre><span class="kd">const</span> <span class="p">[</span><span class="nx">hasInputFilled</span><span class="p">,</span> <span class="nx">setHasInputFilled</span><span class="p">]</span> <span class="o">=</span> <span class="nf">useState</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span> + +<span class="nf">useEffect</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span> + <span class="kd">const</span> <span class="nx">preventGoBack</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> + <span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="nx">hasInputFilled</span><span class="p">)</span> <span class="p">{</span> + <span class="k">return</span><span class="p">;</span> + <span class="p">}</span> + + <span class="k">if</span><span class="p">(</span><span class="nf">confirm</span><span class="p">(</span><span class="dl">"</span><span class="s2">정말 뒤로 가시겠습니까?</span><span class="dl">"</span><span class="p">))</span> <span class="p">{</span> + <span class="nx">history</span><span class="p">.</span><span class="nf">back</span><span class="p">();</span> + <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> + <span class="nx">history</span><span class="p">.</span><span class="nf">pushState</span><span class="p">(</span><span class="kc">null</span><span class="p">,</span> <span class="dl">""</span><span class="p">,</span> <span class="nx">location</span><span class="p">.</span><span class="nx">href</span><span class="p">);</span> + <span class="p">}</span> + <span class="p">};</span> + + <span class="k">if</span><span class="p">(</span><span class="nx">hasInputFilled</span><span class="p">)</span> <span class="p">{</span> + <span class="nx">history</span><span class="p">.</span><span class="nf">pushState</span><span class="p">(</span><span class="kc">null</span><span class="p">,</span> <span class="dl">""</span><span class="p">,</span> <span class="nx">location</span><span class="p">.</span><span class="nx">href</span><span class="p">);</span> + <span class="p">}</span> + <span class="nb">window</span><span class="p">.</span><span class="nf">addEventListener</span><span class="p">(</span><span class="dl">"</span><span class="s2">popstate</span><span class="dl">"</span><span class="p">,</span> <span class="nx">preventGoBack</span><span class="p">);</span> + + <span class="k">return </span><span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> + <span class="nb">window</span><span class="p">.</span><span class="nf">removeEventListener</span><span class="p">(</span><span class="dl">"</span><span class="s2">popstate</span><span class="dl">"</span><span class="p">,</span> <span class="nx">preventGoBack</span><span class="p">);</span> + <span class="p">}</span> +<span class="p">},</span> <span class="p">[</span><span class="nx">hasInputFilled</span><span class="p">])</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>여기서 스택에 넣어진 현재 페이지의 길이를 저장하여 조금 더 안정적으로 동작하도록 할 수 있다.</p> + +<div class="language-react highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +</pre></td><td class="rouge-code"><pre><span class="kd">const</span> <span class="p">[</span><span class="nx">hasInputFilled</span><span class="p">,</span> <span class="nx">setHasInputFilled</span><span class="p">]</span> <span class="o">=</span> <span class="nf">useState</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span> +<span class="kd">const</span> <span class="p">[</span><span class="nx">pushedLength</span><span class="p">,</span> <span class="nx">setPushedLength</span><span class="p">]</span> <span class="o">=</span> <span class="nf">useState</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> + +<span class="nf">useEffect</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span> + <span class="kd">const</span> <span class="nx">preventGoBack</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> + <span class="k">if</span><span class="p">(</span><span class="nx">pushedLength</span> <span class="o">===</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> + <span class="k">return</span><span class="p">;</span> + <span class="p">}</span> + + <span class="k">if</span><span class="p">(</span><span class="nf">confirm</span><span class="p">(</span><span class="dl">"</span><span class="s2">정말 뒤로 가시겠습니까?</span><span class="dl">"</span><span class="p">))</span> <span class="p">{</span> + <span class="nx">history</span><span class="p">.</span><span class="nf">back</span><span class="p">();</span> + <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> + <span class="nx">history</span><span class="p">.</span><span class="nf">pushState</span><span class="p">(</span><span class="kc">null</span><span class="p">,</span> <span class="dl">""</span><span class="p">,</span> <span class="nx">location</span><span class="p">.</span><span class="nx">href</span><span class="p">);</span> + <span class="p">}</span> + <span class="p">};</span> + + <span class="k">if</span><span class="p">(</span><span class="nx">hasInputFilled</span> <span class="o">&amp;&amp;</span> <span class="nx">pushedLength</span> <span class="o">===</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> + <span class="nx">history</span><span class="p">.</span><span class="nf">pushState</span><span class="p">(</span><span class="kc">null</span><span class="p">,</span> <span class="dl">""</span><span class="p">,</span> <span class="nx">location</span><span class="p">.</span><span class="nx">href</span><span class="p">);</span> + <span class="nf">setPushedLength</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> + <span class="p">}</span> <span class="k">else</span> <span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="nx">hasInputFilled</span> <span class="o">&amp;&amp;</span> <span class="nx">pushedLength</span> <span class="o">===</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span> + <span class="nx">history</span><span class="p">.</span><span class="nf">back</span><span class="p">();</span> + <span class="nf">setPushedLength</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span> + <span class="p">}</span> + + <span class="nb">window</span><span class="p">.</span><span class="nf">addEventListener</span><span class="p">(</span><span class="dl">"</span><span class="s2">popstate</span><span class="dl">"</span><span class="p">,</span> <span class="nx">preventGoBack</span><span class="p">);</span> + + <span class="k">return </span><span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> + <span class="nb">window</span><span class="p">.</span><span class="nf">removeEventListener</span><span class="p">(</span><span class="dl">"</span><span class="s2">popstate</span><span class="dl">"</span><span class="p">,</span> <span class="nx">preventGoBack</span><span class="p">);</span> + <span class="p">}</span> +<span class="p">},</span> <span class="p">[</span><span class="nx">hasInputFilled</span><span class="p">,</span> <span class="nx">pushedLength</span><span class="p">])</span> +</pre></td></tr></tbody></table></code></pre></div></div> + + + Sat, 29 Jul 2023 22:00:00 +0900 + https://seongil-shin.github.io/posts/react-%EB%92%A4%EB%A1%9C%EA%B0%80%EA%B8%B0-%EB%A7%89%EA%B8%B0/ + https://seongil-shin.github.io/posts/react-%EB%92%A4%EB%A1%9C%EA%B0%80%EA%B8%B0-%EB%A7%89%EA%B8%B0/ + + react + + + study + + react + + + + + IOS 16.5에서 video가 크기 변경이 적용되지 않는 이슈 + <h2 id="spec">Spec</h2> + +<ul> + <li> + <p><a href="https://swiperjs.com/">Swiper</a> (ver 4.5.1) 안에 <code class="language-plaintext highlighter-rouge">&lt;video&gt;</code> 태그가 <code class="language-plaintext highlighter-rouge">slide</code>로 포함되어있음.</p> + </li> + <li> + <p>현재 가장 가운데 있는 slide 안의 video는 크기를 키우고, slide가 옆으로 이동하면 다시 원래 사이즈로 돌아가도록 함</p> + + <video src="/assets/video/2023-06-18-ios-issue-spec.mov"></video> + </li> +</ul> + +<h2 id="issue">Issue</h2> + +<ul> + <li>IOS 16.5 safari에서는 다음과 같이 가운데 slide 안의 video 크기가 정상적으로 확대되지 않음</li> +</ul> + +<video src="/assets/video/2023-06-18-ios-issue.mp4"></video> + +<h2 id="원인">원인</h2> + +<ul> + <li> + <p>가운데 video 확대를 위한 코드는 다음과 같이 <code class="language-plaintext highlighter-rouge">slide-active</code> 상태일때 스타일을 적용하여 크기를 변경해주는 로직이다.</p> + + <div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +</pre></td><td class="rouge-code"><pre><span class="nc">.swiper</span> <span class="nc">.swiper-slide</span> <span class="nt">video</span> <span class="p">{</span> + <span class="nl">width</span><span class="p">:</span> <span class="m">173px</span><span class="p">;</span> + <span class="nl">height</span><span class="p">:</span> <span class="m">375px</span><span class="p">;</span> +<span class="p">}</span> + +<span class="nc">.swiper</span> <span class="nc">.swiper-slide-active</span> <span class="nt">video</span> <span class="p">{</span> + <span class="nl">width</span><span class="p">:</span> <span class="m">238px</span> <span class="cp">!important</span><span class="p">;</span> + <span class="nl">height</span><span class="p">:</span> <span class="m">418px</span> <span class="cp">!important</span><span class="p">;</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div> </div> + + <ul> + <li>가운데 slide에 정상적으로 <code class="language-plaintext highlighter-rouge">swiper-slide-active</code> 클래스가 적용되면 video 태그의 크기가 정상적으로 커져야했고, 개발자도구로 확인했을떄 문제가 발생했던 기기에서도 제대로 클래스가 적용된 것을 확인할 수 있었다. 하지만 이유는 알수없지만 ios 16.5 safari에서는 video 태그의 크기변화를 잡아내지 못하여 화면에 적용되지 않은 것이다.</li> + </ul> + </li> +</ul> + +<h2 id="해결">해결</h2> + +<p>다음과 같이 swiper 이벤트 중 <code class="language-plaintext highlighter-rouge">slideChangeTransitionStart</code>가 발동될 떄 명시적으로 style을 변경하고, 약간의 타이머를 주어 1px 크게 변경하여 브라우저가 크기 변화를 인식할 수 있도록 하였다.</p> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +</pre></td><td class="rouge-code"><pre><span class="nx">slideChangeTransitionStart</span> <span class="p">:</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span> + <span class="nx">slides</span><span class="p">?.</span><span class="nf">forEach</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">item</span><span class="p">)</span> <span class="p">{</span> + <span class="k">if</span><span class="p">(</span><span class="nx">item</span><span class="p">.</span><span class="nx">slide</span><span class="p">.</span><span class="nx">classList</span><span class="p">.</span><span class="nf">contains</span><span class="p">(</span><span class="dl">"</span><span class="s2">swiper-slide-active</span><span class="dl">"</span><span class="p">))</span> <span class="p">{</span> + <span class="k">if</span><span class="p">(</span><span class="nb">window</span><span class="p">.</span><span class="nx">isIOS</span><span class="p">)</span> <span class="p">{</span> + <span class="k">if</span><span class="p">(</span><span class="nb">window</span><span class="p">.</span><span class="nx">innerWidth</span> <span class="o">&gt;=</span> <span class="mi">1024</span><span class="p">)</span> <span class="p">{</span> + <span class="nx">activeVideo</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nf">setProperty</span><span class="p">(</span><span class="dl">"</span><span class="s2">width</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">236px</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">important</span><span class="dl">"</span><span class="p">);</span> + <span class="nx">activeVideo</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nf">setProperty</span><span class="p">(</span><span class="dl">"</span><span class="s2">height</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">516px</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">important</span><span class="dl">"</span><span class="p">);</span> + <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> + <span class="nx">activeVideo</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nf">setProperty</span><span class="p">(</span><span class="dl">"</span><span class="s2">width</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">238px</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">important</span><span class="dl">"</span><span class="p">);</span> + <span class="nx">activeVideo</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nf">setProperty</span><span class="p">(</span><span class="dl">"</span><span class="s2">height</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">418px</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">important</span><span class="dl">"</span><span class="p">);</span> + <span class="p">}</span> + + <span class="nf">setTimeout</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span> + <span class="k">if</span><span class="p">(</span><span class="nb">window</span><span class="p">.</span><span class="nx">innerWidth</span> <span class="o">&gt;=</span> <span class="mi">1024</span><span class="p">)</span> <span class="p">{</span> + <span class="nx">activeVideo</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nf">setProperty</span><span class="p">(</span><span class="dl">"</span><span class="s2">width</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">237px</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">important</span><span class="dl">"</span><span class="p">);</span> + <span class="nx">activeVideo</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nf">setProperty</span><span class="p">(</span><span class="dl">"</span><span class="s2">height</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">517px</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">important</span><span class="dl">"</span><span class="p">);</span> + <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> + <span class="nx">activeVideo</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nf">setProperty</span><span class="p">(</span><span class="dl">"</span><span class="s2">width</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">239px</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">important</span><span class="dl">"</span><span class="p">);</span> + <span class="nx">activeVideo</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nf">setProperty</span><span class="p">(</span><span class="dl">"</span><span class="s2">height</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">419px</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">important</span><span class="dl">"</span><span class="p">);</span> + <span class="p">}</span> + <span class="p">},</span> <span class="mi">30</span><span class="p">)</span> + <span class="p">}</span> + <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> + <span class="k">if</span><span class="p">(</span><span class="nb">window</span><span class="p">.</span><span class="nx">isIOS</span><span class="p">)</span> <span class="p">{</span> + <span class="k">if</span><span class="p">(</span><span class="nb">window</span><span class="p">.</span><span class="nx">innerWidth</span> <span class="o">&gt;=</span> <span class="mi">1024</span><span class="p">)</span> <span class="p">{</span> + <span class="nx">item</span><span class="p">.</span><span class="nx">video</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nf">setProperty</span><span class="p">(</span><span class="dl">"</span><span class="s2">width</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">187px</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">important</span><span class="dl">"</span><span class="p">);</span> + <span class="nx">item</span><span class="p">.</span><span class="nx">video</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nf">setProperty</span><span class="p">(</span><span class="dl">"</span><span class="s2">height</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">405px</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">important</span><span class="dl">"</span><span class="p">);</span> + <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> + <span class="nx">item</span><span class="p">.</span><span class="nx">video</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nf">setProperty</span><span class="p">(</span><span class="dl">"</span><span class="s2">width</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">187px</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">important</span><span class="dl">"</span><span class="p">);</span> + <span class="nx">item</span><span class="p">.</span><span class="nx">video</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nf">setProperty</span><span class="p">(</span><span class="dl">"</span><span class="s2">height</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">328px</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">important</span><span class="dl">"</span><span class="p">);</span> + <span class="p">}</span> + + <span class="nf">setTimeout</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span> + <span class="k">if</span><span class="p">(</span><span class="nb">window</span><span class="p">.</span><span class="nx">innerWidth</span> <span class="o">&gt;=</span> <span class="mi">1024</span><span class="p">)</span> <span class="p">{</span> + <span class="nx">item</span><span class="p">.</span><span class="nx">video</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nf">setProperty</span><span class="p">(</span><span class="dl">"</span><span class="s2">width</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">188px</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">important</span><span class="dl">"</span><span class="p">);</span> + <span class="nx">item</span><span class="p">.</span><span class="nx">video</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nf">setProperty</span><span class="p">(</span><span class="dl">"</span><span class="s2">height</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">406px</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">important</span><span class="dl">"</span><span class="p">);</span> + <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> + <span class="nx">item</span><span class="p">.</span><span class="nx">video</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nf">setProperty</span><span class="p">(</span><span class="dl">"</span><span class="s2">width</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">188px</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">important</span><span class="dl">"</span><span class="p">);</span> + <span class="nx">item</span><span class="p">.</span><span class="nx">video</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nf">setProperty</span><span class="p">(</span><span class="dl">"</span><span class="s2">height</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">329px</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">important</span><span class="dl">"</span><span class="p">);</span> + <span class="p">}</span> + <span class="p">},</span> <span class="mi">30</span><span class="p">)</span> + <span class="p">}</span> + <span class="p">}</span> + <span class="p">})</span> +<span class="p">},</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>이후 문제없이 동작함을 확인하였다.</p> + + Sun, 18 Jun 2023 18:00:00 +0900 + https://seongil-shin.github.io/posts/ios-16-5-video/ + https://seongil-shin.github.io/posts/ios-16-5-video/ + + issues + + + study + + js + + + + + es6에서 도입된 문법 + <h2 id="let-const-키워드를-통한-변수선언"><strong>let, const 키워드를 통한 변수선언</strong></h2> + +<p>기존 자바스크립트에서는 <code class="language-plaintext highlighter-rouge">var</code> 키워드로만 변수선언이 가능했다. 하지만, <code class="language-plaintext highlighter-rouge">let</code> <code class="language-plaintext highlighter-rouge">const</code> 키워드를 추가하여 보다 예측가능한 코드를 작성할 수 있게 됐다.</p> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +</pre></td><td class="rouge-code"><pre><span class="c1">// es6 이전.</span> +<span class="c1">// var는 재선언 가능</span> +<span class="kd">var</span> <span class="nx">a</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">This is string</span><span class="dl">"</span><span class="p">;</span> +<span class="kd">var</span> <span class="nx">a</span> <span class="o">=</span> <span class="mi">1234</span><span class="p">;</span> + +<span class="c1">// ES6 이후</span> +<span class="c1">// let은 재선언 불가능</span> +<span class="kd">let</span> <span class="nx">b</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">This is string</span><span class="dl">"</span><span class="p">;</span> +<span class="nx">b</span> <span class="o">=</span> <span class="mi">1234</span><span class="p">;</span> + +<span class="c1">// const는 재할당 불가능</span> +<span class="kd">const</span> <span class="nx">c</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">This is string</span><span class="dl">"</span><span class="p">;</span> +<span class="nx">c</span> <span class="o">=</span> <span class="mi">1234</span><span class="p">;</span> <span class="c1">// &lt;-- 에러 발생</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p><code class="language-plaintext highlighter-rouge">const</code>는 상수 키워드로 재할당, 재선언이 불가능하다. 변경할 수 없다로 이해하기 쉽지만, <code class="language-plaintext highlighter-rouge">let</code>과 <code class="language-plaintext highlighter-rouge">var</code>는 헷갈릴 수 있다. 차이는 다음과 같다.</p> + +<table> + <thead> + <tr> + <th> </th> + <th>var</th> + <th>let</th> + </tr> + </thead> + <tbody> + <tr> + <td>재선언</td> + <td>가능</td> + <td>불가능</td> + </tr> + <tr> + <td>재할당</td> + <td>가능</td> + <td>가능</td> + </tr> + <tr> + <td>scope</td> + <td>function-scope</td> + <td>block-scope</td> + </tr> + <tr> + <td>호이스팅</td> + <td>일어남</td> + <td>안일어남<br />(정확히는 일어나지만 할당문을 만나기 전까지는 사용불가능)</td> + </tr> + </tbody> +</table> + +<p><br /></p> + +<h2 id="템플릿리터럴">템플릿리터럴</h2> + +<p>문자열 내에 변수사용을 쉽게 만들어주는 문법으로 다음과 같이 사용한다.</p> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +</pre></td><td class="rouge-code"><pre><span class="kd">const</span> <span class="nx">name</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">user-1</span><span class="dl">"</span><span class="p">;</span> +<span class="kd">const</span> <span class="nx">count</span> <span class="o">=</span> <span class="mi">324</span><span class="p">;</span> + +<span class="c1">// es6 이전</span> +<span class="kd">var</span> <span class="nx">str1</span> <span class="o">=</span> <span class="nx">name</span> <span class="o">+</span> <span class="dl">"</span><span class="s2"> has pushed button </span><span class="dl">"</span> <span class="o">+</span> <span class="nx">count</span> <span class="o">+</span> <span class="dl">"</span><span class="s2"> times.</span><span class="dl">"</span><span class="p">;</span> +<span class="c1">// es6 이후</span> +<span class="kd">const</span> <span class="nx">str2</span> <span class="o">=</span> <span class="s2">`</span><span class="p">${</span><span class="nx">name</span><span class="p">}</span><span class="s2"> has pushed button </span><span class="p">${</span><span class="nx">count</span><span class="p">}</span><span class="s2"> times.`</span><span class="p">;</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p><br /></p> + +<h2 id="화살표함수">화살표함수</h2> + +<p>함수 표현식을 화살표함수로 표현하여 간결하게 함수를 작성할 수 있다.</p> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +</pre></td><td class="rouge-code"><pre><span class="c1">// es6 이전</span> +<span class="kd">function</span> <span class="nf">func</span><span class="p">(</span><span class="nx">a</span><span class="p">,</span> <span class="nx">b</span><span class="p">)</span> <span class="p">{</span> + <span class="k">return</span> <span class="nx">a</span> <span class="o">+</span> <span class="nx">b</span><span class="p">;</span> +<span class="p">}</span> + +<span class="c1">// es6 이후</span> +<span class="kd">const</span> <span class="nx">func</span> <span class="o">=</span> <span class="p">(</span><span class="nx">a</span><span class="p">,</span> <span class="nx">b</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> + <span class="k">return</span> <span class="nx">a</span> <span class="o">+</span> <span class="nx">b</span><span class="p">;</span> +<span class="p">}</span> +<span class="c1">// return 생략</span> +<span class="kd">const</span> <span class="nx">func</span> <span class="o">=</span> <span class="p">(</span><span class="nx">a</span><span class="p">,</span> <span class="nx">b</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">a</span> <span class="o">+</span> <span class="nx">b</span><span class="p">;</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p><br /></p> + +<h2 id="구조분해할당">구조분해할당</h2> + +<p>객체, 배열의 구조를 분해하여, 새로운 변수에 할당하는 과정이다. 객체/배열에서 값을 꺼낼때 가독성 좋게 가져올 수 있도록 한다.</p> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +</pre></td><td class="rouge-code"><pre><span class="c1">// es6 이전 (배열)</span> +<span class="kd">var</span> <span class="nx">arr</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">];</span> +<span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="nx">arr</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="nx">arr</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="nx">arr</span><span class="p">[</span><span class="mi">2</span><span class="p">]);</span> +<span class="c1">// es6 이후 (배열)</span> +<span class="kd">const</span> <span class="p">[</span><span class="nx">first</span><span class="p">,</span> <span class="nx">second</span><span class="p">,</span> <span class="nx">third</span><span class="p">]</span> <span class="o">=</span> <span class="nx">arr</span><span class="p">;</span> +<span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="nx">first</span><span class="p">,</span> <span class="nx">second</span><span class="p">,</span> <span class="nx">third</span><span class="p">);</span> + +<span class="c1">// es6 이전 (객체)</span> +<span class="kd">var</span> <span class="nx">obj</span> <span class="o">=</span> <span class="p">{</span> + <span class="na">name</span> <span class="p">:</span> <span class="dl">"</span><span class="s2">user-1</span><span class="dl">"</span><span class="p">,</span> + <span class="na">count</span><span class="p">:</span> <span class="mi">123</span> +<span class="p">}</span> +<span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="nx">obj</span><span class="p">.</span><span class="nx">name</span><span class="p">,</span> <span class="nx">obj</span><span class="p">.</span><span class="nx">count</span><span class="p">);</span> + +<span class="c1">// es6 이후 (객체)</span> +<span class="kd">const</span> <span class="p">{</span><span class="nx">name</span><span class="p">,</span> <span class="nx">count</span><span class="p">}</span> <span class="o">=</span> <span class="nx">obj</span><span class="p">;</span> +<span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="nx">name</span><span class="p">,</span> <span class="nx">count</span><span class="p">);</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p><br /></p> + +<h2 id="promise">Promise</h2> + +<p>ES6 이전에 자바스크립트의 비동기처리는 콜백함수로 이루어졌다. 하지만 비동기 함수가 많아지면서 콜백 지옥이 일어나기도 했다.</p> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +</pre></td><td class="rouge-code"><pre><span class="c1">// es6 이전</span> +<span class="kd">function</span> <span class="nf">callbackHell</span><span class="p">(</span><span class="nx">callback</span><span class="p">)</span> <span class="p">{</span> + <span class="nf">setTimeout</span><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span> + <span class="p">...</span> <span class="c1">// do something</span> + <span class="nf">setTimeout</span><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span> + <span class="p">...</span> <span class="c1">// do something</span> + <span class="nf">setTimeout</span><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span> + <span class="p">...</span> <span class="c1">// do something</span> + <span class="nf">callback</span><span class="p">();</span> + <span class="p">},</span> <span class="mi">1000</span><span class="p">)</span> + <span class="p">},</span> <span class="mi">1000</span><span class="p">)</span> + <span class="p">},</span> <span class="mi">1000</span><span class="p">)</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>이러한 콜백 지옥을 개션하기 위한 문법으로 다음과 같이 코드가 변경된다.</p> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +</pre></td><td class="rouge-code"><pre><span class="kd">function</span> <span class="nf">callbackFree</span><span class="p">(</span><span class="nx">callback</span><span class="p">)</span> <span class="p">{</span> + <span class="kd">const</span> <span class="nx">promise1</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Promise</span><span class="p">((</span><span class="nx">resolve</span><span class="p">,</span> <span class="nx">reject</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> + <span class="nf">setTimeout</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span> + <span class="p">...</span> <span class="c1">// do something</span> + <span class="nf">resolve</span><span class="p">();</span> + <span class="p">},</span> <span class="mi">1000</span><span class="p">)</span> + <span class="p">})</span> + + <span class="kd">const</span> <span class="nx">promise2</span> <span class="o">=</span> <span class="nx">promise1</span><span class="p">.</span><span class="nf">then</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span> + <span class="k">return</span> <span class="k">new</span> <span class="nc">Promise</span><span class="p">((</span><span class="nx">resolve</span><span class="p">,</span> <span class="nx">reject</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> + <span class="nf">setTimeout</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span> + <span class="p">...</span> <span class="c1">// do something</span> + <span class="nf">resolve</span><span class="p">();</span> + <span class="p">},</span> <span class="mi">1000</span><span class="p">)</span> + <span class="p">})</span> + <span class="p">});</span> + + <span class="nx">promise2</span><span class="p">.</span><span class="nf">then</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span> + <span class="nf">setTimeout</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span> + <span class="p">...</span> <span class="c1">// do something</span> + <span class="nf">callback</span><span class="p">();</span> + <span class="p">},</span> <span class="mi">1000</span><span class="p">)</span> + <span class="p">})</span> +<span class="p">}</span> + +</pre></td></tr></tbody></table></code></pre></div></div> + +<p><br /></p> + +<h2 id="class">Class</h2> + +<p>자바스크립트에서 객체 지향 프로그래밍이 가능하도록 <code class="language-plaintext highlighter-rouge">Class</code> 키워드가 도입되었다.</p> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +</pre></td><td class="rouge-code"><pre><span class="kd">class</span> <span class="nc">Polygon</span> <span class="p">{</span> + <span class="nf">constructor</span><span class="p">(</span><span class="nx">height</span><span class="p">,</span> <span class="nx">width</span><span class="p">)</span> <span class="p">{</span> + <span class="k">this</span><span class="p">.</span><span class="nx">name</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">Polygon</span><span class="dl">'</span><span class="p">;</span> + <span class="k">this</span><span class="p">.</span><span class="nx">height</span> <span class="o">=</span> <span class="nx">height</span><span class="p">;</span> + <span class="k">this</span><span class="p">.</span><span class="nx">width</span> <span class="o">=</span> <span class="nx">width</span><span class="p">;</span> + <span class="p">}</span> +<span class="p">}</span> + +<span class="kd">class</span> <span class="nc">Square</span> <span class="kd">extends</span> <span class="nc">Polygon</span> <span class="p">{</span> + <span class="nf">constructor</span><span class="p">(</span><span class="nx">length</span><span class="p">)</span> <span class="p">{</span> + <span class="k">super</span><span class="p">(</span><span class="nx">length</span><span class="p">,</span> <span class="nx">length</span><span class="p">);</span> + <span class="k">this</span><span class="p">.</span><span class="nx">name</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">Square</span><span class="dl">'</span><span class="p">;</span> + <span class="p">}</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p><br /></p> + +<h2 id="모듈-시스템">모듈 시스템</h2> + +<p>자바스크립트에서 코드를 분리하여 관리할 수 있게하여 재사용성과 생산성을 높이기위해 모듈 시스템이 도입되었다.</p> + +<ul> + <li> + <p>script 태그 사용 예시</p> + + <div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +</pre></td><td class="rouge-code"><pre><span class="nt">&lt;script </span><span class="na">type=</span><span class="s">"module"</span> <span class="na">src=</span><span class="s">"lib.mjs"</span><span class="nt">&gt;&lt;/script&gt;</span> +</pre></td></tr></tbody></table></code></pre></div> </div> + </li> + <li> + <p>es6에서 사용 에시</p> + + <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +</pre></td><td class="rouge-code"><pre><span class="k">import</span> <span class="nx">compute</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">commons/compute</span><span class="dl">"</span><span class="p">;</span> + +<span class="kd">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="nf">compute</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">);</span> + +<span class="k">export</span> <span class="nx">result</span><span class="p">;</span> +</pre></td></tr></tbody></table></code></pre></div> </div> + </li> + <li> + <p>commonsJs에서 사용 예시</p> + <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +</pre></td><td class="rouge-code"><pre><span class="kd">const</span> <span class="nx">compute</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">commons/compute</span><span class="dl">"</span><span class="p">);</span> + +<span class="kd">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="nf">compute</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">);</span> + +<span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="p">{</span> <span class="nx">result</span> <span class="p">};</span> +</pre></td></tr></tbody></table></code></pre></div> </div> + </li> +</ul> + +<blockquote> + <p>require와 import의 차이점</p> + + <ul> + <li>require/exports</li> + <li>구형브라우저나, 브라우저 밖에서 사용하는 <code class="language-plaintext highlighter-rouge">CommonJS</code> 문법</li> + <li> + <p>파일의 어디서나 호출 가능</p> + </li> + <li>import/export</li> + <li>ES6에서 도입된 문법</li> + <li>파일의 시작부분에서만 실행가능(import 전용 비동기 문법으로 중간에 불러올 수도 있다.)</li> + <li> + <p>필요한 부분만 선택하여 로드 가능. 또한 require보다 성능이 우수함</p> + </li> + <li>하나의 파일에서 두 키워드를 동시에 사용할 순 없</li> + </ul> +</blockquote> + +<p><br /></p> + +<h2 id="출처">출처</h2> + +<ul> + <li>https://hanamon.kr/javascript-es6-%EB%AC%B8%EB%B2%95/</li> +</ul> + + Mon, 12 Jun 2023 23:00:00 +0900 + https://seongil-shin.github.io/posts/es6/ + https://seongil-shin.github.io/posts/es6/ + + javascript + + + study + + javascript + + + + + 영상이 자동재생되지 않는 문제 + <p>다음과 같은 요구사항을 가지는 웹페이지를 개발하고 있었다.</p> + +<ul> + <li>swiper로 여러 슬라이드를 띄움</li> + <li>슬라이드 내에는 영상이 존재하고, 각 영상은 자동으로 재생되어야함.</li> +</ul> + +<p>단순히 swiper를 연동하고 video 태그에 <code class="language-plaintext highlighter-rouge">autoplay</code> 속성을 주면 될거라 생각하여 다음과 같이 코드를 작성했었다.</p> + +<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +</pre></td><td class="rouge-code"><pre><span class="nt">&lt;li</span> <span class="na">className=</span><span class="s">"swiper-slide"</span><span class="nt">&gt;</span> + <span class="nt">&lt;video</span> + <span class="na">poster=</span><span class="s">"poster 주소"</span> + <span class="na">autoPlay</span> <span class="na">muted</span> <span class="na">playsInline</span> <span class="na">loop</span><span class="nt">&gt;</span> + <span class="nt">&lt;source</span> + <span class="na">src=</span><span class="s">"영상주소"</span> + <span class="na">type=</span><span class="s">"video/mp4"</span><span class="nt">/&gt;</span> + <span class="nt">&lt;/video&gt;</span> +<span class="nt">&lt;/li&gt;</span> +<span class="nt">&lt;li</span> <span class="na">className=</span><span class="s">"swiper-slide"</span><span class="nt">&gt;</span> + <span class="nt">&lt;video</span> + <span class="na">poster=</span><span class="s">"poster 주소"</span> + <span class="na">autoPlay</span> <span class="na">muted</span> <span class="na">playsInline</span> <span class="na">loop</span><span class="nt">&gt;</span> + <span class="nt">&lt;source</span> + <span class="na">src=</span><span class="s">"영상주소"</span> + <span class="na">type=</span><span class="s">"video/mp4"</span><span class="nt">/&gt;</span> + <span class="nt">&lt;/video&gt;</span> +<span class="nt">&lt;/li&gt;</span> +... +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>실제로 당시 개발 환경(mac, chrome)에서는 문제없이 동작했었다. 하지만 QA 단계에서 영상이 재생되지 않으며 사이트 자체가 매우 느리다는 제보를 받았다. 그리고 이 문제를 해결한 과정은 다음과 같다.</p> + +<p><br /></p> + +<h2 id="필요한-영상들만-재생">필요한 영상들만 재생</h2> + +<p>swiper에서 loop 옵션을 사용하고 있었다. 끝까지 가면 다시 처음 슬라이드가 나오도록 하기 위해서였다. 하지만, loop 옵션을 사용하면 다음과 같이 실제로 보여질 <code class="language-plaintext highlighter-rouge">swiper-slide</code> 앞 뒤로 <code class="language-plaintext highlighter-rouge">swiper-slide-duplicate</code>를 생성한다. 그리고 각 <code class="language-plaintext highlighter-rouge">swiper-slide-duplicate</code>는 <code class="language-plaintext highlighter-rouge">slide-swiper</code>와 구성이 같다.</p> + +<p><img src="/assets/image-20230603185315584.png" alt="image-20230603185315584" /></p> + +<p>즉, 필요한 슬라이드 개수의 3배 정도 슬라이드가 만들어지고, 각 슬라이드 안에는 영상이 포함되어있다는 것이다. 이처럼 많은 영상을 한번에 재생하는 것은 사이트 성능에 악영향을 줄거라 생각했다. 따라서 다음과 같이 swiper에 옵션을 주어 <code class="language-plaintext highlighter-rouge">swiper-slide-duplicate</code> 안에 있는 비디오의 재생은 멈추었다.</p> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +</pre></td><td class="rouge-code"><pre><span class="k">new</span> <span class="nc">Swiper</span><span class="p">(</span><span class="dl">'</span><span class="s1">.myswiper1</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span> + <span class="na">touchRatio</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> + <span class="na">autoHeight</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> + <span class="na">speed</span><span class="p">:</span> <span class="mi">650</span><span class="p">,</span> + <span class="na">observer</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> + <span class="na">observeParents</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> + <span class="na">slidesPerView</span><span class="p">:</span> <span class="dl">'</span><span class="s1">auto</span><span class="dl">'</span><span class="p">,</span> + <span class="na">loop</span><span class="p">:</span><span class="kc">true</span><span class="p">,</span> + <span class="na">navigation</span><span class="p">:</span> <span class="p">{</span> + <span class="na">nextEl</span><span class="p">:</span> <span class="dl">'</span><span class="s1">.swiper-button-next</span><span class="dl">'</span><span class="p">,</span> + <span class="na">prevEl</span><span class="p">:</span> <span class="dl">'</span><span class="s1">.swiper-button-prev</span><span class="dl">'</span><span class="p">,</span> + <span class="p">},</span> + <span class="na">on</span><span class="p">:</span> <span class="p">{</span> + <span class="na">slideChangeTransitionEnd</span> <span class="p">:</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span> + <span class="nf">$</span><span class="p">(</span><span class="dl">'</span><span class="s1">.myswiper1 .swiper-slide.swiper-slide-duplicate</span><span class="dl">'</span><span class="p">)</span> + <span class="p">.</span><span class="nf">find</span><span class="p">(</span><span class="dl">"</span><span class="s2">.video_box video</span><span class="dl">"</span><span class="p">)?</span> + <span class="p">.</span><span class="nf">each</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">idx</span><span class="p">,</span> <span class="nx">item</span><span class="p">)</span> <span class="p">{</span> + <span class="nx">item</span><span class="p">.</span><span class="nf">pause</span><span class="p">();</span> + <span class="p">})</span> + <span class="p">},</span> + <span class="p">},</span> + <span class="p">});</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>하지만 이렇게 하니 <code class="language-plaintext highlighter-rouge">마지막 -&gt; 첫번쨰</code>, <code class="language-plaintext highlighter-rouge">첫번쨰 -&gt; 마지막</code>으로 갈때 이동한 후 슬라이드의 영상이 재생되지 않는 것을 발견했다. 그 이유는 앞 두 경우에는 <code class="language-plaintext highlighter-rouge">swiper-slide-duplicate</code>가 화면에 보이는 경우였기 떄문이다. 다음 사진을 보면 <code class="language-plaintext highlighter-rouge">swiper-slide-active</code>가 <code class="language-plaintext highlighter-rouge">swiper-slide-duplicate</code>에 붙은 걸 확인할 수 있다.</p> + +<p><img src="/assets/image-20230603190020412.png" alt="image-20230603190020412" /></p> + +<p>따라서 다음과 같이 이벤트를 수정하여 <code class="language-plaintext highlighter-rouge">swiper-slide-active</code>가 붙은건 명시적으로 실행해주어 문제를 해결했다.</p> + +<pre><code class="language- js">new Swiper('.myswiper', { + touchRatio: 0, + autoHeight: true, + speed: 650, + observer: true, + observeParents: true, + slidesPerView: 'auto', + loop:true, + navigation: { + nextEl: '.swiper-button-next', + prevEl: '.swiper-button-prev', + }, + on: { + slideChangeTransitionEnd : function() { + $('.myswiper .swiper-slide.swiper-slide-duplicate') + .find(".video_box video")? + .each(function(idx, item) { + item.pause(); + }) + $('.myswiper .swiper-slide.swiper-slide-active') + .find(".video_box video")? + .each(function(idx, item) { + item.play() + }); + }, + }, + }); +</code></pre> + +<p>이렇게 꼭 필요한 영상들만 자동재생을 적용하니 사이트가 빨라졌다는 답변을 받을 수 있었다.</p> + +<p><br /></p> + +<h2 id="영상-최적화">영상 최적화</h2> + +<p>위에서 슬라이드가 복사되는 것을 보고 영상이 너무 많아서 사이트가 느려지고 자동재생이 안되는게 아닐까라는 생각이 들었다. 따라서 영상 자체를 최적화하였다.</p> + +<ul> + <li>영상 압축</li> + <li>필요한 사이즈로 줄이기</li> + <li>음성은 필요없으므로 음성 제거</li> +</ul> + +<p>위 세가지 작업을 진행하니 영상 사이즈를 90%정도 줄일 수 있었다.</p> + +<p>그리고 사이트가 빨라졌으며 자동재생이 안되는 문제도 크게 개선되었다는 답변이 왔다.</p> + +<p>하지만 여전히 자동재생은 가끔씩 안되고 있다는 애기를 들었다.</p> + +<p><br /></p> + +<h2 id="수동으로-영상-실행">수동으로 영상 실행</h2> + +<p>최종 수단으로 chatGPT에게 물어보니 다음과 같은 코드를 실행해보라는 답변을 받았다. 간헐적으로 영상이 자동 재생이 안되면 네트워크 이슈를 비롯하여 다양한 이슈가 있을 수 있으므로 수동으로도 영상을 실행해보라는 얘기였다.</p> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +</pre></td><td class="rouge-code"><pre><span class="kd">function</span> <span class="nf">playVideo</span><span class="p">()</span> <span class="p">{</span> + <span class="kd">var</span> <span class="nx">videos</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">querySelectorAll</span><span class="p">(</span><span class="dl">'</span><span class="s1">video</span><span class="dl">'</span><span class="p">);</span> + <span class="k">for </span><span class="p">(</span><span class="kd">var</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">videos</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> + <span class="nx">videos</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nf">play</span><span class="p">();</span> + <span class="p">}</span> +<span class="p">}</span> +<span class="nf">playVideo</span><span class="p">();</span> +<span class="nb">window</span><span class="p">.</span><span class="nf">addEventListener</span><span class="p">(</span><span class="dl">'</span><span class="s1">load</span><span class="dl">'</span><span class="p">,</span> <span class="nx">playVideo</span><span class="p">);</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>실제로 적용해보니 영상이 제대로 재생되었다..</p> + + + Sat, 03 Jun 2023 18:00:00 +0900 + https://seongil-shin.github.io/posts/video-notplaying/ + https://seongil-shin.github.io/posts/video-notplaying/ + + issues + + + study + + react + + + + + DB index + <p>##</p> + +<h3 id="인덱스란"><strong>인덱스란?</strong></h3> + +<p>DBMS에서 데이터베이스 테이블의 모든 데이터를 검색하여 데이터를 찾기에는 시간이 오래 걸리기에, <strong>데이터의 칼럼의 값과 그 데이터가 저장된 레코드의 주소를 키와 값의 쌍</strong>으로 만든 것.</p> + +<p>DBMS의 인덱스는 항상 정렬된 상태를 유지하기 때문에, 원하는 값을 탐색하는데는 빠르지만, 추가/삭제/수정에는 쿼리문 실행 속도가 느려진다.(작업 후 다시 정렬해야하기에)</p> + +<p>결과적으로 DBMS의 인덱스는 데이터 저장 성능을 희생하고, 읽기 성능을 높이는 기능이다. 따라서 모든 컬럼을 대상으로 인덱스를 생성하면 데이터 저장 성능이 떨어지고, 인덱스의 크기가 비대해지기에 역효과가 난다.</p> + +<p><br /></p> + +<h3 id="index-의-성능과-고려해야할-사항"><strong>Index 의 성능과 고려해야할 사항</strong></h3> + +<p>인덱스가 많아지면 SELECT는 빨라지지만, INSERT/DELETE/UPDATE는 느려진다. 이유는 인덱스에서 mutation 작업시 다음과 같은 일이 발생하기 떄문이다.</p> + +<ul> + <li>INSERT: 새로운 데이터에 대한 인덱스를 추가함. 이후 다시 데이터 정렬.</li> + <li>DELETE: 삭제하는 데이터의 인덱스를 사용하지 않는다는 표시를 함</li> + <li>UPDATE: 기존의 인덱스를 사용하지 않음 처리하고, 갱신된 데이터에 대해 인덱스를 추가함</li> +</ul> + +<p>UPDATE, DELETE 작업에서 기존의 인덱스를 그대로 두기에, mutation 작업이 빈번히 발생하면 테이블 로우는 10개인데 인덱스는 100개인 상황이 발생할 수 있다.</p> + +<p>또한 데이터의 형식에 따라 인덱스를 만들면 효율적이고 만들면 비효율적인 데이터의 형식이 존재한다. 예를들어, <code class="language-plaintext highlighter-rouge">이름</code>, <code class="language-plaintext highlighter-rouge">나이</code>, <code class="language-plaintext highlighter-rouge">성별</code> 세 가지의 필드가 있을때, 이름에 대해서는 인덱스가 효율적이고 나이,성별에 대해서는 그렇지 않다. 왜냐하면, 데이터의 범위가 작기때문에 겹치는 인덱스가 많아지고 되고, 따라서 추가적으로 원하는 데이터를 다시 검색해야하기 때문이다.</p> + +<p><br /></p> + +<h3 id="index-자료구조"><strong>Index 자료구조</strong></h3> + +<p><strong>B +- Tree 인덱스 알고리즘</strong></p> + +<p>일반적으로 사용되는 알고리즘. 칼럼의 값을 변형하지 않고, 원래의 값을 이용해 인덱싱하는 알고리즘이다.</p> + +<p><strong>Hash 인덱스 알고리즘</strong></p> + +<p>컬럼의 값으로 해시 값을 계산하여 인덱싱하는 알고리즘으로 매우 빠른 검색을 지원함. 하지만 값을 변경하여 인덱싱하므로, 값의 일부만으로 검색하고자 할때는 해시 인덱스를 사용할 수 없음.</p> + +<p>주로 메모리 기반의 데이터베이스에서 많이 사용함.</p> + +<p><strong>B 트리를 자주 사용하는 이유</strong></p> + +<p>SELECT 질의의 조건에는 부등호 (&lt;&gt;) 연산도 포함됨. 해시 인덱스를 사용하면 이러한 부등호 연산에 문제가 생긴다. 왜냐하면 해시는 동등(=) 연산에 특화되어있기 때문이다.</p> + +<p><br /></p> + +<h3 id="클러스터드-인덱스"><strong>클러스터드 인덱스</strong></h3> + +<p>물리적으로 인접한 장소에 있는 데이터들을 동시에 조회하는 경우가 많으므로(spatial locality), 인접한 것들을 묶어 저장하는 방식의 인덱스</p> + +<p>이때 클러스터드 인덱스는 테이블의 primary key에 대해서만 적용된다. 즉 primary key가 비슷한 레코드끼리 묶어 저장한 것을 클러스터드 인덱스라고 표현함. 클러스터드 인덱스에서는 primary key에 의해 레코드의 저장 위치가 결정되며 primary key가 변경되면 레코드의 물리적인 저장위치 또한 변경되어야한다. 따라서 클러스터드 인덱스에서는 primary key를 신중하게 결정해야한다.</p> + +<p>클러스터드 인덱스는 테이블 당 한개만 생성할 수 있음(primary key에 대해 적용되기에). non 클러스터드 인덱스는 테이블당 여러개의 인덱스를 만들 수 있음</p> + +<p><br /></p> + +<h3 id="composite-index"><strong>Composite index</strong></h3> + +<p>여러개의 필드로 구성하는 인덱스. 이때 인덱스로 설정하는 필드의 속성이 중요하다.</p> + +<p>title, author 이 순서로 인덱스를 설정한다면 title 을 search 하는 경우, index 를 생성한 효과를 볼 수 있지만, author 만으로 search 하는 경우, index 를 생성한 것이 소용이 없어진다. 따라서 SELECT 질의를 어떻게 할 것인가가 인덱스를 어떻게 생성할 것인가에 대해 많은 영향을 끼치게 된다.</p> + +<p><br /></p> + + Sat, 03 Jun 2023 16:00:00 +0900 + https://seongil-shin.github.io/posts/index/ + https://seongil-shin.github.io/posts/index/ + + db + + + study + + database + + + + + 딥링크 + <p><strong>딥링크</strong>는 웹사이트에서 사용자 기기에 설치된 앱을 URI로 실행시킬 수 있도록하는 기술이다.</p> + +<p><br /></p> + +<h2 id="딥링크의-세가지-유형">딥링크의 세가지 유형</h2> + +<p>이러한 딥링크에는 세가지 유형이 있다.</p> + +<ul> + <li>URI Scheme (초기 형태)</li> + <li>보완 + <ul> + <li>Universal Link (IOS)</li> + <li>App Link (Android)</li> + </ul> + </li> +</ul> + +<h3 id="uri-scheme">URI Scheme</h3> + +<p>앱 개발자가 앱 내 특정 페이지마다 고유한 주소를 설정하고, 그 주소를 웹에서 실행시키면 설정한 특정 페이지가 열리는 형태이다. 이때 주소는 다음과 같은 형태이다.</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +</pre></td><td class="rouge-code"><pre>myapp://post?version=1 +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>여기서 <code class="language-plaintext highlighter-rouge">myapp://</code> 부분을 Scheme(프로토콜)이라고 하고, 앱 개발자가 자신만의 Scheme를 등록할 수 있다.</p> + +<p>하지만 이 Scheme를 소유권을 증명하지 않고 개발자가 원하는 값으로 등록할 수 있다는 점에서 <strong>Scheme값이 중복되는 문제</strong>가 발생했다. path까지 모두 동일한 주소를 사용한다면 android는 딥링크로 오픈할 앱 선택창이 노출되고, IOS는 가장 마지막에 설치한 앱이 자동으로 열린다.</p> + +<p>이 문제점을 보완하기 위해 나온 것이 Universal Link와 App Link이다.</p> + +<h3 id="universal-link-app-link">Universal Link, App Link</h3> + +<p>URI Scheme의 문제점을 보완하여 <strong>OS에 앱에 대한 도메인 주소를 등록함으로써 소유권을 증명</strong>한다. 도메인은 도메인 소유자만이 관리할 수 있기에 소유권이 증명되기 떄문이다.</p> + +<p>앱 빌드 시 앱을 열 도메인을 등록하고 보안을 위해 등록한 도메인의 특정 경로에 인증 텍스트를 심어 놓는다. 그러면 앱이 설치된 후 OS 레벨에서 해당 도메인 경로의 인증 텍스트와 앱 빌드시의 값이 동일한지 검증하여 소유권을 인지한다.</p> + +<p>검증된 도메인으로 생성된 딥링크 클릭 시,</p> + +<ul> + <li>앱이 설치된 경우 앱이 바로 열리고</li> + <li>앱이 설치되지 않은 경우 해당 도메인의 웹 페이지로 이동한다. 이때 웹페이지에서 마켓으로 보낼 수도 있다.</li> +</ul> + +<p>이 Universal Link와 App Link는 도메인으로 접속하기에 다음과 같이 일반적인 http, https 도메인 형식을 따른다.</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +</pre></td><td class="rouge-code"><pre>https://myapp.com/post?version=1 +</pre></td></tr></tbody></table></code></pre></div></div> + +<p><br /></p> + +<h2 id="딥링크-사용-시-참고사항">딥링크 사용 시 참고사항</h2> + +<p>1) 딥링크는 OS, 브라우저마다 다르게 동작한다. 내가 겪은 것은 다음과 같다. 좀 더 자세한 OS/브라우저별 동작 차이는 <a href="https://www.airbridge.io/blog-ko/deeplink-101-for-marketers-and-developers">이 글</a>에 표로 정리되어있다.</p> +<ul> + <li>IOS Safari에서 URI Scheme 사용시 경고창이 뜸 + <ul> + <li>앱이 설치되어있을 경우) <code class="language-plaintext highlighter-rouge">"{앱이름}"에서 여시겠습니까?</code> 라는 경고창이 뜸</li> + <li>앱이 설치되어있지 않은 경우) <code class="language-plaintext highlighter-rouge">주소가 유효하지 않기 때문에 Safari가 해당 페이지를 열 수 없습니다.</code> 경고창이 뜸.</li> + </ul> + </li> + <li>URI Scheme의 소유권을 증명할 수 없다는 문제 때문에 정해진 보안 정책</li> +</ul> + +<ol> + <li> + <p>Universal LInk을 사용할 때 다음과 같은 상황이 있을 수 있다.</p> + + <ul> + <li> + <p>주소창에 링크를 직접 붙여넣기할 경우 동작하지 않음</p> + </li> + <li> + <p>JS로 트리거 된 경우 리다이렉트가 되지 않음</p> + </li> + <li> + <p>openUrl과 같이 앱 내에서 프로그래밍 방식으로 링크를 열 경우 작동하지 않음</p> + </li> + </ul> + </li> + <li> + <p>Android 에서 <code class="language-plaintext highlighter-rouge">intent://</code> .</p> + + <ul> + <li>Android 에서 URI Scheme 대신 다음과 같은 <code class="language-plaintext highlighter-rouge">intent://</code> 을 사용할 수 있다. + <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +</pre></td><td class="rouge-code"><pre>intent://details?id={앱id}&amp;url={마켓주소}#Intent;scheme=market;package=${앱패키지};end; +</pre></td></tr></tbody></table></code></pre></div> </div> + </li> + <li>장점은 앱이 없을 경우 자동으로 마켓으로 리다이렉트 시켜준다는 것이다.</li> + </ul> + </li> +</ol> + +<p><br /></p> + +<h2 id="딥링크로-앱-여는-코드">딥링크로 앱 여는 코드</h2> + +<p>아래 코드는 프로젝트 수행 시 사용했던 코드이다.</p> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +</pre></td><td class="rouge-code"><pre><span class="c1">// 안드로이드</span> +<span class="kd">const</span> <span class="nx">openOnAndroid</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> + <span class="nb">window</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">href</span> <span class="o">=</span> <span class="s2">`</span><span class="p">${</span><span class="nx">URIscheme</span><span class="p">}</span><span class="s2">`</span> + <span class="c1">// 1초간 변화없으면 플레이스토어 열기</span> + <span class="nf">setTimeout</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span> + <span class="nb">window</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">href</span> <span class="o">=</span> <span class="s2">`market://details?id=</span><span class="p">${</span><span class="kr">package</span><span class="p">}</span><span class="s2">`</span> + <span class="p">},</span> <span class="mi">1000</span><span class="p">)</span> + <span class="k">return</span><span class="p">;</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<ul> + <li>URI scheme을 사용하여 앱 열기를 시도한다. 그 후 <code class="language-plaintext highlighter-rouge">setTimeout</code>을 등록하여 1초간 반응이 없을 시 마켓을 연다.</li> + <li><code class="language-plaintext highlighter-rouge">intent://</code> 를 지원하지 않는 상황이라 앱 여는 동작과 앱 없을 시 마켓 여는 동작을 분리했다.</li> + <li>마켓은 <code class="language-plaintext highlighter-rouge">market://</code>이라는 URI Scheme을 사용했다. + <ul> + <li>처음에는 플레이스토어 앱 링크를 사용했는데 정상적으로 앱이 열린 상황에서 다시 웹페이지로 돌아왔을때 플레이스토어로 리다이렉트 된 경우들이 종종 있었다. 백그라운드에서 앱링크가 동작하지 않고 URL로 인식하여 리다이렉트된 것이다. (브라우저, 버전 차이)</li> + <li>하지만 <code class="language-plaintext highlighter-rouge">market://</code>으로 URI Scheme을 사용하면 URL이 아니기에 리다이렉트되지 않는다. 다만, 사용자 기기에 원스토어와 같은 다른 마켓이 설치되어있으면 어떤 앱으로 마켓을 열 것인지 선택하는 창이 뜬다.</li> + </ul> + </li> +</ul> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +</pre></td><td class="rouge-code"><pre><span class="kd">const</span> <span class="nx">openOnIos</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> + <span class="nb">window</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">href</span> <span class="o">=</span> <span class="s2">`</span><span class="p">${</span><span class="nx">URIScheme</span><span class="p">}</span><span class="s2">`</span><span class="dl">''</span> + <span class="nf">setTimeout</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span> + <span class="c1">// 크롬에서는 URI 스킴으로 열어야 앱 이동 후 웹 복귀 시 마켓이 안뜸.</span> + <span class="c1">// ios 사파리에서는 유니버셜 링크로 앱스토어를 열어야 '"AppStore"에서 여시겠습니까?' 라는 알럿창이 뜨지 않음.</span> + <span class="nb">window</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">href</span> <span class="o">=</span> <span class="nx">isChrome</span> <span class="p">?</span> <span class="s2">`itms-apps://itunes.apple.com/app/</span><span class="p">${</span><span class="nx">appId</span><span class="p">}</span><span class="s2">`</span> <span class="p">:</span> <span class="s2">`https://itunes.apple.com/app/</span><span class="p">${</span><span class="nx">appId</span><span class="p">}</span><span class="s2">`</span> + <span class="p">},</span> <span class="mi">1000</span><span class="p">)</span> + <span class="p">}</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<ul> + <li>IOS에서도 URI scheme을 사용하여 앱 열기를 시도한다. 그 후 <code class="language-plaintext highlighter-rouge">setTimeout</code>을 등록하여 1초간 반응이 없을 시 마켓을 연다.</li> + <li>이때 크롬일경우랑 그 외 브라우저일경우랑 다른 주소로 마켓을 열도록 했다. + <ul> + <li>크롬에서 유니버셜 링크로 마켓을 열 경우, 정상적으로 앱이 열린 상황에서 다시 웹페이지로 돌아왔을 때 앱스토어가 띄워진 경우가 있었다. 백그라운드에서 유니버셜링크가 실행된 것으로 보인다. 따라서 크롬에서는 URI 스킴을 사용해 앱을 열도록 했다.</li> + <li>다른 브라우저에서는 유니버셜링크를 사용했다. 특히 사파리에서는 URI Scheme을 사용할 경우 <code class="language-plaintext highlighter-rouge">"AppStore"에서 여시겠습니까? </code>라는 알럿창이 뜨기에 유니버셜링크로 마켓을 여는 것이 사용자 경험에 좋다.</li> + </ul> + </li> +</ul> + +<p><br /></p> + +<h2 id="출처">출처</h2> + +<p>https://www.airbridge.io/blog-ko/deeplink-101-for-marketers-and-developers</p> + +<p>https://www.airbridge.io/blog-ko/deeplink-101-ios-safari-alert</p> + + + Fri, 19 May 2023 22:00:00 +0900 + https://seongil-shin.github.io/posts/deep-link/ + https://seongil-shin.github.io/posts/deep-link/ + + web + + issue + + + study + + web + + + + + bfcache + <h2 id="bfcache">BFCache</h2> + +<ul> + <li>사용자가 브라우저 내에서 뒤로가기/앞으로가기를 할 때 이전 페이지를 자바스크립트 heap 영역까지 전체 캐싱하여 메모리에 저장하는 것</li> + <li>HTTP 캐시는 리소스만을 캐시하고 JS 작업까지 캐시하지 않기에 BFCache보다 빠르지 않다.</li> +</ul> + +<p><br /></p> + +<h2 id="bfcache-작동을-판단할-수-있는-api">BFCache 작동을 판단할 수 있는 API</h2> + +<h3 id="pageshow-pagehide">pageshow, pagehide</h3> + +<p>pageshow</p> + +<ul> + <li>페이지가 처음 로드 될 때, bfcache에서 페이지가 복원될 때마다 load 이벤트 직후 트리거 된다.</li> + <li>event.persisted === true이면 BFCache에서 복구된 것이다.</li> +</ul> + +<p>pagehide</p> + +<ul> + <li>페이지에서 나가거나, 브라우저가 페이지를 bfcache에 저장하려고 할 때 트리거 된다.</li> + <li>event.persisted === false이면 bfcache에 저장되지 않았다는 뜻이다.</li> + <li>true라고 항상 bfcache에 저장되지 않는다. (브라우저가 저장하려고 했다는 알수 있음.)</li> +</ul> + +<h3 id="freeze--resume">freeze / resume</h3> + +<p>크롬에서 지원되는 이벤트</p> + +<p>freeze</p> + +<ul> + <li>pagehide 이벤트의 persisted 가 true일 경우, pagehide 이벤트 다음에 호출됨.</li> + <li>bfcache에 저장하려고 한 의도를 알 수 있음.</li> +</ul> + +<p>resume</p> + +<ul> + <li>BFCache로 들어가고 나올 때 호출되는 이벤트 (이외 상황에서도 호출됨 ex : CPU 사용량 최소화를 위해 백그라운드 탭이 동결된 경우)</li> + <li>bfcache에서 페이지가 복원 될 때, 정지된 배경 탭을 다시 방문할 때도 실행</li> +</ul> + +<p><br /></p> + +<h2 id="bfcache를-위한-페이지-최적화-방법">BFCache를 위한 페이지 최적화 방법</h2> + +<ol> + <li>unload 이벤트 사용하지 않기 + <ul> + <li>unload 이벤트는 페이지가 더이상 존재하지 않을 거라는 가정하여 운영됨.</li> + <li>따라서 unload 이벤트 사용시 BFCache는 사용되지 않음. 대신 pagehide 이벤트 사용하기</li> + </ul> + </li> + <li>window.opener 참조를 피하라 + <ul> + <li><code class="language-plaintext highlighter-rouge">rel="noopener"</code>를 사용하지 않고 열면 새로운 페이지는 window.opener를 가지고 있고, 이 opener를 가지고 있는 페이지는 bfcache에 넣지 않는다.</li> + <li><code class="language-plaintext highlighter-rouge">Tabnabbing</code> 보안 취약점 공격과도 연관된 부분이기 때문이다. (<a href="https://velog.io/@sisofiy626/Tabnabbing-%ED%94%BC%EC%8B%B1-%EA%B3%B5%EA%B2%A9%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0.-noopener-noreferrer">Tabnabbing 공격</a>)</li> + </ul> + </li> + <li>navigate 전에 항상 open된 connection 닫기. + <ul> + <li>setTimeout, promise 함수가 진행중일때 bfcache에 삽입하면, 작업을 일시정지하고 다시 페이지에 돌아왔을 때 진행중인 작업을 재개한다.</li> + <li>스케쥴된 작업이 단지 DOM APIdㅔ 접근하는 등 해당 페이지에만 영향을 주는 API라면 일시 정지 후 재개가 문제되지 않는다.</li> + <li>하지만 same-origin의 다른 페이지에서도 액세스할 수 있는 API의 경우 작업이 일시 중지되었을 때, 다른 탭의 코드가 실행되지 않을 수도 있기 때문에 문제가 발생한다.</li> + <li>따라서 많은 브라우저에서는 connection이 open되어있을 경우 bfcache에 해당 페이지를 넣지 않는다. + <ul> + <li>IndexedDB 트랜잭션 중일때</li> + <li>fetch(), XMLHttpRequest 중일때</li> + <li>WebSocket, WebRTC 연결이 열려있을 때.</li> + </ul> + </li> + <li>최적화를 위해선, pagehide나 freeze 이벤트에서 항상 connection을 닫고, observer를 제거하거나 연결을 끊는게 좋다. 물론 다시 돌아왔을 때 연결을 맺어줘야한다.</li> + </ul> + </li> +</ol> + +<p><br /></p> + +<h2 id="bfcache-비활성화하기">bfcache 비활성화하기</h2> + +<p>아래와 같이 Cache-Control 속성을 설정하면 된다.</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +</pre></td><td class="rouge-code"><pre>Cache-Control: no-store +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>물론 다른 캐시들도 동작하지 않는다.</p> + +<p><br /></p> + +<h2 id="bfcache-문제점">BFCache 문제점</h2> + +<ul> + <li>성능측정을 불분명하게 함 : bfcache에서 페이지가 로드되면 매우 빠르게 로드되기에 성능 측정 시 잘못된 결과값을 내게할 수 있다.</li> + <li>집계를 내는 것이 힘듬 : bfcache로 다시 방문했을때 페이지 접속 집계가 안될 수도 있음.</li> +</ul> + +<p>두 경우 모두 <code class="language-plaintext highlighter-rouge">pageshow</code> 이벤트를 사용하여 정상적으로 수행하도록 해야한다.</p> + +<p><br /></p> + +<h2 id="출처">출처</h2> + +<p>https://blog.naver.com/PostView.nhn?blogId=qls0147&amp;logNo=222157982248&amp;categoryNo=0&amp;parentCategoryNo=0&amp;viewDate=&amp;currentPage=1&amp;postListTopCurrentPage=1&amp;from=postView</p> + + Fri, 12 May 2023 22:00:00 +0900 + https://seongil-shin.github.io/posts/bfcache/ + https://seongil-shin.github.io/posts/bfcache/ + + browser + + + study + + web + + + + + beforeunload vs pagehide + <p>ios 모바일에서 새로고침시 현재 스크롤 위치를 고정하기 위해 다음과 같은 코드를 사용하였다.</p> + +<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +</pre></td><td class="rouge-code"><pre><span class="k">if </span><span class="p">(</span><span class="nx">sessionStorage</span><span class="p">.</span><span class="nx">scrollPosition</span><span class="p">)</span> <span class="p">{</span> + <span class="nb">window</span><span class="p">.</span><span class="nf">scrollTo</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nx">sessionStorage</span><span class="p">.</span><span class="nx">scrollPosition</span><span class="p">);</span> +<span class="p">}</span> + +<span class="nb">window</span><span class="p">.</span><span class="nx">onbeforeunload</span> <span class="o">=</span> <span class="nf">function </span><span class="p">()</span> <span class="p">{</span> + <span class="nx">sessionStorage</span><span class="p">.</span><span class="nx">scrollPosition</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">scrollTop</span> <span class="o">||</span> <span class="nb">document</span><span class="p">.</span><span class="nx">documentElement</span><span class="p">.</span><span class="nx">scrollTop</span><span class="p">;</span> +<span class="p">};</span> + +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>하지만 제대로 동작하지 않았는데, 찾아보니 ios에서는 <code class="language-plaintext highlighter-rouge">beforeunload</code> 이벤트를 지원해주지 않는다는 <a href="https://gosasac.tistory.com/52">글</a>을 읽었다. (글에서는 ios safari를 지원하지 않는다고 명시되어있지만, 크롬에서도 동작하지 않았다.)</p> + +<p>대신에 <code class="language-plaintext highlighter-rouge">pagehide</code>를 사용하도록 안내받았고, 실제로 동작했다. 하지만 둘의 차이점에 대해서 명확히 알고 넘어가기로 했다.</p> + +<p><br /></p> + +<h2 id="beforeunload-vs-pagehide">beforeunload vs pagehide</h2> + +<p><strong>beforeunload</strong></p> + +<ul> + <li>window, document, resource가 unload 되려할 때 발생하는 이벤트.</li> + <li>document는 계속 보이고 있고, 이벤트는 취소할 수 있다. 따라서 실제로 페이지를 떠날건지 사용자에게 물어볼 수 있다.</li> + <li>모바일에서 다음과 같은 상황에서는 발생하지 않는다. + <ol> + <li>모바일 사용자가 페이지를 방문한다</li> + <li>사용자가 다른 앱으로 전환한다.</li> + <li>사용자가 앱 관리자에서 브라우저를 닫는다.</li> + </ol> + </li> + <li><strong>back/forward cache</strong> 와 호환되지 않는다. 이벤트 후에 더이상 페이지가 존재하지 않을 것이라고 가정하기 때문이다. 따라서 브라우저는 <code class="language-plaintext highlighter-rouge">unload</code> 리스너가 있으면 bfcache에 페이지를 넣지않는다. 하지만 이는 성능에 악영향을 끼친다. 따라서 성능 악영향을 최소화하기 위해 저장되지 않은 변경 사항이 있을 때만 <code class="language-plaintext highlighter-rouge">unload</code> 리스너를 붙이는게 좋다.</li> +</ul> + +<p><strong>pagehide</strong></p> + +<ul> + <li>세션 히스토리에서 다른 페이지를 표시하는 과정에서 브라우저가 현재 페이지를 숨길때 <code class="language-plaintext highlighter-rouge">Window</code>에서 발생한다. + <ul> + <li>ex) 뒤로가기 눌렀을 때 이전 페이지가 보이기 전에 발생</li> + </ul> + </li> + <li><code class="language-plaintext highlighter-rouge">unload</code> 이벤트와 마찬가지로 모바일에서 안정적으로 동작하지 않는다.</li> + <li><strong>back/forward cache</strong>와 호환된다. 따라서 이 이벤트에 리스너를 붙이는 건 bfcache를 비활성화시키지 않는다.</li> +</ul> + +<p><br /></p> + +<h2 id="의문--왜-beforeunload-는-ios에서-동작하지-않지">의문 : 왜 <code class="language-plaintext highlighter-rouge">beforeunload</code> 는 ios에서 동작하지 않지?</h2> + +<p>beforeunload와 pagehide의 가장 큰 차이는 bfcache와 호환되지는 안되는지라는 건 알았다.</p> + +<p>하지만 다시 최초로 돌아가서 왜 <code class="language-plaintext highlighter-rouge">beforeunload</code>는 ios에서 동작하지 않는지 의문이 든다. 왜냐면 <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event#browser_compatibility">MDN 공식문서</a>에서는 safari IOS에서 버전 1부터 지원한다고 적혀있기 때문이다.</p> + +<p>1시간 정도 조사를 해본 결과 아래와 같은 단서를 찾았다.</p> + +<p><a href="https://stackoverflow.com/questions/69969722/why-doesnt-onbeforeunload-fire-in-mobile-safari-ios">단서 1</a></p> + +<ul> + <li>ios에 버그가 있다고 한다.</li> + <li>링크를 클릭해서 다른 곳으로 이동하는 경우나 history.back()을 사용하는 경우에는 동작하는데, history.back()으로 다른 도메인으로 이동할 때는 동작하지 않았다고 한다.</li> + <li>새로고침 동작에서도 버그일수도 있겠다는 생각이 들었다.</li> +</ul> + +<p><a href="https://stackoverflow.com/questions/3239834/window-onbeforeunload-not-working-on-the-ipad">단서 2</a></p> + +<ul> + <li>webkit의 known issue이다</li> + <li>애플에서 의도적으로 막았다(?)</li> +</ul> + +<p>이후 좀 더 조사를 해봤지만, ios에서는 지원을 안한다, ios의 버그이다라는 말 이외에 찾을 수 없었다. 추정만 있지 명확한 공식문서는 볼 수 없었다.</p> + +<p>향후 <code class="language-plaintext highlighter-rouge">beforeunload</code> 이벤트가 필요해졌을 떄 한번 더 찾아봐야할거 같다.</p> + + Thu, 11 May 2023 23:00:00 +0900 + https://seongil-shin.github.io/posts/pagehide/ + https://seongil-shin.github.io/posts/pagehide/ + + javascript + + + study + + javascript + + + + + useState 원리 + <h2 id="state-변경-시-어떤-일이-벌어질까">State 변경 시 어떤 일이 벌어질까?</h2> + +<p>리액트의 함수형 컴포넌트는 최초에 한번 실행이 되면서 초기값으로 설정해놓은 상태를 기억한다.</p> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +</pre></td><td class="rouge-code"><pre><span class="kd">const</span> <span class="p">[</span><span class="nx">state</span><span class="p">,</span> <span class="nx">setState</span><span class="p">]</span> <span class="o">=</span> <span class="nf">useState</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>이후 <code class="language-plaintext highlighter-rouge">setState</code>가 호출되어 상태가 변경된다면 다시 함수형 컴포넌트가 실행되고 virtual DOM을 리턴한다. 그리고 이전에 리턴했던 virtual DOM과 비교해서 state 값이 달라졌다면 달라진 부분에 해당하는 DOM만 업데이트한다.</p> + +<h2 id="usestate와-closure">useState와 Closure</h2> + +<p>클래스형 컴포넌트는 <code class="language-plaintext highlighter-rouge">render()</code> 메서드를 통해 상태 변경을 감지할 수 있다. 반면 함수형 컴포넌트는 렌더링이 발생하면 함수 자체가 다시 호출된다. 그래서 상태 관리를 위해선 함수가 다시 호출되었을 때 이전 상태를 기억하고 있어야한다.</p> + +<p>useState는 Closure를 통해 이 문제를 해결한다.</p> + +<blockquote> + <p>Closure는 내부함수에서 상위 함수 스코프의 변수에 접근할 수 있는 개념</p> +</blockquote> + +<p>다음은 클로저의 특징을 이용한 <code class="language-plaintext highlighter-rouge">MyReact</code> 모듈이다</p> + +<div class="language-react highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +</pre></td><td class="rouge-code"><pre><span class="kd">const</span> <span class="nx">MyReact</span> <span class="o">=</span> <span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span> + <span class="kd">let</span> <span class="nx">_val</span> <span class="c1">// hold our state in module scope</span> + <span class="k">return</span> <span class="p">{</span> + <span class="nf">render</span><span class="p">(</span><span class="nx">Component</span><span class="p">)</span> <span class="p">{</span> + <span class="kd">const</span> <span class="nx">Comp</span> <span class="o">=</span> <span class="nc">Component</span><span class="p">()</span> + <span class="nx">Comp</span><span class="p">.</span><span class="nf">render</span><span class="p">()</span> + <span class="k">return</span> <span class="nx">Comp</span> + <span class="p">},</span> + <span class="nf">useState</span><span class="p">(</span><span class="nx">initialValue</span><span class="p">)</span> <span class="p">{</span> + <span class="nx">_val</span> <span class="o">=</span> <span class="nx">_val</span> <span class="o">||</span> <span class="nx">initialValue</span> + <span class="kd">function</span> <span class="nf">setState</span><span class="p">(</span><span class="nx">newVal</span><span class="p">)</span> <span class="p">{</span> + <span class="nx">_val</span> <span class="o">=</span> <span class="nx">newVal</span> + <span class="p">}</span> + <span class="k">return</span> <span class="p">[</span><span class="nx">_val</span><span class="p">,</span> <span class="nx">setState</span><span class="p">]</span> + <span class="p">}</span> + <span class="p">}</span> +<span class="p">})()</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<ul> + <li>MyReact는 익명함수로부터 두개의 Closure를 반환받아 저장한다.</li> + <li><code class="language-plaintext highlighter-rouge">_val</code>은 익명함수 scope 안에서 정의된다. 하지만 반환되는 함수의 Closure에서 사용되기에 익명함수가 종료되더라도 메모리에 유지된다.</li> +</ul> + +<p>이 <code class="language-plaintext highlighter-rouge">MyReact</code> 함수는 다음과 같이 사용할 수 있다.</p> + +<div class="language-react highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +</pre></td><td class="rouge-code"><pre><span class="kd">function</span> <span class="nf">Counter</span><span class="p">()</span> <span class="p">{</span> + <span class="kd">const</span> <span class="p">[</span><span class="nx">count</span><span class="p">,</span> <span class="nx">setCount</span><span class="p">]</span> <span class="o">=</span> <span class="nx">MyReact</span><span class="p">.</span><span class="nf">useState</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> + <span class="k">return</span> <span class="p">{</span> + <span class="na">click</span><span class="p">:</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="nf">setCount</span><span class="p">(</span><span class="nx">count</span> <span class="o">+</span> <span class="mi">1</span><span class="p">),</span> + <span class="na">render</span><span class="p">:</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">render:</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span> <span class="nx">count</span> <span class="p">})</span> + <span class="p">}</span> +<span class="p">}</span> +<span class="kd">let</span> <span class="nx">App</span> +<span class="nx">App</span> <span class="o">=</span> <span class="nx">MyReact</span><span class="p">.</span><span class="nf">render</span><span class="p">(</span><span class="nx">Counter</span><span class="p">)</span> <span class="c1">// render: { count: 0 }</span> +<span class="nx">App</span><span class="p">.</span><span class="nf">click</span><span class="p">()</span> +<span class="nx">App</span> <span class="o">=</span> <span class="nx">MyReact</span><span class="p">.</span><span class="nf">render</span><span class="p">(</span><span class="nx">Counter</span><span class="p">)</span> <span class="c1">// render: { count: 1 }</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>Counter가 일종의 컴포넌트라고 생각해보자.</p> + +<ul> + <li>MyReact의 render를 이용해 첫 Counter를 렌더링한다.</li> + <li>렌더링하면 MyReact의 <code class="language-plaintext highlighter-rouge">useState</code>가 실행된다. <code class="language-plaintext highlighter-rouge">_val</code>에 아무것도 없기에 초기값으로 설정된다.</li> + <li><code class="language-plaintext highlighter-rouge">useState</code>는 반환값으로 상태와 setter를 낸다. 그 후 Counter는 <code class="language-plaintext highlighter-rouge">click</code>과 <code class="language-plaintext highlighter-rouge">render</code>가 들어있는 객체를 반환해 App에 저장한다.</li> + <li>App.click을 통해 상태를 업데이트한다. 이러면 리렌더링이 되는데, 현재 MyReact에는 이러한 로직이 구현되어있지 않다. 실제 React에서는 setter가 실행될 경우 컴포넌트를 리렌더링한다. 여기서는 <code class="language-plaintext highlighter-rouge">click</code> 이후 <code class="language-plaintext highlighter-rouge">render</code>를 실행함으로써 리렌더링을 보여준다.</li> + <li>Counter가 다시 실행되는데 이번엔 메모리에 저장된 <code class="language-plaintext highlighter-rouge">_val</code>에 값이 있기에 초기값을 설정하는 대신 업데이트된 값을 계속 유지한다.</li> + <li>새롭게 반환된 객체는 <code class="language-plaintext highlighter-rouge">count</code>가 1인 상태로 동작한다.</li> +</ul> + +<p>하지만 위와 같은 useState엔 문제점이 있다. 바로 여러 useState를 사용할 경우 다 같은 <code class="language-plaintext highlighter-rouge">_val</code>을 바라본다는 점이다. 이를 해결하기 위해선 다음과 같이 할 수 있다.</p> + +<div class="language-react highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +</pre></td><td class="rouge-code"><pre><span class="kd">let</span> <span class="nx">state</span> <span class="o">=</span> <span class="p">[];</span> +<span class="kd">let</span> <span class="nx">setters</span> <span class="o">=</span> <span class="p">[];</span> +<span class="kd">let</span> <span class="nx">cursor</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> +<span class="kd">let</span> <span class="nx">firstrun</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span> + +<span class="kd">const</span> <span class="nx">createSetter</span> <span class="o">=</span> <span class="p">(</span><span class="nx">cursor</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> + <span class="k">return </span><span class="p">(</span><span class="nx">newValue</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> + <span class="nx">state</span><span class="p">[</span><span class="nx">cursor</span><span class="p">]</span> <span class="o">=</span> <span class="nx">newValue</span><span class="p">;</span> + <span class="p">};</span> +<span class="p">};</span> + +<span class="kd">const</span> <span class="nx">useState</span> <span class="o">=</span> <span class="p">(</span><span class="nx">initialValue</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> + <span class="k">if </span><span class="p">(</span><span class="nx">firstrun</span><span class="p">)</span> <span class="p">{</span> + <span class="nx">state</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span><span class="nx">initialValue</span><span class="p">);</span> + <span class="nx">setters</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span><span class="nf">createSetter</span><span class="p">(</span><span class="nx">cursor</span><span class="p">));</span> + <span class="nx">firstrun</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span> + <span class="p">}</span> + + <span class="kd">const</span> <span class="nx">resState</span> <span class="o">=</span> <span class="nx">state</span><span class="p">[</span><span class="nx">cursor</span><span class="p">];</span> + <span class="kd">const</span> <span class="nx">resSetter</span> <span class="o">=</span> <span class="nx">setters</span><span class="p">[</span><span class="nx">cursor</span><span class="p">];</span> + <span class="nx">cursor</span><span class="o">++</span><span class="p">;</span> + + <span class="k">return</span> <span class="p">[</span><span class="nx">resState</span><span class="p">,</span> <span class="nx">resSetter</span><span class="p">];</span> +<span class="p">};</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<ul> + <li>useState는 초깃값을 받는다. 그리고 최초 실행일 경우 초깃값을 <code class="language-plaintext highlighter-rouge">state</code> 배열에 삽입한다. 마찬가지로 <code class="language-plaintext highlighter-rouge">setter</code>도 추가한다. 이때 <code class="language-plaintext highlighter-rouge">state</code> 배열과 <code class="language-plaintext highlighter-rouge">setters</code> 배열은 useState 함수 외부에 위치한다.</li> + <li>추가된 위치의 state와 setter를 반환하면, state 값은 useState 함수 외부에 위치하므로 closure가 적용되어 useState가 종료되더라도 값이 유지된다.</li> +</ul> + +<p><br /></p> + +<h2 id="hook-규칙">Hook 규칙</h2> + +<ol> + <li>함수형 컴포넌트의 최상위에서만 hook을 호출해야한다. + <ul> + <li>state는 컴포넌트의 실행 순서대로 배열에 저장된다. 따라서 반복문, 조건문 혹은 중첩함수에서 hook을 호출하면, 컴포넌트의 실행 순서가 달라질 수 있다.</li> + </ul> + </li> + <li>오직 React 함수 내에서 hook을 호출해야한다. + <ul> + <li>Hook을 일반적인 JS 함수에서 호출하면 안되고, 함수형 컴포넌트 또는 커스텀 훅 내에서만 호출할 수 있다.</li> + </ul> + </li> +</ol> + +<p><br /></p> + +<h2 id="usestate-모듈-분석">useState 모듈 분석</h2> + +<div class="language-react highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +</pre></td><td class="rouge-code"><pre><span class="k">export</span> <span class="kd">function</span> <span class="nf">useState</span><span class="o">&lt;</span><span class="nx">S</span><span class="o">&gt;</span><span class="p">(</span> + <span class="nx">initialState</span><span class="p">:</span> <span class="p">(()</span> <span class="o">=&gt;</span> <span class="nx">S</span><span class="p">)</span> <span class="o">|</span> <span class="nx">S</span><span class="p">,</span> +<span class="p">):</span> <span class="p">[</span><span class="nx">S</span><span class="p">,</span> <span class="nx">Dispatch</span><span class="o">&lt;</span><span class="nx">BasicStateAction</span><span class="o">&lt;</span><span class="nx">S</span><span class="o">&gt;&gt;</span><span class="p">]</span> <span class="p">{</span> + <span class="kd">const</span> <span class="nx">dispatcher</span> <span class="o">=</span> <span class="nf">resolveDispatcher</span><span class="p">();</span> + <span class="k">return</span> <span class="nx">dispatcher</span><span class="p">.</span><span class="nf">useState</span><span class="p">(</span><span class="nx">initialState</span><span class="p">);</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<div class="language-react highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +</pre></td><td class="rouge-code"><pre><span class="kd">function</span> <span class="nf">resolveDispatcher</span><span class="p">()</span> <span class="p">{</span> + <span class="kd">const</span> <span class="nx">dispatcher</span> <span class="o">=</span> <span class="nx">ReactCurrentDispatcher</span><span class="p">.</span><span class="nx">current</span><span class="p">;</span> + + <span class="k">if </span><span class="p">(</span><span class="nx">__DEV__</span><span class="p">)</span> <span class="p">{</span> + <span class="k">if </span><span class="p">(</span><span class="nx">dispatcher</span> <span class="o">===</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span> + <span class="nx">console</span><span class="p">.</span><span class="nf">error</span><span class="p">(</span><span class="dl">'</span><span class="s1">Some error msg...</span><span class="dl">'</span><span class="p">);</span> + <span class="p">}</span> + <span class="p">}</span> + + <span class="k">return </span><span class="p">((</span><span class="nx">dispatcher</span><span class="p">:</span> <span class="nx">any</span><span class="p">):</span> <span class="nx">Dispatcher</span><span class="p">);</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<div class="language-react highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +</pre></td><td class="rouge-code"><pre><span class="kd">const</span> <span class="nx">ReactCurrentDispatcher</span> <span class="o">=</span> <span class="p">{</span> + <span class="cm">/** + * @internal + * @type {ReactComponent} + */</span> + <span class="na">current</span><span class="p">:</span> <span class="p">(</span><span class="na">null</span><span class="p">:</span> <span class="kc">null</span> <span class="o">|</span> <span class="nx">Dispatcher</span><span class="p">),</span> +<span class="p">};</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p><code class="language-plaintext highlighter-rouge">ReactCurrentDispatcher</code>은 전역에 선언된 객체의 프로퍼티이다. useState 리턴 값의 출처가 전역에서 온다는 것이다. +리액트가 실제로 클로저를 활용해 함수 외부의 값에 접근하는 사실을 알 수 있다.</p> + +<p><br /></p> + +<h2 id="usestate-배치-프로세스">useState 배치 프로세스</h2> + +<p>다음 예제에서 <code class="language-plaintext highlighter-rouge">increase1</code>의 결과는 <code class="language-plaintext highlighter-rouge">1</code>이고 <code class="language-plaintext highlighter-rouge">increase2</code>의 결과는 <code class="language-plaintext highlighter-rouge">3</code>이다.</p> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +</pre></td><td class="rouge-code"><pre><span class="kd">const</span> <span class="nx">Counter</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> + <span class="kd">const</span> <span class="p">[</span><span class="nx">count</span><span class="p">,</span> <span class="nx">setCount</span><span class="p">]</span> <span class="o">=</span> <span class="nf">useState</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span> + + <span class="kd">const</span> <span class="nx">increase1</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> + <span class="nf">setCount</span><span class="p">(</span><span class="nx">count</span> <span class="o">+</span> <span class="mi">1</span><span class="p">);</span> + <span class="nf">setCount</span><span class="p">(</span><span class="nx">count</span> <span class="o">+</span> <span class="mi">1</span><span class="p">);</span> + <span class="nf">setCount</span><span class="p">(</span><span class="nx">count</span> <span class="o">+</span> <span class="mi">1</span><span class="p">);</span> + <span class="p">}</span> + + <span class="kd">const</span> <span class="nx">increase2</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> + <span class="nf">setCount</span><span class="p">((</span><span class="nx">count</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">count</span> <span class="o">+</span> <span class="mi">1</span><span class="p">);</span> + <span class="nf">setCount</span><span class="p">((</span><span class="nx">count</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">count</span> <span class="o">+</span> <span class="mi">1</span><span class="p">);</span> + <span class="nf">setCount</span><span class="p">((</span><span class="nx">count</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">count</span> <span class="o">+</span> <span class="mi">1</span><span class="p">);</span> + <span class="p">}</span> +<span class="p">}</span> + +<span class="k">export</span> <span class="k">default</span> <span class="nx">Counter</span><span class="p">;</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>그 이유는 새로운 상태가 바로 이전의 상태를 통해 계산되어야하면 함수를 인자로 넣어야하기 때문인데, 리액트는 퍼포먼스 향상을 위해 특별한 배치 프로세스를 사용하기 때문이다. 여러 setState 업데이트를 한 번에 묶어서 처리한 후 마지막 값을 통해 state를 결정한다.</p> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +</pre></td><td class="rouge-code"><pre><span class="p">{</span> + <span class="nl">memoizedState</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> <span class="c1">// first hook</span> + <span class="nx">baseState</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> + <span class="nx">queue</span><span class="p">:</span> <span class="p">{</span> <span class="cm">/* ... */</span> <span class="p">},</span> + <span class="nx">baseUpdate</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> + <span class="nx">next</span><span class="p">:</span> <span class="p">{</span> <span class="c1">// second hook</span> + <span class="nl">memoizedState</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> + <span class="nx">baseState</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> + <span class="nx">queue</span><span class="p">:</span> <span class="p">{</span> <span class="cm">/* ... */</span> <span class="p">},</span> + <span class="nx">baseUpdate</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> + <span class="nx">next</span><span class="p">:</span> <span class="p">{</span> <span class="c1">// third hook</span> + <span class="nl">memoizedState</span><span class="p">:</span> <span class="p">{</span> + <span class="na">tag</span><span class="p">:</span> <span class="mi">192</span><span class="p">,</span> + <span class="na">create</span><span class="p">:</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{},</span> + <span class="na">destory</span><span class="p">:</span> <span class="kc">undefined</span><span class="p">,</span> + <span class="na">deps</span><span class="p">:</span> <span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="kc">false</span><span class="p">],</span> + <span class="na">next</span><span class="p">:</span> <span class="p">{</span> <span class="cm">/* ... */</span> <span class="p">}</span> + <span class="p">},</span> + <span class="nx">baseState</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> + <span class="nx">queue</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> + <span class="nx">baseUpdate</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> + <span class="nx">next</span><span class="p">:</span> <span class="kc">null</span> + <span class="p">}</span> + <span class="p">}</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<ul> + <li>실제 hook을 변수에 할당하여 출력했을 때 나타나는 결과</li> + <li>next는 연결리스트의 일종으로 한 컴포넌트 안에서 여러번 실행되는 hook들을 연결해주는 역할</li> +</ul> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +</pre></td><td class="rouge-code"><pre><span class="p">{</span> + <span class="nl">memoizedState</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> + <span class="nx">baseState</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> + <span class="nx">queue</span><span class="p">:</span> <span class="p">{</span> + <span class="nl">last</span><span class="p">:</span> <span class="p">{</span> + <span class="na">expirationTime</span><span class="p">:</span> <span class="mi">1073741823</span><span class="p">,</span> + <span class="na">suspenseConfig</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> + <span class="na">action</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="c1">// setCount를 통해 설정한 값</span> + <span class="na">eagerReducer</span><span class="p">:</span> <span class="nf">basicStateReducer</span><span class="p">(</span><span class="nx">state</span><span class="p">,</span> <span class="nx">action</span><span class="p">),</span> + <span class="na">eagerState</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="c1">// 상태 업데이트를 마치고 실제 렌더링되는 값</span> + <span class="na">next</span><span class="p">:</span> <span class="p">{</span> <span class="cm">/* ... */</span> <span class="p">},</span> + <span class="na">priority</span><span class="p">:</span> <span class="mi">98</span> + <span class="p">},</span> + <span class="nx">dispatch</span><span class="p">:</span> <span class="nx">dispatchAction</span><span class="p">.</span><span class="nf">bind</span><span class="p">(</span><span class="nx">bull</span><span class="p">,</span> <span class="nx">currenctlyRenderingFiber$1</span><span class="p">,</span> <span class="nx">queue</span><span class="p">),</span> + <span class="nx">lastRenderedReducer</span><span class="p">:</span> <span class="nf">basicStateReducer</span><span class="p">(</span><span class="nx">state</span><span class="p">,</span> <span class="nx">action</span><span class="p">),</span> + <span class="nx">lastRenderedState</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> + <span class="p">},</span> + <span class="nx">baseUpdate</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span> + <span class="nx">next</span><span class="p">:</span> <span class="kc">null</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>리액트의 <code class="language-plaintext highlighter-rouge">배치 프로세스</code>는 이렇게 묶인 hook들을 한 번에 처리한 뒤 last를 생성한다. 여기서 최종 반환될 <code class="language-plaintext highlighter-rouge">eagerState</code>를 계산하는 함수가 Reducer이다.</p> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +</pre></td><td class="rouge-code"><pre><span class="kd">function</span> <span class="nf">basicStateReducer</span><span class="p">(</span><span class="nx">state</span><span class="p">,</span> <span class="nx">action</span><span class="p">)</span> <span class="p">{</span> + <span class="k">return</span> <span class="k">typeof</span> <span class="nx">action</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">function</span><span class="dl">'</span> <span class="p">?</span> <span class="nf">action</span><span class="p">(</span><span class="nx">state</span><span class="p">)</span> <span class="p">:</span> <span class="nx">action</span><span class="p">;</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>이 Reducer에 넘기는 action 타입이 함수일 때 이전 상태를 인자로 받는다. 그래서 기존 상태를 기반으로 새로운 상태를 업데이트할 수 있게 된다.</p> + +<p><br /></p> + +<h2 id="출처">출처</h2> + +<p>https://thinkforthink.tistory.com/339 +https://seokzin.tistory.com/entry/React-useState%EC%9D%98-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC%EC%99%80-%ED%81%B4%EB%A1%9C%EC%A0%80 +https://hengxi.tistory.com/22</p> + + Sat, 06 May 2023 18:00:00 +0900 + https://seongil-shin.github.io/posts/react-state/ + https://seongil-shin.github.io/posts/react-state/ + + react + + + study + + react + + + + + typescript 조건부타입(extends) + <p>타입 스크립트 2.8부터 다음과 같은 <code class="language-plaintext highlighter-rouge">조건부 타입</code>을 사용할 수 있다.</p> + +<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +</pre></td><td class="rouge-code"><pre><span class="nx">T</span> <span class="kd">extends</span> <span class="nx">U</span> <span class="p">?</span> <span class="nx">X</span> <span class="p">:</span> <span class="nx">Y</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>뜻은 T가 U의 서브타입이거나 같은 타입이면 X, 아니면 Y 타입을 할당한다는 것이다.</p> + +<h2 id="t가-유니온-타입일-경우">T가 유니온 타입일 경우</h2> + +<p>다음과 같이 T가 유니온 타입일 경우가 있다.</p> + +<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +</pre></td><td class="rouge-code"><pre><span class="kd">type</span> <span class="nx">T</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">A</span><span class="dl">"</span> <span class="o">|</span> <span class="dl">"</span><span class="s2">B</span><span class="dl">"</span> <span class="o">|</span> <span class="dl">"</span><span class="s2">C</span><span class="dl">"</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>이러한 경우엔 분배법칙이 성립된다.</p> + +<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +</pre></td><td class="rouge-code"><pre><span class="p">{</span><span class="dl">"</span><span class="s2">A</span><span class="dl">"</span> <span class="kd">extends</span> <span class="nx">U</span> <span class="p">?</span> <span class="nx">X</span> <span class="p">:</span> <span class="nx">Y</span><span class="p">}</span> <span class="o">|</span> <span class="p">{</span><span class="dl">"</span><span class="s2">B</span><span class="dl">"</span> <span class="kd">extends</span> <span class="nx">U</span> <span class="p">?</span> <span class="nx">X</span> <span class="p">:</span> <span class="nx">Y</span><span class="p">}</span> <span class="o">|</span> <span class="p">{</span><span class="dl">"</span><span class="s2">C</span><span class="dl">"</span> <span class="kd">extends</span> <span class="nx">U</span> <span class="p">?</span> <span class="nx">X</span> <span class="p">:</span> <span class="nx">Y</span><span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p><br /></p> + +<h2 id="출처">출처</h2> + +<p>https://mugglim.tistory.com/13</p> + + Mon, 24 Apr 2023 22:30:00 +0900 + https://seongil-shin.github.io/posts/typescript-extends/ + https://seongil-shin.github.io/posts/typescript-extends/ + + typescript + + + study + + typescript + + + + + Javscript 메모리 누수 방지 및 성능 개선 + <h1 id="javscript-메모리-관리-이해하기">Javscript 메모리 관리 이해하기</h1> + +<h2 id="garbage-collector">Garbage collector</h2> + +<p>자바스크립트 엔진은 더이상 사용하지 않을 메모리를 놓아주기 위해 <code class="language-plaintext highlighter-rouge">garbage collector</code>를 사용한다. <code class="language-plaintext highlighter-rouge">garbage collector(GC)</code>는 앱에서 더이상 사용하지 않을 객체를 찾아내고 삭제한다. 따라서 GC는 앱의 object와 변수를 계속 모니터링하고 어떤 것이 여전히 referenced 되고 있는지 트랙킹한다. 그러다 Object가 더이상 쓰이지 않으면 마킹하고 삭제하여 메모리를 놓아준다.</p> + +<p>GC가 사용하는 이 기법은 <code class="language-plaintext highlighter-rouge">mark and sweep</code>이다.</p> + +<ol> + <li>아직 사용중인 모든 Object를 마킹함 (mark)</li> + <li>heap을 검사하고, 마킹되지 않은 object는 삭제함. (sweep)</li> +</ol> + +<p>이 작업은 주기적으로 수행되며, heap 의 사이즈가 작더라도 수행된다.</p> + +<h2 id="stack-vs-heap">Stack vs Heap</h2> + +<p>자바스크립트의 메모리를 얘기하면 <strong>stack</strong>과 <strong>heap</strong>이 주역이다.</p> + +<p>stack은 함수 실행 동안 필요한 데이터를 저장한다. 빠르고 효율적이지만 공간은 한정적이다. 함수가 실행되면 자바스크립트 엔진이 그 함수의 변수와 파라미터를 스택에 push하고, 함수가 return 되면 pop 한다. 이렇게 stack은 빠르고 효율적으로 메모리를 관리한다.</p> + +<p>반면에 heap은 앱의 생명주기 동안 데이터를 저장하는데 사용한다. stack에 비해 다소 느리고 덜 정리되어있지만 공간은 크다. Heap은 object, array 등 복잡한 데이터 구조를 여러번 접근하기 위해 사용된다.</p> + +<p><br /></p> + +<h1 id="common-causes-of-memory-leaks">Common causes of Memory Leaks</h1> + +<h2 id="순환참조circular-reference">순환참조(Circular reference)</h2> + +<p>가장 흔한 케이스 중 하나다. 2개 이상의 object가 서로를 참조하고 있을 때 발생한다.</p> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +</pre></td><td class="rouge-code"><pre><span class="kd">let</span> <span class="nx">object1</span> <span class="o">=</span> <span class="p">{};</span> +<span class="kd">let</span> <span class="nx">object2</span> <span class="o">=</span> <span class="p">{};</span> + +<span class="c1">// create a circular reference between object1 and object2</span> +<span class="nx">object1</span><span class="p">.</span><span class="nx">next</span> <span class="o">=</span> <span class="nx">object2</span><span class="p">;</span> +<span class="nx">object2</span><span class="p">.</span><span class="nx">prev</span> <span class="o">=</span> <span class="nx">object1</span><span class="p">;</span> + +<span class="c1">// do something with object1 and object2</span> +<span class="c1">// ...</span> + +<span class="c1">// set object1 and object2 to null to break the circular reference</span> +<span class="nx">object1</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span> +<span class="nx">object2</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>위 예제에서는 마지막에 <code class="language-plaintext highlighter-rouge">object1</code>과 <code class="language-plaintext highlighter-rouge">object2</code>를 null로 설정하여 순환 참조를 깨려고 했지만, <code class="language-plaintext highlighter-rouge">{next: refenceToObject2}</code>, <code class="language-plaintext highlighter-rouge">{ prev: refenceToObject1}</code>는 각각 메모리에 남아서 서로를 참조하고 있다. 즉 null로 할당하는 것은 <code class="language-plaintext highlighter-rouge">object1</code>, <code class="language-plaintext highlighter-rouge">object2</code>에 할당된 reference를 지운것 뿐이고, 순환참조된 obejct들 자체는 메모리에 계속 남아있다. 따라서 GC는 순환 참조를 꺨 수 없다. (하지만 최신 자바스크립트 엔진에서는 GC가 발전하여 메모리 회수를 한다.)</p> + +<p>이러한 경우에는 <code class="language-plaintext highlighter-rouge">delete</code> 키워드를 사용한 <code class="language-plaintext highlighter-rouge">manual memory management</code>가 필요하다. <code class="language-plaintext highlighter-rouge">delete</code>를 사용하여 순환참조를 만들어내는 property를 삭제하는 것이다.</p> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +</pre></td><td class="rouge-code"><pre><span class="k">delete</span> <span class="nx">object1</span><span class="p">.</span><span class="nx">next</span><span class="p">;</span> +<span class="k">delete</span> <span class="nx">object2</span><span class="p">.</span><span class="nx">prev</span><span class="p">;</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>또다른 방법은 <code class="language-plaintext highlighter-rouge">WeakMap</code>, <code class="language-plaintext highlighter-rouge">WeakSet</code>을 사용하는 방법이다. 이 두개는 object와 variable에 대한 약한 참조를 만들 수 있게 한다.</p> + +<h2 id="event-listeners">Event Listeners</h2> + +<p>엘리먼트에 Event Listener를 붙이면, listener function의 reference가 생긴다. 그리고 이 reference는 GC가 메모리를 풀어주는걸 막는다. 이는 앨리먼트가 더이상 필요없어져도 listener function이 제거되지 않으므로 memory leak의 원인이 된다.</p> + +<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +</pre></td><td class="rouge-code"><pre><span class="kd">let</span> <span class="nx">button</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">getElementById</span><span class="p">(</span><span class="dl">"</span><span class="s2">my-button</span><span class="dl">"</span><span class="p">);</span> + +<span class="c1">// attach an event listener to the button</span> +<span class="nx">button</span><span class="p">.</span><span class="nf">addEventListener</span><span class="p">(</span><span class="dl">"</span><span class="s2">click</span><span class="dl">"</span><span class="p">,</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span> + <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">Button was clicked!</span><span class="dl">"</span><span class="p">);</span> +<span class="p">});</span> + +<span class="c1">// do something with the button</span> +<span class="c1">// ...</span> + +<span class="c1">// remove the button from the DOM</span> +<span class="nx">button</span><span class="p">.</span><span class="nx">parentNode</span><span class="p">.</span><span class="nf">removeChild</span><span class="p">(</span><span class="nx">button</span><span class="p">);</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>이를 해결하기 위해선 필요없어진 event listener는 삭제해야한다.</p> + +<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +</pre></td><td class="rouge-code"><pre><span class="nx">button</span><span class="p">.</span><span class="nf">removeEventListener</span><span class="p">(</span><span class="dl">"</span><span class="s2">click</span><span class="dl">"</span><span class="p">,</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span> + <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">Button was clicked!</span><span class="dl">"</span><span class="p">);</span> +<span class="p">});</span> + +<span class="c1">// 전부 삭제</span> +<span class="nx">button</span><span class="p">.</span><span class="nf">removeAllListeners</span><span class="p">();</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<h2 id="global-variables">Global variables</h2> + +<p>전역 변수를 생성하면, 그 변수는 모든 코드에서 접근이 가능하다. 따라서 더 이상 필요한지 아닌지 판단하기가 어렵다. 이는 memory leak로 이어진다.</p> + +<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +</pre></td><td class="rouge-code"><pre><span class="c1">// create a global variable</span> +<span class="kd">let</span> <span class="nx">myData</span> <span class="o">=</span> <span class="p">{</span> + <span class="na">largeArray</span><span class="p">:</span> <span class="k">new</span> <span class="nc">Array</span><span class="p">(</span><span class="mi">1000000</span><span class="p">).</span><span class="nf">fill</span><span class="p">(</span><span class="dl">"</span><span class="s2">some data</span><span class="dl">"</span><span class="p">),</span> + <span class="na">id</span><span class="p">:</span> <span class="mi">1</span> +<span class="p">};</span> + +<span class="c1">// do something with myData</span> +<span class="c1">// ...</span> + +<span class="c1">// set myData to null to break the reference</span> +<span class="nx">myData</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>위 코드의 마지막에 <code class="language-plaintext highlighter-rouge">myData</code>에 <code class="language-plaintext highlighter-rouge">null</code>을 할당하였지만, 전역 변수이기에 모든 코드에서 접근 가능하다. 따라서 더이상 필요한지 아닌지 판단하기 어렵고, 이는 memory leak로 이어진다.</p> + +<p>이를 해결하기 위해선 <code class="language-plaintext highlighter-rouge">Function Scoping</code>을 사용해야한다. 함수를 생성하고 그 안에서 지역변수로 변수를 생성하는 방법이다. 그러면 그 변수는 오직 그 함수에서만 접근 가능하고, 더이상 필요한지 아닌지 판단하기 쉽다. 즉 필요없어지면 GC에 의해 청소된다.</p> + +<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +</pre></td><td class="rouge-code"><pre><span class="kd">function</span> <span class="nf">myFunction</span><span class="p">()</span> <span class="p">{</span> + <span class="kd">let</span> <span class="nx">myData</span> <span class="o">=</span> <span class="p">{</span> + <span class="na">largeArray</span><span class="p">:</span> <span class="k">new</span> <span class="nc">Array</span><span class="p">(</span><span class="mi">1000000</span><span class="p">).</span><span class="nf">fill</span><span class="p">(</span><span class="dl">"</span><span class="s2">some data</span><span class="dl">"</span><span class="p">),</span> + <span class="na">id</span><span class="p">:</span> <span class="mi">1</span> + <span class="p">};</span> + + <span class="c1">// do something with myData</span> + <span class="c1">// ...</span> +<span class="p">}</span> +<span class="nf">myFunction</span><span class="p">();</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>다른 방법은 <code class="language-plaintext highlighter-rouge">var</code> 대신에 <code class="language-plaintext highlighter-rouge">let</code>, <code class="language-plaintext highlighter-rouge">const</code>를 사용하는 방법이다. <code class="language-plaintext highlighter-rouge">let</code>, <code class="language-plaintext highlighter-rouge">const</code>는 block-scoped이므로 선언된 block 외에선 사용이 불가능하니 GC에 의해 제거될수 있다.</p> + +<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +</pre></td><td class="rouge-code"><pre><span class="p">{</span> + <span class="kd">let</span> <span class="nx">myData</span> <span class="o">=</span> <span class="p">{</span> + <span class="na">largeArray</span><span class="p">:</span> <span class="k">new</span> <span class="nc">Array</span><span class="p">(</span><span class="mi">1000000</span><span class="p">).</span><span class="nf">fill</span><span class="p">(</span><span class="dl">"</span><span class="s2">some data</span><span class="dl">"</span><span class="p">),</span> + <span class="na">id</span><span class="p">:</span> <span class="mi">1</span> + <span class="p">};</span> + + <span class="c1">// do something with myData</span> + <span class="c1">// ...</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<h2 id="그외-이유들">그외 이유들</h2> + +<ul> + <li>DOM 노드 참조 + <ul> + <li>DOM 노드를 메모리에 저장하면, 저장한 노드의 자식 노드까지 모두 참조가 유지되어 실제 DOM에는 없는데도 메모리 상에선 유지될 수 있다.</li> + </ul> + </li> + <li>클로저 : 사용하지 않는 클로저는 함수에 의해 계속 참조되어있는 상태이므로, 메모리에 계속 남아있어 메모리 누수의 원인이 될 수 있다.</li> +</ul> + +<p><br /></p> + +<h1 id="best-practices-for-manual-memory-management">Best Practices for Manual Memory Management</h1> + +<h2 id="using-weak-references">Using weak references</h2> + +<p><code class="language-plaintext highlighter-rouge">WeakMap</code>, <code class="language-plaintext highlighter-rouge">WeakSet</code> 을 사용하여 object와 variable에 대한 weak reference를 만들 수 있다. <strong>Weak reference</strong>는 일반적인 reference와는 달리 GC가 object에 의해 사용되는 메모리를 free up 하는 걸 막지 않는다. 이러한 특성은 <code class="language-plaintext highlighter-rouge">WeakMap</code>과 <code class="language-plaintext highlighter-rouge">WeakSet</code>을 Circular reference를 해결하는데 사용할 수 있도록 한다.</p> + +<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +</pre></td><td class="rouge-code"><pre><span class="kd">let</span> <span class="nx">object1</span> <span class="o">=</span> <span class="p">{};</span> +<span class="kd">let</span> <span class="nx">object2</span> <span class="o">=</span> <span class="p">{};</span> + +<span class="c1">// create a WeakMap</span> +<span class="kd">let</span> <span class="nx">weakMap</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">WeakMap</span><span class="p">();</span> + +<span class="c1">// create a circular reference by adding object1 to the WeakMap</span> +<span class="c1">// and then adding the WeakMap to object1</span> +<span class="nx">weakMap</span><span class="p">.</span><span class="nf">set</span><span class="p">(</span><span class="nx">object1</span><span class="p">,</span> <span class="dl">"</span><span class="s2">some data</span><span class="dl">"</span><span class="p">);</span> +<span class="nx">object1</span><span class="p">.</span><span class="nx">weakMap</span> <span class="o">=</span> <span class="nx">weakMap</span><span class="p">;</span> + +<span class="c1">// create a WeakSet and add object2 to it</span> +<span class="kd">let</span> <span class="nx">weakSet</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">WeakSet</span><span class="p">();</span> +<span class="nx">weakSet</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="nx">object2</span><span class="p">);</span> + +<span class="c1">// in this case, the garbage collector will be able to free up the memory</span> +<span class="c1">// used by object1 and object2, since the references to them are weak</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>위에서 순환참조 때 봤던 예시와 비슷하게 서로 참조하고 있더라도, 이번엔 WeakMap을 사용하여 순환참조가 일어나도 메모리 free up을 막지 않는다.</p> + +<p><code class="language-plaintext highlighter-rouge">WeakMap</code>은 약한 참조이므로, key object가 <code class="language-plaintext highlighter-rouge">WekMap</code>에 존재하더라도 GC에 의해 메모리를 회수할 수 있도록 해준다. 다른 말로, <code class="language-plaintext highlighter-rouge">WeakMap</code>은 key object가 다른 코드에서 접근이 불가능해지면, GC가 key object가 차지하고 있던 메모리를 회수하는 걸 막지않는다. 다음 예제에서 <code class="language-plaintext highlighter-rouge">objA</code>, <code class="language-plaintext highlighter-rouge">objB</code>는 함수 호출이 끝나면 scope에서 벗어나므로, GC가 메모리를 회수할 수 있다.</p> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +</pre></td><td class="rouge-code"><pre><span class="kd">function</span> <span class="nf">createCircularReference</span><span class="p">()</span> <span class="p">{</span> + <span class="kd">let</span> <span class="nx">objA</span> <span class="o">=</span> <span class="p">{};</span> + <span class="kd">let</span> <span class="nx">objB</span> <span class="o">=</span> <span class="p">{};</span> + + <span class="kd">let</span> <span class="nx">weakMap</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">WeakMap</span><span class="p">();</span> + <span class="nx">weakMap</span><span class="p">.</span><span class="nf">set</span><span class="p">(</span><span class="nx">objA</span><span class="p">,</span> <span class="nx">objB</span><span class="p">);</span> + <span class="nx">weakMap</span><span class="p">.</span><span class="nf">set</span><span class="p">(</span><span class="nx">objB</span><span class="p">,</span> <span class="nx">objA</span><span class="p">);</span> +<span class="p">}</span> + +<span class="nf">createCircularReference</span><span class="p">();</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p><code class="language-plaintext highlighter-rouge">WeakSet</code>도 비슷하게 <code class="language-plaintext highlighter-rouge">WeakSet</code>이 가지고 있는 object를 GC가 회수할 수 있다. <code class="language-plaintext highlighter-rouge">WeakSet</code>은 objects의 집합을 GC의 메모리 회수를 막는 거 없이 저장할 수 있는 방법이다. 하지만 <code class="language-plaintext highlighter-rouge">WeakSet</code>은 순환 참조에 직접 사용할 수 있는 것은 아니다. <code class="language-plaintext highlighter-rouge">key</code>-<code class="language-plaintext highlighter-rouge">value</code>를 저장하지 않기 때문이다. 오직 object를 저장만하고 그들 사이에 관계를 만들진 않는다.</p> + +<h2 id="using-garbage-collector-api">Using Garbage Collector API</h2> + +<p>GC API를 사용하는 방법도 있다. GC를 수동적으로 동작하게 하고, 현재 heap의 상태를 알 수 있다. 따라서 memory leak나 성능 이슈를 디버깅하는데도 도움을 준다.</p> + +<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +</pre></td><td class="rouge-code"><pre><span class="kd">let</span> <span class="nx">object1</span> <span class="o">=</span> <span class="p">{};</span> +<span class="kd">let</span> <span class="nx">object2</span> <span class="o">=</span> <span class="p">{};</span> + +<span class="c1">// create a circular reference between object1 and object2</span> +<span class="nx">object1</span><span class="p">.</span><span class="nx">next</span> <span class="o">=</span> <span class="nx">object2</span><span class="p">;</span> +<span class="nx">object2</span><span class="p">.</span><span class="nx">prev</span> <span class="o">=</span> <span class="nx">object1</span><span class="p">;</span> + +<span class="c1">// manually trigger garbage collection</span> +<span class="nf">gc</span><span class="p">();</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p><code class="language-plaintext highlighter-rouge">gc()</code>를 실행하여 GC를 수동적으로 실행하면 설령 아직 참조되고 있더라도 object에 의해 사용되고 있던 메모리를 비운다.</p> + +<p><code class="language-plaintext highlighter-rouge">gc()</code>는 모든 자바스크립트 엔진에서 지원되지는 않는다. 그리고 엔진에 따라 동작이 다르다. 또한 성능에 영향을 줄 수 있기에 신중하게 필요할 떄만 사용해야한다.</p> + +<h2 id="using-heap-snapshots-and-profilers">Using <code class="language-plaintext highlighter-rouge">heap snapshots</code> and <code class="language-plaintext highlighter-rouge">profilers</code></h2> + +<p>Javascripts는 <code class="language-plaintext highlighter-rouge">heap snapshot</code>과 <code class="language-plaintext highlighter-rouge">profilers</code>를 제공하여 어플리케이션이 어떻게 메모리를 사용하고 있는지 알려준다.</p> + +<p><code class="language-plaintext highlighter-rouge">Heap Snapshot</code>은 현재 heap 상태의 스냅샷을 찍어주고, 그것을 분석하여 어떤 object가 가장 많은 메모리를 사용하고 있는지 알 수 있게 해준다.</p> + +<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +</pre></td><td class="rouge-code"><pre><span class="c1">// Start a heap snapshot</span> +<span class="kd">let</span> <span class="nx">snapshot1</span> <span class="o">=</span> <span class="nx">performance</span><span class="p">.</span><span class="nf">heapSnapshot</span><span class="p">();</span> + +<span class="c1">// Do some actions that might cause memory leaks</span> +<span class="k">for </span><span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="mi">100000</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> + <span class="nx">myArray</span><span class="p">.</span><span class="nf">push</span><span class="p">({</span> + <span class="na">largeData</span><span class="p">:</span> <span class="k">new</span> <span class="nc">Array</span><span class="p">(</span><span class="mi">1000000</span><span class="p">).</span><span class="nf">fill</span><span class="p">(</span><span class="dl">"</span><span class="s2">some data</span><span class="dl">"</span><span class="p">),</span> + <span class="na">id</span><span class="p">:</span> <span class="nx">i</span> + <span class="p">});</span> +<span class="p">}</span> + +<span class="c1">// Take another heap snapshot</span> +<span class="kd">let</span> <span class="nx">snapshot2</span> <span class="o">=</span> <span class="nx">performance</span><span class="p">.</span><span class="nf">heapSnapshot</span><span class="p">();</span> + +<span class="c1">// Compare the two snapshots to see which objects were created</span> +<span class="kd">let</span> <span class="nx">diff</span> <span class="o">=</span> <span class="nx">snapshot2</span><span class="p">.</span><span class="nf">compare</span><span class="p">(</span><span class="nx">snapshot1</span><span class="p">);</span> + +<span class="c1">// Analyze the diff to see which objects are using the most memory</span> +<span class="nx">diff</span><span class="p">.</span><span class="nf">forEach</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">item</span><span class="p">)</span> <span class="p">{</span> + <span class="k">if </span><span class="p">(</span><span class="nx">item</span><span class="p">.</span><span class="nx">size</span> <span class="o">&gt;</span> <span class="mi">1000000</span><span class="p">)</span> <span class="p">{</span> + <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="nx">item</span><span class="p">.</span><span class="nx">name</span><span class="p">);</span> + <span class="p">}</span> +<span class="p">});</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>위 예시에서는 루프 실행 전후로 스냅샷을 찍고, 두 스냅샷을 비교하여 어떤 object가 메모리를 많이 차지하는지 알 수 있게 해준다.</p> + +<p><code class="language-plaintext highlighter-rouge">Profiler</code>는 어플리케이션의 성능을 트랙킹하고, 메모리 사용이 많은 area를 인식한다.</p> + +<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +</pre></td><td class="rouge-code"><pre><span class="kd">let</span> <span class="nx">profiler</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Profiler</span><span class="p">();</span> + +<span class="nx">profiler</span><span class="p">.</span><span class="nf">start</span><span class="p">();</span> + +<span class="c1">// do some actions that might cause memory leaks</span> +<span class="k">for </span><span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="mi">100000</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> + <span class="nx">myArray</span><span class="p">.</span><span class="nf">push</span><span class="p">({</span> + <span class="na">largeData</span><span class="p">:</span> <span class="k">new</span> <span class="nc">Array</span><span class="p">(</span><span class="mi">1000000</span><span class="p">).</span><span class="nf">fill</span><span class="p">(</span><span class="dl">"</span><span class="s2">some data</span><span class="dl">"</span><span class="p">),</span> + <span class="na">id</span><span class="p">:</span> <span class="nx">i</span> + <span class="p">});</span> +<span class="p">}</span> + +<span class="nx">profiler</span><span class="p">.</span><span class="nf">stop</span><span class="p">();</span> + +<span class="kd">let</span> <span class="nx">report</span> <span class="o">=</span> <span class="nx">profiler</span><span class="p">.</span><span class="nf">report</span><span class="p">();</span> + +<span class="c1">// analyze the report to identify areas where memory usage is high</span> +<span class="k">for </span><span class="p">(</span><span class="kd">let</span> <span class="nx">func</span> <span class="k">of</span> <span class="nx">report</span><span class="p">)</span> <span class="p">{</span> + <span class="k">if </span><span class="p">(</span><span class="nx">func</span><span class="p">.</span><span class="nx">memory</span> <span class="o">&gt;</span> <span class="mi">1000000</span><span class="p">)</span> <span class="p">{</span> + <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="nx">func</span><span class="p">.</span><span class="nx">name</span><span class="p">);</span> + <span class="p">}</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p><code class="language-plaintext highlighter-rouge">heap snapshot</code>과 <code class="language-plaintext highlighter-rouge">profilers</code>은 모든 엔진과 브라우저에서 지원되는 것은 아니다.</p> + +<p><br /></p> + +<h2 id="출처">출처</h2> + +<p>https://itnext.io/javascript-memory-management-how-to-avoid-common-memory-leaks-and-improve-performance-c018dbbca954#9f04</p> + + + Tue, 18 Apr 2023 22:00:00 +0900 + https://seongil-shin.github.io/posts/js-memory-leak/ + https://seongil-shin.github.io/posts/js-memory-leak/ + + javascript + + + study + + javascript + + + + + Typescript 5.0 + <p>typescript 5.0이 나왔다고 하여 간단히 어떤 기능들이 추가되었는지 살펴보았다. 자세한 내용은 <a href="https://velog.io/@hustle-dev/TypeScript-5.0-%EB%B2%88%EC%97%AD#%EB%8D%B0%EC%BD%94%EB%A0%88%EC%9D%B4%ED%84%B0">Typescript 5.0 번역</a>에서 확인 가능하다.</p> + +<h2 id="데코레이터">데코레이터</h2> + +<p>데코레이터는 재사용 가능한 방식으로 클래스와 그 멤버를 사용자 정의하는 ECMAScript의 기능이다. 데코레이터는 이전부터 지원되었으나 Typescript 5.0부터는 공식 기능으로 지원한다.</p> + +<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +</pre></td><td class="rouge-code"><pre><span class="kd">function</span> <span class="nf">loggedMethod</span><span class="p">(</span><span class="nx">originalMethod</span><span class="p">:</span> <span class="kr">any</span><span class="p">,</span> <span class="nx">context</span><span class="p">:</span> <span class="nx">ClassMethodDecoratorContext</span><span class="p">)</span> <span class="p">{</span> + <span class="kd">const</span> <span class="nx">methodName</span> <span class="o">=</span> <span class="nc">String</span><span class="p">(</span><span class="nx">context</span><span class="p">.</span><span class="nx">name</span><span class="p">);</span> + + <span class="kd">function</span> <span class="nf">replacementMethod</span><span class="p">(</span><span class="k">this</span><span class="p">:</span> <span class="kr">any</span><span class="p">,</span> <span class="p">...</span><span class="nx">args</span><span class="p">:</span> <span class="kr">any</span><span class="p">[])</span> <span class="p">{</span> + <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="s2">`LOG: Entering method '</span><span class="p">${</span><span class="nx">methodName</span><span class="p">}</span><span class="s2">'.`</span><span class="p">)</span> + <span class="kd">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="nx">originalMethod</span><span class="p">.</span><span class="nf">call</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="p">...</span><span class="nx">args</span><span class="p">);</span> + <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="s2">`LOG: Exiting method '</span><span class="p">${</span><span class="nx">methodName</span><span class="p">}</span><span class="s2">'.`</span><span class="p">)</span> + <span class="k">return</span> <span class="nx">result</span><span class="p">;</span> + <span class="p">}</span> + + <span class="k">return</span> <span class="nx">replacementMethod</span><span class="p">;</span> +<span class="p">}</span> + +<span class="kd">class</span> <span class="nc">Person</span> <span class="p">{</span> + <span class="nl">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span> + <span class="nf">constructor</span><span class="p">(</span><span class="nx">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">)</span> <span class="p">{</span> + <span class="k">this</span><span class="p">.</span><span class="nx">name</span> <span class="o">=</span> <span class="nx">name</span><span class="p">;</span> + <span class="p">}</span> + + <span class="p">@</span><span class="nd">loggedMethod</span> + <span class="nf">greet</span><span class="p">()</span> <span class="p">{</span> + <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="s2">`Hello, my name is </span><span class="p">${</span><span class="k">this</span><span class="p">.</span><span class="nx">name</span><span class="p">}</span><span class="s2">.`</span><span class="p">);</span> + <span class="p">}</span> +<span class="p">}</span> + +<span class="kd">const</span> <span class="nx">p</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Person</span><span class="p">(</span><span class="dl">"</span><span class="s2">Ron</span><span class="dl">"</span><span class="p">);</span> +<span class="nx">p</span><span class="p">.</span><span class="nf">greet</span><span class="p">();</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<h2 id="const-타입-파라미터">const 타입 파라미터</h2> + +<p>특정 문자열로 타입을 구성하고 싶을 때, 주로 <code class="language-plaintext highlighter-rouge">as const</code>를 사용하였다.</p> + +<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +</pre></td><td class="rouge-code"><pre><span class="c1">// The type we wanted:</span> +<span class="c1">// readonly ["Alice", "Bob", "Eve"]</span> +<span class="c1">// The type we got:</span> +<span class="c1">// string[]</span> +<span class="kd">const</span> <span class="nx">names1</span> <span class="o">=</span> <span class="nf">getNamesExactly</span><span class="p">({</span> <span class="na">names</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">Alice</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">Bob</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">Eve</span><span class="dl">"</span><span class="p">]});</span> + +<span class="c1">// Correctly gets what we wanted:</span> +<span class="c1">// readonly ["Alice", "Bob", "Eve"]</span> +<span class="kd">const</span> <span class="nx">names2</span> <span class="o">=</span> <span class="nf">getNamesExactly</span><span class="p">({</span> <span class="na">names</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">Alice</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">Bob</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">Eve</span><span class="dl">"</span><span class="p">]}</span> <span class="kd">as const</span><span class="p">);</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>하지만 이제는 제네릭에 <code class="language-plaintext highlighter-rouge">const</code>를 추가하여 비슷한 효과를 얻을 수 있다.</p> + +<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +</pre></td><td class="rouge-code"><pre><span class="kd">type</span> <span class="nx">HasNames</span> <span class="o">=</span> <span class="p">{</span> <span class="na">names</span><span class="p">:</span> <span class="k">readonly</span> <span class="kr">string</span><span class="p">[]</span> <span class="p">};</span> +<span class="kd">function</span> <span class="nf">getNamesExactly</span><span class="o">&lt;</span><span class="kd">const</span> <span class="nx">T</span> <span class="kd">extends</span> <span class="nx">HasNames</span><span class="o">&gt;</span><span class="p">(</span><span class="nx">arg</span><span class="p">:</span> <span class="nx">T</span><span class="p">):</span> <span class="nx">T</span><span class="p">[</span><span class="dl">"</span><span class="s2">names</span><span class="dl">"</span><span class="p">]</span> <span class="p">{</span> +<span class="c1">// ^^^^^</span> + <span class="k">return</span> <span class="nx">arg</span><span class="p">.</span><span class="nx">names</span><span class="p">;</span> +<span class="p">}</span> + +<span class="c1">// Inferred type: readonly ["Alice", "Bob", "Eve"]</span> +<span class="c1">// Note: Didn't need to write 'as const' here</span> +<span class="kd">const</span> <span class="nx">names</span> <span class="o">=</span> <span class="nf">getNamesExactly</span><span class="p">({</span> <span class="na">names</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">Alice</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">Bob</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">Eve</span><span class="dl">"</span><span class="p">]</span> <span class="p">});</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p><br /></p> + +<h2 id="extends에서-여러-파일-가져오기-가능">extends에서 여러 파일 가져오기 가능</h2> + +<p>typescript 5.0부터는 다음과 같이 <code class="language-plaintext highlighter-rouge">tsconfig.json</code>의 <code class="language-plaintext highlighter-rouge">extends</code>에서 여러 파일을 작성할 수 있다. 따라서 여러 파일을 확장할 수 있게 된다.</p> + +<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +</pre></td><td class="rouge-code"><pre><span class="p">{</span><span class="w"> + </span><span class="nl">"extends"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"a"</span><span class="p">,</span><span class="w"> </span><span class="s2">"b"</span><span class="p">,</span><span class="w"> </span><span class="s2">"c"</span><span class="p">],</span><span class="w"> + </span><span class="nl">"compilerOptions"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> + </span><span class="err">//</span><span class="w"> </span><span class="err">...</span><span class="w"> + </span><span class="p">}</span><span class="w"> +</span><span class="p">}</span><span class="w"> +</span></pre></td></tr></tbody></table></code></pre></div></div> + +<p>c는 b를 확장하고, b는 a를 확장하는 것과 비슷한 효과를 가진다. 따라서 설정이 충돌되는 경우 후자의 항목이 우선된다.</p> + +<p><br /></p> + +<h2 id="모든-enum은-유니온-enum이다">모든 enum은 유니온 enum이다.</h2> + +<p>typescript 2.0에서 enum literal type이 도입되면서 enum은 좀 더 특별해졌다. enum literal type은 각 enum 멤버에 고유한 타입을 부여하고 enum 자체를 각 멤버타입의 union으로 만들었다.</p> + +<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +</pre></td><td class="rouge-code"><pre><span class="c1">// Color is like a union of Red | Orange | Yellow | Green | Blue | Violet</span> +<span class="kr">enum</span> <span class="nx">Color</span> <span class="p">{</span> + <span class="nx">Red</span><span class="p">,</span> <span class="nx">Orange</span><span class="p">,</span> <span class="nx">Yellow</span><span class="p">,</span> <span class="nx">Green</span><span class="p">,</span> <span class="nx">Blue</span><span class="p">,</span> <span class="cm">/* Indigo */</span><span class="p">,</span> <span class="nx">Violet</span> +<span class="p">}</span> + +<span class="c1">// Each enum member has its own type that we can refer to!</span> +<span class="kd">type</span> <span class="nx">PrimaryColor</span> <span class="o">=</span> <span class="nx">Color</span><span class="p">.</span><span class="nx">Red</span> <span class="o">|</span> <span class="nx">Color</span><span class="p">.</span><span class="nx">Green</span> <span class="o">|</span> <span class="nx">Color</span><span class="p">.</span><span class="nx">Blue</span><span class="p">;</span> + +<span class="kd">function</span> <span class="nf">isPrimaryColor</span><span class="p">(</span><span class="nx">c</span><span class="p">:</span> <span class="nx">Color</span><span class="p">):</span> <span class="nx">c</span> <span class="k">is</span> <span class="nx">PrimaryColor</span> <span class="p">{</span> + <span class="c1">// Narrowing literal types can catch bugs.</span> + <span class="c1">// TypeScript will error here because</span> + <span class="c1">// we'll end up comparing 'Color.Red' to 'Color.Green'.</span> + <span class="c1">// We meant to use ||, but accidentally wrote &amp;&amp;.</span> + <span class="k">return</span> <span class="nx">c</span> <span class="o">===</span> <span class="nx">Color</span><span class="p">.</span><span class="nx">Red</span> <span class="o">&amp;&amp;</span> <span class="nx">c</span> <span class="o">===</span> <span class="nx">Color</span><span class="p">.</span><span class="nx">Green</span> <span class="o">&amp;&amp;</span> <span class="nx">c</span> <span class="o">===</span> <span class="nx">Color</span><span class="p">.</span><span class="nx">Blue</span><span class="p">;</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>이때 문제는 멤버 타입이 실제 값과 일부 연관되어있다는 것이다. 그래서 멤버가 함수 호출로 초기화되면 값을 계산할 수 없는 경우가 있다.</p> + +<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +</pre></td><td class="rouge-code"><pre><span class="kr">enum</span> <span class="nx">E</span> <span class="p">{</span> + <span class="nx">Blah</span> <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nf">random</span><span class="p">()</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>Typescript 5.0 은 계산된 각 멤버에 대해 고유한 타입을 생성하여 모든 enum을 공용체 enum으로 만들 수 있다.</p> + +<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +</pre></td><td class="rouge-code"><pre><span class="c1">// typescript 5.0</span> +<span class="kd">const</span> <span class="nx">a</span> <span class="p">:</span> <span class="nx">E</span><span class="p">.</span><span class="nx">Blah</span> <span class="o">=</span> <span class="mi">10</span><span class="p">;</span> <span class="c1">// 문제 없음</span> +<span class="c1">// typescript 5.0 미만</span> +<span class="kd">const</span> <span class="nx">a</span> <span class="p">:</span> <span class="nx">E</span><span class="p">.</span><span class="nx">Blah</span> <span class="o">=</span> <span class="mi">10</span><span class="p">;</span> <span class="c1">// Enum type 'E' has members with initializers that are not literals.</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p><br /></p> + +<h2 id="moduleresolution-bundler">–moduleResolution bundler</h2> + +<p>typesciprt 4.7에는 <code class="language-plaintext highlighter-rouge">--module</code>, <code class="language-plaintext highlighter-rouge">--moduleResolution</code>에 <code class="language-plaintext highlighter-rouge">node16</code>, <code class="language-plaintext highlighter-rouge">nodenext</code> 옵션을 도입하여 ECMAScript 모듈에 대한 정확한 조회 규칙을 모델링할 수 있게 되었다. 하지만 이는 몇몇 개발자에겐 번거로운 설정이었다.</p> + +<p>5.0에서는 compilerOptionsdp <code class="language-plaintext highlighter-rouge">moduleResolution</code>에 <code class="language-plaintext highlighter-rouge">bundler</code> 옵션을 추가하여 번들러의 동작을 모델링할 수 있다.</p> + +<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +</pre></td><td class="rouge-code"><pre><span class="p">{</span><span class="w"> + </span><span class="nl">"compilerOptions"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> + </span><span class="nl">"target"</span><span class="p">:</span><span class="w"> </span><span class="s2">"esnext"</span><span class="p">,</span><span class="w"> + </span><span class="nl">"moduleResolution"</span><span class="p">:</span><span class="w"> </span><span class="s2">"bundler"</span><span class="w"> + </span><span class="p">}</span><span class="w"> +</span><span class="p">}</span><span class="w"> +</span></pre></td></tr></tbody></table></code></pre></div></div> + +<p>하이브리드 조회 전략을 구현하는 Vite, esbuild, swc, Webpack, Parcel 등의 최신 번들러를 사용 중이라면 새로운 <code class="language-plaintext highlighter-rouge">bundler</code>옵션이 적합할 것이다. 하지만, 라이브러리 제작자일 경우 <code class="language-plaintext highlighter-rouge">bundler</code> 옵션을 사용하면, 번들러를 사용하지 않는 사용자에게 문제가 생길 수 있으니 <code class="language-plaintext highlighter-rouge">node16</code>, <code class="language-plaintext highlighter-rouge">nodenext</code> 옵션을 추천한다.</p> + +<p><br /></p> + +<h2 id="resolution-커스터마이징-플래그">Resolution 커스터마이징 플래그</h2> + +<ul> + <li>allowImportingTsExtensions : <code class="language-plaintext highlighter-rouge">.ts</code>, <code class="language-plaintext highlighter-rouge">.mts</code>, <code class="language-plaintext highlighter-rouge">.tsx</code>와 같이 Typescript 전용 확장자를 사용하여 서로를 import 할 수 있다.</li> + <li>resolvePackageJsonExports : typescript가 <code class="language-plaintext highlighter-rouge">node_modules</code>의 패키지를 읽을 경우, package.json의 exports 필드를 참조하도록 한다.</li> + <li>resolvePackageJsonImports : package.json을 포함하는 조상 디렉터리의 파일로부터 <code class="language-plaintext highlighter-rouge">#</code>으로 시작하는 조회를 수행할 때, typescript가 package.json의 import 플드를 참조하도록 강제함</li> + <li>allowArbitraryExtensions + <ul> + <li>typescript 5.0에서 import 경로가 js또는 ts 파일 확장자가 아닌 경우 <code class="language-plaintext highlighter-rouge">{파일 기본 이름}.d.{확장자}.ts</code> 형식의 해당 경로에서 타입을 찾는다.</li> + <li>Typescript에서는 이 파일 형식을 이해하지 못하여 런타임에서 import를 지원하지 않을 수 있음을 알리는 오류를 내지만, 이 플래그를 통해 억제 할 수 있다.</li> + </ul> + </li> +</ul> + +<p><br /></p> + +<h2 id="customconditions">customConditions</h2> + +<p>package.json의 <code class="language-plaintext highlighter-rouge">exports</code>, <code class="language-plaintext highlighter-rouge">imports</code> 필드를 Typescript가 리졸브할 때 성공해야하는 추가 조건의 목록을 받는다. 현존하는 조건에 추가된다.</p> + +<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +</pre></td><td class="rouge-code"><pre><span class="p">{</span><span class="w"> + </span><span class="nl">"compilerOptions"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> + </span><span class="nl">"target"</span><span class="p">:</span><span class="w"> </span><span class="s2">"es2022"</span><span class="p">,</span><span class="w"> + </span><span class="nl">"moduleResolution"</span><span class="p">:</span><span class="w"> </span><span class="s2">"bundler"</span><span class="p">,</span><span class="w"> + </span><span class="nl">"customConditions"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"my-condition"</span><span class="p">]</span><span class="w"> + </span><span class="p">}</span><span class="w"> +</span><span class="p">}</span><span class="w"> +</span></pre></td></tr></tbody></table></code></pre></div></div> + +<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +</pre></td><td class="rouge-code"><pre><span class="p">{</span><span class="w"> + </span><span class="err">//</span><span class="w"> </span><span class="err">...</span><span class="w"> + </span><span class="nl">"exports"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> + </span><span class="nl">"."</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> + </span><span class="nl">"my-condition"</span><span class="p">:</span><span class="w"> </span><span class="s2">"./foo.mjs"</span><span class="p">,</span><span class="w"> + </span><span class="nl">"node"</span><span class="p">:</span><span class="w"> </span><span class="s2">"./bar.mjs"</span><span class="p">,</span><span class="w"> + </span><span class="nl">"import"</span><span class="p">:</span><span class="w"> </span><span class="s2">"./baz.mjs"</span><span class="p">,</span><span class="w"> + </span><span class="nl">"require"</span><span class="p">:</span><span class="w"> </span><span class="s2">"./biz.mjs"</span><span class="w"> + </span><span class="p">}</span><span class="w"> + </span><span class="p">}</span><span class="w"> +</span><span class="p">}</span><span class="w"> +</span></pre></td></tr></tbody></table></code></pre></div></div> + +<p>위와 같은 경우 <code class="language-plaintext highlighter-rouge">exports</code>, <code class="language-plaintext highlighter-rouge">imports</code> 필드를 리졸브할 때 항상 <code class="language-plaintext highlighter-rouge">my-condition</code>을 고려한다.</p> + +<p><br /></p> + +<h2 id="verbatimmodulesyntax">–verbatimModuleSyntax</h2> + +<p>기본적으로 Typescript는 <code class="language-plaintext highlighter-rouge">import elision</code>을 수행한다. 예를들어 다음과 같이 <code class="language-plaintext highlighter-rouge">import</code>를 type을 가져오기만을 위해 사용한다면, 이를 지운다.</p> + +<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +</pre></td><td class="rouge-code"><pre><span class="k">import</span> <span class="p">{</span> <span class="nx">Car</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./car</span><span class="dl">"</span><span class="p">;</span> + +<span class="k">export</span> <span class="kd">function</span> <span class="nf">drive</span><span class="p">(</span><span class="nx">car</span><span class="p">:</span> <span class="nx">Car</span><span class="p">)</span> <span class="p">{</span> + <span class="c1">// ...</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +</pre></td><td class="rouge-code"><pre><span class="c1">// import 문을 지움</span> +<span class="k">export</span> <span class="kd">function</span> <span class="nf">drive</span><span class="p">(</span><span class="nx">car</span><span class="p">)</span> <span class="p">{</span> + <span class="c1">// ...</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>type은 값이 아니기에 지우지 않으면 런타임 에러가 날 수 있기 때문이다. 하지만 몇가지 엣지 케이스가 있을 수 있다. 예를들어 변환된 코드는 <code class="language-plaintext highlighter-rouge">import ".car"</code>조차 남기지 않는데, 이는 사이드 이펙트를 일으킬 수도 있다.</p> + +<p>따라서 Typescript에서는 <code class="language-plaintext highlighter-rouge">type</code> 수정자를 사용하도록 하는 <code class="language-plaintext highlighter-rouge">--importsNotUsedAsValues</code>, 일부 모듈 엘리션 동작을 방지하는 <code class="language-plaintext highlighter-rouge">--perserveValueImports</code>, Typescript가 여러 컴파일러에서 작동하는지 확인하는 <code class="language-plaintext highlighter-rouge">--isolatedModules</code> 플래그가 있다. 하지만 이 플래그들은 이해하기 어렵고, 에지 케이스 역시 존재한다.</p> + +<p>Typescript 5.0에서는 <code class="language-plaintext highlighter-rouge">--verbatimModuleSyntax</code> 를 적용하여 <code class="language-plaintext highlighter-rouge">type</code> 수정자가 없는 모든 import, export는 그대로 유지하도록 했다. <code class="language-plaintext highlighter-rouge">type</code> 수정자를 사용하는 모든 항목은 완전히 삭제된다.</p> + +<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +</pre></td><td class="rouge-code"><pre><span class="c1">// Erased away entirely.</span> +<span class="k">import</span> <span class="kd">type</span> <span class="p">{</span> <span class="nx">A</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">a</span><span class="dl">"</span><span class="p">;</span> + +<span class="c1">// Rewritten to 'import { b } from "bcd";'</span> +<span class="k">import</span> <span class="p">{</span> <span class="nx">b</span><span class="p">,</span> <span class="kd">type</span> <span class="nx">c</span><span class="p">,</span> <span class="kd">type</span> <span class="nx">d</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">bcd</span><span class="dl">"</span><span class="p">;</span> + +<span class="c1">// Rewritten to 'import {} from "xyz";'</span> +<span class="k">import</span> <span class="p">{</span> <span class="kd">type</span> <span class="nx">xyz</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">xyz</span><span class="dl">"</span><span class="p">;</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>하지만 이 플래그를 사용하면 ECMAScript <code class="language-plaintext highlighter-rouge">import</code>와 <code class="language-plaintext highlighter-rouge">export</code>는, 다른 모듈 시스템을 암시하는 파일 확장자 또는 세팅이 있을때 <code class="language-plaintext highlighter-rouge">require</code> 로 재작성되지 않는다. 대신 오류를 낸다. 만약 <code class="language-plaintext highlighter-rouge">require</code>나 <code class="language-plaintext highlighter-rouge">module.exports</code>를 사용하는 코드를 emit 하고 싶다면 ES2015 이전의 Typescript 모듈 구문을 사용해야한다.</p> + +<table> + <thead> + <tr> + <th>Input Typescript</th> + <th>Output Javascript</th> + </tr> + </thead> + <tbody> + <tr> + <td><code class="language-plaintext highlighter-rouge">import foo = require("foo"); </code></td> + <td><code class="language-plaintext highlighter-rouge">const foo = require("foo"); </code></td> + </tr> + <tr> + <td><strong>function</strong> <strong>foo</strong>() {} <br /><strong>function</strong> <strong>bar</strong>() {} <br /><strong>function</strong> <strong>baz</strong>() {} <br /><strong>export</strong> = { foo, bar, baz };</td> + <td><strong>function</strong> <strong>foo</strong>() {} <br /><strong>function</strong> <strong>bar</strong>() {} <br /><strong>function</strong> <strong>baz</strong>() {} <br />module.exports = { foo, bar, baz };</td> + </tr> + </tbody> +</table> + +<p>이는 몇몇 상황에서 도움이 되는데, 예를들어 개발자가 <code class="language-plaintext highlighter-rouge">package.json</code>의 <code class="language-plaintext highlighter-rouge">--module node16</code>을 설정하지 않은 경우가 있다. 개발자는 인지하지 못한채 ES 모듈 대신 CommonJS 모듈을 작성하기 시작하여 예상치 못한 조회 규칙과 JavaScript 출력을 제공하게 된다. 이 플래그는 구문이 의도적으로 다르기 때문에 사용중인 파일 형식에 대해 의도적으로 확인할 수 있다.</p> + +<p><code class="language-plaintext highlighter-rouge">-verbatimModuleSyntax</code>은 <code class="language-plaintext highlighter-rouge">--importsNotUsedAsValues</code> 및 <code class="language-plaintext highlighter-rouge">--preserveValueImports</code>보다 더 일관된 스토리를 제공하므로, 기존의 두 플래그는 이 구문으로 대체된다.</p> + +<p><br /></p> + +<h2 id="support-for-export-type-">Support for export type *</h2> + +<p>기존엔 <code class="language-plaintext highlighter-rouge">export * from "module"</code>이나 <code class="language-plaintext highlighter-rouge">export * as ns from "module"</code> 처럼 re-export가 불가능했다. Typescript 5.0 부터는 가능하다.</p> + +<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +</pre></td><td class="rouge-code"><pre><span class="c1">// models/vehicles.ts</span> +<span class="k">export</span> <span class="kd">class</span> <span class="nc">Spaceship</span> <span class="p">{</span> + <span class="c1">// ...</span> +<span class="p">}</span> + +<span class="c1">// models/index.ts</span> +<span class="k">export</span> <span class="kd">type</span> <span class="o">*</span> <span class="kd">as </span><span class="nx">vehicles</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./vehicles</span><span class="dl">"</span><span class="p">;</span> + +<span class="c1">// main.ts</span> +<span class="k">import</span> <span class="p">{</span> <span class="nx">vehicles</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./models</span><span class="dl">"</span><span class="p">;</span> + +<span class="kd">function</span> <span class="nf">takeASpaceship</span><span class="p">(</span><span class="nx">s</span><span class="p">:</span> <span class="nx">vehicles</span><span class="p">.</span><span class="nx">Spaceship</span><span class="p">)</span> <span class="p">{</span> + <span class="c1">// ✅ ok - `vehicles` only used in a type position</span> +<span class="p">}</span> + +<span class="kd">function</span> <span class="nf">makeASpaceship</span><span class="p">()</span> <span class="p">{</span> + <span class="k">return</span> <span class="k">new</span> <span class="nx">vehicles</span><span class="p">.</span><span class="nc">Spaceship</span><span class="p">();</span> + <span class="c1">// ^^^^^^^^</span> + <span class="c1">// 'vehicles' cannot be used as a value because it was exported using 'export type'.</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p><br /></p> + +<h2 id="satisfies-support-in-jsdoc"><code class="language-plaintext highlighter-rouge">@satisfies</code> Support in JSDoc</h2> + +<p>Typescript 4.9에서 <code class="language-plaintext highlighter-rouge">satisfies</code> 연산자가 추가됐다. 이 연산자는 타입에 영향을 주지 않고, 표현식이 타입에 호환되는지만 확인한다.</p> + +<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +</pre></td><td class="rouge-code"><pre><span class="kr">interface</span> <span class="nx">CompilerOptions</span> <span class="p">{</span> + <span class="nl">strict</span><span class="p">?:</span> <span class="nx">boolean</span><span class="p">;</span> + <span class="nl">outDir</span><span class="p">?:</span> <span class="kr">string</span><span class="p">;</span> + <span class="c1">// ...</span> +<span class="p">}</span> + +<span class="kr">interface</span> <span class="nx">ConfigSettings</span> <span class="p">{</span> + <span class="nl">compilerOptions</span><span class="p">?:</span> <span class="nx">CompilerOptions</span><span class="p">;</span> + <span class="nl">extends</span><span class="p">?:</span> <span class="kr">string</span> <span class="o">|</span> <span class="kr">string</span><span class="p">[];</span> + <span class="c1">// ...</span> +<span class="p">}</span> + +<span class="kd">let</span> <span class="nx">myConfigSettings</span> <span class="o">=</span> <span class="p">{</span> + <span class="na">compilerOptions</span><span class="p">:</span> <span class="p">{</span> + <span class="na">strict</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> + <span class="na">outDir</span><span class="p">:</span> <span class="dl">"</span><span class="s2">../lib</span><span class="dl">"</span><span class="p">,</span> + <span class="c1">// ...</span> + <span class="p">},</span> + + <span class="na">extends</span><span class="p">:</span> <span class="p">[</span> + <span class="dl">"</span><span class="s2">@tsconfig/strictest/tsconfig.json</span><span class="dl">"</span><span class="p">,</span> + <span class="dl">"</span><span class="s2">../../../tsconfig.base.json</span><span class="dl">"</span> + <span class="p">],</span> + +<span class="p">}</span> <span class="kd">satisfies </span><span class="nx">ConfigSettings</span><span class="p">;</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>위 코드에서 Typescript는 <code class="language-plaintext highlighter-rouge">myConfigSettings.extends</code>가 배열임을 아는데, 이는 <code class="language-plaintext highlighter-rouge">ConfigSettings</code>와 호환 가능한지만 확인하고, <code class="language-plaintext highlighter-rouge">myConfigSettings</code>의 타입을 변경하지는 않았기 떄문이다. 따라서 <code class="language-plaintext highlighter-rouge">extends</code>에 map 함수를 사용해도 오류가 나지 않는다.</p> + +<p>이 <code class="language-plaintext highlighter-rouge">satisfies</code>가 JSDoc 태그에 추가되었다.</p> + +<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +</pre></td><td class="rouge-code"><pre><span class="c1">// @ts-check</span> + +<span class="cm">/** + * @typedef CompilerOptions + * @prop {boolean} [strict] + * @prop {string} [outDir] + */</span> + +<span class="cm">/** + * @satisfies {CompilerOptions} + */</span> +<span class="kd">let</span> <span class="nx">myCompilerOptions</span> <span class="o">=</span> <span class="p">{</span> + <span class="na">outdir</span><span class="p">:</span> <span class="dl">"</span><span class="s2">../lib</span><span class="dl">"</span><span class="p">,</span> +<span class="c1">// ~~~~~~ oops! we meant outDir</span> +<span class="p">};</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>다음과 같이 <code class="language-plaintext highlighter-rouge">/** @satisfies */ </code>인라인으로 사용하거나, 함수 호출과 같은 곳에서도 사용할 수 있다.</p> + +<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +</pre></td><td class="rouge-code"><pre><span class="kd">let</span> <span class="nx">myConfigSettings</span> <span class="o">=</span> <span class="cm">/** @satisfies {ConfigSettings} */</span> <span class="p">({</span> + <span class="na">compilerOptions</span><span class="p">:</span> <span class="p">{</span> + <span class="na">strict</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> + <span class="na">outDir</span><span class="p">:</span> <span class="dl">"</span><span class="s2">../lib</span><span class="dl">"</span><span class="p">,</span> + <span class="p">},</span> + <span class="na">extends</span><span class="p">:</span> <span class="p">[</span> + <span class="dl">"</span><span class="s2">@tsconfig/strictest/tsconfig.json</span><span class="dl">"</span><span class="p">,</span> + <span class="dl">"</span><span class="s2">../../../tsconfig.base.json</span><span class="dl">"</span> + <span class="p">],</span> +<span class="p">});</span> + +<span class="c1">// 함수 호출에서 사용</span> +<span class="nf">compileCode</span><span class="p">(</span><span class="cm">/** @satisfies {ConfigSettings} */</span> <span class="p">({</span> + <span class="c1">// ...</span> +<span class="p">}));</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p><br /></p> + +<h2 id="overload-support-in-jsdoc"><code class="language-plaintext highlighter-rouge">@overload</code> Support in JSDoc</h2> + +<p>Typescript에서는 함수 오버로드를 만들 수 있다. 이로써 함수를 사용하는 방법을 제한할 수 있다.</p> + +<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +</pre></td><td class="rouge-code"><pre><span class="c1">// Our overloads:</span> +<span class="kd">function</span> <span class="nf">printValue</span><span class="p">(</span><span class="nx">str</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span><span class="p">;</span> +<span class="kd">function</span> <span class="nf">printValue</span><span class="p">(</span><span class="nx">num</span><span class="p">:</span> <span class="kr">number</span><span class="p">,</span> <span class="nx">maxFractionDigits</span><span class="p">?:</span> <span class="kr">number</span><span class="p">):</span> <span class="k">void</span><span class="p">;</span> + +<span class="c1">// Our implementation:</span> +<span class="kd">function</span> <span class="nf">printValue</span><span class="p">(</span><span class="nx">value</span><span class="p">:</span> <span class="kr">string</span> <span class="o">|</span> <span class="kr">number</span><span class="p">,</span> <span class="nx">maximumFractionDigits</span><span class="p">?:</span> <span class="kr">number</span><span class="p">)</span> <span class="p">{</span> + <span class="k">if </span><span class="p">(</span><span class="k">typeof</span> <span class="nx">value</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">number</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span> + <span class="kd">const</span> <span class="nx">formatter</span> <span class="o">=</span> <span class="nx">Intl</span><span class="p">.</span><span class="nc">NumberFormat</span><span class="p">(</span><span class="dl">"</span><span class="s2">en-US</span><span class="dl">"</span><span class="p">,</span> <span class="p">{</span> + <span class="nx">maximumFractionDigits</span><span class="p">,</span> + <span class="p">});</span> + <span class="nx">value</span> <span class="o">=</span> <span class="nx">formatter</span><span class="p">.</span><span class="nf">format</span><span class="p">(</span><span class="nx">value</span><span class="p">);</span> + <span class="p">}</span> + + <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="nx">value</span><span class="p">);</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>이러한 오버로드 기능을 <code class="language-plaintext highlighter-rouge">@overload</code> 태그를 사용해서 JSDoc 안에서 사용할 수 있다.</p> + +<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +</pre></td><td class="rouge-code"><pre><span class="c1">// @ts-check</span> + +<span class="cm">/** + * @overload + * @param {string} value + * @return {void} + */</span> + +<span class="cm">/** + * @overload + * @param {number} value + * @param {number} [maximumFractionDigits] + * @return {void} + */</span> + +<span class="cm">/** + * @param {string | number} value + * @param {number} [maximumFractionDigits] + */</span> +<span class="kd">function</span> <span class="nf">printValue</span><span class="p">(</span><span class="nx">value</span><span class="p">,</span> <span class="nx">maximumFractionDigits</span><span class="p">)</span> <span class="p">{</span> + <span class="k">if </span><span class="p">(</span><span class="k">typeof</span> <span class="nx">value</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">number</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span> + <span class="kd">const</span> <span class="nx">formatter</span> <span class="o">=</span> <span class="nx">Intl</span><span class="p">.</span><span class="nc">NumberFormat</span><span class="p">(</span><span class="dl">"</span><span class="s2">en-US</span><span class="dl">"</span><span class="p">,</span> <span class="p">{</span> + <span class="nx">maximumFractionDigits</span><span class="p">,</span> + <span class="p">});</span> + <span class="nx">value</span> <span class="o">=</span> <span class="nx">formatter</span><span class="p">.</span><span class="nf">format</span><span class="p">(</span><span class="nx">value</span><span class="p">);</span> + <span class="p">}</span> + + <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="nx">value</span><span class="p">);</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p><br /></p> + +<h2 id="passing-emit-specific-flags-under---build">Passing Emit-Specific Flags Under <code class="language-plaintext highlighter-rouge">--build</code></h2> + +<p><code class="language-plaintext highlighter-rouge">--build</code> 모드에서 다음 플래그가 추가되었다. 빌드 시 커스터마이징을 위해 사용한다.</p> + +<ul> + <li><code class="language-plaintext highlighter-rouge">--declaration</code></li> + <li><code class="language-plaintext highlighter-rouge">—-emitDeclarationOnly</code></li> + <li><code class="language-plaintext highlighter-rouge">—-declarationMap</code></li> + <li><code class="language-plaintext highlighter-rouge">—-sourceMap</code></li> + <li><code class="language-plaintext highlighter-rouge">—-inlineSourceMap</code></li> +</ul> + +<p><br /></p> + +<h2 id="case-insensitive-import-sorting-in-editors">Case-Insensitive Import Sorting in Editors</h2> + +<p>VSCode 와 같은 코드에디터에서 TypeScript는 import, export 목록 정렬을 지원한다. 하지만 Typescript는 기본적으로 대소문자를 구분하여 정렬한다. 아스키 문자 인코딩 상 대문자는 소문자 앞이기에 다음 정렬은 Typescript에서 적절하다.</p> + +<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +</pre></td><td class="rouge-code"><pre><span class="k">import</span> <span class="p">{</span> + <span class="nx">Toggle</span><span class="p">,</span> + <span class="nx">freeze</span><span class="p">,</span> + <span class="nx">toBoolean</span><span class="p">,</span> +<span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./utils</span><span class="dl">"</span><span class="p">;</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>하지만 대소문자를 구분하지 않고 정렬하고 싶을 수도 있다. 이제는 <code class="language-plaintext highlighter-rouge">organizeImports</code> 옵션이 추가되어 해당 설정을 할 수 있다.</p> + +<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +</pre></td><td class="rouge-code"><pre><span class="p">{</span><span class="w"> + </span><span class="nl">"typescript.unstable"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> + </span><span class="err">//</span><span class="w"> </span><span class="err">Should</span><span class="w"> </span><span class="err">sorting</span><span class="w"> </span><span class="err">be</span><span class="w"> </span><span class="err">case-sensitive?</span><span class="w"> </span><span class="err">Can</span><span class="w"> </span><span class="err">be:</span><span class="w"> + </span><span class="err">//</span><span class="w"> </span><span class="err">-</span><span class="w"> </span><span class="kc">true</span><span class="w"> + </span><span class="err">//</span><span class="w"> </span><span class="err">-</span><span class="w"> </span><span class="kc">false</span><span class="w"> + </span><span class="err">//</span><span class="w"> </span><span class="err">-</span><span class="w"> </span><span class="s2">"auto"</span><span class="w"> </span><span class="err">(auto-detect)</span><span class="w"> + </span><span class="nl">"organizeImportsIgnoreCase"</span><span class="p">:</span><span class="w"> </span><span class="s2">"auto"</span><span class="p">,</span><span class="w"> + + </span><span class="err">//</span><span class="w"> </span><span class="err">Should</span><span class="w"> </span><span class="err">sorting</span><span class="w"> </span><span class="err">be</span><span class="w"> </span><span class="s2">"ordinal"</span><span class="w"> </span><span class="err">and</span><span class="w"> </span><span class="err">use</span><span class="w"> </span><span class="err">code</span><span class="w"> </span><span class="err">points</span><span class="w"> </span><span class="err">or</span><span class="w"> </span><span class="err">consider</span><span class="w"> </span><span class="err">Unicode</span><span class="w"> </span><span class="err">rules?</span><span class="w"> </span><span class="err">Can</span><span class="w"> </span><span class="err">be:</span><span class="w"> + </span><span class="err">//</span><span class="w"> </span><span class="err">-</span><span class="w"> </span><span class="s2">"ordinal"</span><span class="w"> + </span><span class="err">//</span><span class="w"> </span><span class="err">-</span><span class="w"> </span><span class="s2">"unicode"</span><span class="w"> + </span><span class="nl">"organizeImportsCollation"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ordinal"</span><span class="p">,</span><span class="w"> + + </span><span class="err">//</span><span class="w"> </span><span class="err">Under</span><span class="w"> </span><span class="err">`</span><span class="nl">"organizeImportsCollation"</span><span class="p">:</span><span class="w"> </span><span class="s2">"unicode"</span><span class="err">`</span><span class="p">,</span><span class="w"> + </span><span class="err">//</span><span class="w"> </span><span class="err">what</span><span class="w"> </span><span class="err">is</span><span class="w"> </span><span class="err">the</span><span class="w"> </span><span class="err">current</span><span class="w"> </span><span class="err">locale?</span><span class="w"> </span><span class="err">Can</span><span class="w"> </span><span class="err">be:</span><span class="w"> + </span><span class="err">//</span><span class="w"> </span><span class="err">-</span><span class="w"> </span><span class="p">[</span><span class="err">any</span><span class="w"> </span><span class="err">other</span><span class="w"> </span><span class="err">locale</span><span class="w"> </span><span class="err">code</span><span class="p">]</span><span class="w"> + </span><span class="err">//</span><span class="w"> </span><span class="err">-</span><span class="w"> </span><span class="s2">"auto"</span><span class="w"> </span><span class="err">(use</span><span class="w"> </span><span class="err">the</span><span class="w"> </span><span class="err">editor's</span><span class="w"> </span><span class="err">locale)</span><span class="w"> + </span><span class="nl">"organizeImportsLocale"</span><span class="p">:</span><span class="w"> </span><span class="s2">"en"</span><span class="p">,</span><span class="w"> + + </span><span class="err">//</span><span class="w"> </span><span class="err">Under</span><span class="w"> </span><span class="err">`</span><span class="nl">"organizeImportsCollation"</span><span class="p">:</span><span class="w"> </span><span class="s2">"unicode"</span><span class="err">`</span><span class="p">,</span><span class="w"> + </span><span class="err">//</span><span class="w"> </span><span class="err">should</span><span class="w"> </span><span class="err">upper-case</span><span class="w"> </span><span class="err">letters</span><span class="w"> </span><span class="err">or</span><span class="w"> </span><span class="err">lower-case</span><span class="w"> </span><span class="err">letters</span><span class="w"> </span><span class="err">come</span><span class="w"> </span><span class="err">first?</span><span class="w"> </span><span class="err">Can</span><span class="w"> </span><span class="err">be:</span><span class="w"> + </span><span class="err">//</span><span class="w"> </span><span class="err">-</span><span class="w"> </span><span class="kc">false</span><span class="w"> </span><span class="err">(locale-specific)</span><span class="w"> + </span><span class="err">//</span><span class="w"> </span><span class="err">-</span><span class="w"> </span><span class="s2">"upper"</span><span class="w"> + </span><span class="err">//</span><span class="w"> </span><span class="err">-</span><span class="w"> </span><span class="s2">"lower"</span><span class="w"> + </span><span class="nl">"organizeImportsCaseFirst"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w"> + + </span><span class="err">//</span><span class="w"> </span><span class="err">Under</span><span class="w"> </span><span class="err">`</span><span class="nl">"organizeImportsCollation"</span><span class="p">:</span><span class="w"> </span><span class="s2">"unicode"</span><span class="err">`</span><span class="p">,</span><span class="w"> + </span><span class="err">//</span><span class="w"> </span><span class="err">do</span><span class="w"> </span><span class="err">runs</span><span class="w"> </span><span class="err">of</span><span class="w"> </span><span class="err">numbers</span><span class="w"> </span><span class="err">get</span><span class="w"> </span><span class="err">compared</span><span class="w"> </span><span class="err">numerically</span><span class="w"> </span><span class="err">(i.e.</span><span class="w"> </span><span class="s2">"a1"</span><span class="w"> </span><span class="err">&lt;</span><span class="w"> </span><span class="s2">"a2"</span><span class="w"> </span><span class="err">&lt;</span><span class="w"> </span><span class="s2">"a100"</span><span class="err">)?</span><span class="w"> </span><span class="err">Can</span><span class="w"> </span><span class="err">be:</span><span class="w"> + </span><span class="err">//</span><span class="w"> </span><span class="err">-</span><span class="w"> </span><span class="kc">true</span><span class="w"> + </span><span class="err">//</span><span class="w"> </span><span class="err">-</span><span class="w"> </span><span class="kc">false</span><span class="w"> + </span><span class="nl">"organizeImportsNumericCollation"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"> + + </span><span class="err">//</span><span class="w"> </span><span class="err">Under</span><span class="w"> </span><span class="err">`</span><span class="nl">"organizeImportsCollation"</span><span class="p">:</span><span class="w"> </span><span class="s2">"unicode"</span><span class="err">`</span><span class="p">,</span><span class="w"> + </span><span class="err">//</span><span class="w"> </span><span class="err">do</span><span class="w"> </span><span class="err">letters</span><span class="w"> </span><span class="err">with</span><span class="w"> </span><span class="err">accent</span><span class="w"> </span><span class="err">marks/diacritics</span><span class="w"> </span><span class="err">get</span><span class="w"> </span><span class="err">sorted</span><span class="w"> </span><span class="err">distinctly</span><span class="w"> + </span><span class="err">//</span><span class="w"> </span><span class="err">from</span><span class="w"> </span><span class="err">their</span><span class="w"> </span><span class="s2">"base"</span><span class="w"> </span><span class="err">letter</span><span class="w"> </span><span class="err">(i.e.</span><span class="w"> </span><span class="err">is</span><span class="w"> </span><span class="err">é</span><span class="w"> </span><span class="err">different</span><span class="w"> </span><span class="err">from</span><span class="w"> </span><span class="err">e)?</span><span class="w"> </span><span class="err">Can</span><span class="w"> </span><span class="err">be</span><span class="w"> + </span><span class="err">//</span><span class="w"> </span><span class="err">-</span><span class="w"> </span><span class="kc">true</span><span class="w"> + </span><span class="err">//</span><span class="w"> </span><span class="err">-</span><span class="w"> </span><span class="kc">false</span><span class="w"> + </span><span class="nl">"organizeImportsAccentCollation"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w"> + </span><span class="p">},</span><span class="w"> + </span><span class="nl">"javascript.unstable"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> + </span><span class="err">//</span><span class="w"> </span><span class="err">same</span><span class="w"> </span><span class="err">options</span><span class="w"> </span><span class="err">valid</span><span class="w"> </span><span class="err">here...</span><span class="w"> + </span><span class="p">},</span><span class="w"> +</span><span class="p">}</span><span class="w"> +</span></pre></td></tr></tbody></table></code></pre></div></div> + +<p><br /></p> + +<h2 id="exhaustive-switchcase-completions"><strong>Exhaustive <code class="language-plaintext highlighter-rouge">switch</code>/<code class="language-plaintext highlighter-rouge">case</code> Completions</strong></h2> + +<p>switch 문을 작성할 때 검사 대상에 리터럴 타입이 있는지 감지한다. 감지되면 발견된지 않은 각 대소문자를 스카폴딩하는 완결성을 제공한다.</p> + +<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +</pre></td><td class="rouge-code"><pre><span class="kd">type</span> <span class="nx">Fruit</span> <span class="o">=</span> + <span class="o">|</span> <span class="p">{</span> <span class="na">kind</span> <span class="p">:</span> <span class="dl">"</span><span class="s2">apple</span><span class="dl">"</span> <span class="p">}</span> + <span class="o">|</span> <span class="p">{</span> <span class="na">kind</span> <span class="p">:</span> <span class="dl">"</span><span class="s2">orange</span><span class="dl">"</span> <span class="p">}</span> + <span class="o">|</span> <span class="p">{</span> <span class="na">kind</span> <span class="p">:</span> <span class="dl">"</span><span class="s2">banana</span><span class="dl">"</span> <span class="p">}</span> + +<span class="kd">function</span> <span class="nf">nom</span><span class="p">(</span><span class="nx">fruit</span><span class="p">:</span> <span class="nx">Fruit</span><span class="p">)</span> <span class="p">{</span> + <span class="k">switch</span><span class="p">(</span><span class="nx">fruit</span><span class="p">.</span><span class="nx">kind</span><span class="p">)</span> <span class="p">{</span> + <span class="k">case</span> <span class="c1">// case 문을 작성하면 자동 완성으로 case "apple": case "orange": case "banana"가 생김</span> + <span class="p">}</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p><br /></p> + +<h2 id="speed-memory-and-package-size-optimizations">Speed, Memory, and Package Size Optimizations</h2> + +<p>Typescript 5.0에서는 코드 구조, 데이터 구조, 알고리즘 구현을 변경하여 전체 경험을 좀 더 빠르게 만들었다.</p> + +<p><img src="/assets/image.png" alt="image" /></p> + +<p><img src="/assets/image-20230421235917311.png" alt="img" /></p> + +<p><br /></p> + +<h2 id="breaking-changes-and-deprecations">Breaking Changes and Deprecations</h2> + +<h3 id="runtime-requirements">Runtime Requirements</h3> + +<p>Typescript 5.0부터는 ECMAScript 2018을 대상으로 한다. 또한, Typescipt 패키지는 최소 요구 엔진을 12.20으로 설정했다.</p> + +<h3 id="libdts-changes"><code class="language-plaintext highlighter-rouge">lib.d.ts</code> Changes</h3> + +<p>DOM 유형이 생성되는 방식이 변경되어 기존 코드에 영향을 미칠 수 있다. 특히 특정 property가 숫자에서 숫자 리터럴 타입으로 변환되었다. 잘라내기, 복사, 붙여넣기 이벤트 처리를 위한 프로퍼티와 메서드가 인터페이스 전반으로 이동되었다.</p> + +<h3 id="api-breaking-changes">API Breaking Changes</h3> + +<p>모듈로 전환하고, 불필요한 인터페이스를 제거했으며 일부 정확성을 개선했다.</p> + +<h3 id="forbidden-implicit-coercions-in-relational-operators">Forbidden Implicit Coercions in Relational Operators</h3> + +<p>Typescript의 특정 연산은 암시적으로 문자열을 숫자로 강제 변환할 수 있는 코드를 작성할 경우 경고하고 있다.</p> + +<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +</pre></td><td class="rouge-code"><pre><span class="kd">function</span> <span class="nf">func</span><span class="p">(</span><span class="nx">ns</span><span class="p">:</span> <span class="kr">number</span> <span class="o">|</span> <span class="kr">string</span><span class="p">)</span> <span class="p">{</span> + <span class="k">return</span> <span class="nx">ns</span> <span class="o">*</span> <span class="mi">4</span><span class="p">;</span> <span class="c1">// Error, possible implicit coercion</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>5.0 부터는 관계 연산자 &gt;, &lt;, &lt;=, &gt;= 에도 이 기능이 적용된다. 원하는 경우 <code class="language-plaintext highlighter-rouge">+</code>를 사용하여 피연산자를 숫자로 명시적으로 강제할 수 있다.</p> + +<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +</pre></td><td class="rouge-code"><pre><span class="kd">function</span> <span class="nf">func</span><span class="p">(</span><span class="nx">ns</span><span class="p">:</span> <span class="kr">number</span> <span class="o">|</span> <span class="kr">string</span><span class="p">)</span> <span class="p">{</span> + <span class="k">return</span> <span class="nx">ns</span> <span class="o">&gt;</span> <span class="mi">4</span><span class="p">;</span> <span class="c1">// Now also an error</span> +<span class="p">}</span> + +<span class="kd">function</span> <span class="nf">func</span><span class="p">(</span><span class="nx">ns</span><span class="p">:</span> <span class="kr">number</span> <span class="o">|</span> <span class="kr">string</span><span class="p">)</span> <span class="p">{</span> + <span class="k">return</span> <span class="o">+</span><span class="nx">ns</span> <span class="o">&gt;</span> <span class="mi">4</span><span class="p">;</span> <span class="c1">// OK</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<h3 id="enum-overhaul">Enum Overhaul</h3> + +<p>Enum을 사용할 때 Enum 유형에 도메인 외부 리터럴을 할당하면 오류가 발생하도록 추가됐다.</p> + +<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +</pre></td><td class="rouge-code"><pre><span class="kr">enum</span> <span class="nx">SomeEvenDigit</span> <span class="p">{</span> + <span class="nx">Zero</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> + <span class="nx">Two</span> <span class="o">=</span> <span class="mi">2</span><span class="p">,</span> + <span class="nx">Four</span> <span class="o">=</span> <span class="mi">4</span> +<span class="p">}</span> + +<span class="c1">// Now correctly an error</span> +<span class="kd">let</span> <span class="nx">m</span><span class="p">:</span> <span class="nx">SomeEvenDigit</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>또 숫자와 간접 string enum 참조가 혼합되어 선언된 값이 있는 enum에서 오류를 제대로 생성하도록 변경됐다.</p> + +<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +</pre></td><td class="rouge-code"><pre><span class="kr">enum</span> <span class="nx">Letters</span> <span class="p">{</span> + <span class="nx">A</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">a</span><span class="dl">"</span> +<span class="p">}</span> +<span class="kr">enum</span> <span class="nx">Numbers</span> <span class="p">{</span> + <span class="nx">one</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span> + <span class="nx">two</span> <span class="o">=</span> <span class="nx">Letters</span><span class="p">.</span><span class="nx">A</span> +<span class="p">}</span> + +<span class="c1">// Now correctly an error</span> +<span class="kd">const</span> <span class="nx">t</span><span class="p">:</span> <span class="kr">number</span> <span class="o">=</span> <span class="nx">Numbers</span><span class="p">.</span><span class="nx">two</span><span class="p">;</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<h3 id="more-accurate-type-checking-for-parameter-decorators-in-constructors-under---experimentaldecorators"><strong>More Accurate Type-Checking for Parameter Decorators in Constructors Under <code class="language-plaintext highlighter-rouge">--experimentalDecorators</code></strong></h3> + +<p>decorator에 대한 좀 더 정확한 type-checking이 되도록 하는 옵션이 추가되었다. 특히 생성자 파라미터에서 사용할 때 효과적이다.</p> + +<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +</pre></td><td class="rouge-code"><pre><span class="k">export</span> <span class="kr">declare</span> <span class="kd">const</span> <span class="nx">inject</span><span class="p">:</span> + <span class="p">(</span><span class="nx">entity</span><span class="p">:</span> <span class="kr">any</span><span class="p">)</span> <span class="o">=&gt;</span> + <span class="p">(</span><span class="nx">target</span><span class="p">:</span> <span class="nx">object</span><span class="p">,</span> <span class="nx">key</span><span class="p">:</span> <span class="kr">string</span> <span class="o">|</span> <span class="nx">symbol</span><span class="p">,</span> <span class="nx">index</span><span class="p">?:</span> <span class="kr">number</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="k">void</span><span class="p">;</span> + +<span class="k">export</span> <span class="kd">class</span> <span class="nc">Foo</span> <span class="p">{}</span> + +<span class="k">export</span> <span class="kd">class</span> <span class="nc">C</span> <span class="p">{</span> + <span class="nf">constructor</span><span class="p">(@</span><span class="nd">inject</span><span class="p">(</span><span class="nx">Foo</span><span class="p">)</span> <span class="k">private</span> <span class="nx">x</span><span class="p">:</span> <span class="kr">any</span><span class="p">)</span> <span class="p">{</span> + <span class="p">}</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p><code class="language-plaintext highlighter-rouge">key</code>가 <code class="language-plaintext highlighter-rouge">string | symbol</code>을 예상하기에 이 호출은 실패하지만 생성자 파라미터는 <code class="language-plaintext highlighter-rouge">undefined</code>키를 받는다. 올바른 수정은 <code class="language-plaintext highlighter-rouge">inject</code>의 <code class="language-plaintext highlighter-rouge">key</code>의 타입을 변경하는 것이다. 만약 변경할 수 없는 라이브러리를 사용중인 경우에는 <code class="language-plaintext highlighter-rouge">inject</code>를 좀 더 type-safe한 decorator로 감싸고 <code class="language-plaintext highlighter-rouge">key</code>에 type assertion을 사용하는 것이다.</p> + +<h3 id="deprecations-and-default-changes"><strong>Deprecations and Default Changes</strong></h3> + +<p>이제 다음 설정값들은 사용되지 않는다.</p> + +<ul> + <li><code class="language-plaintext highlighter-rouge">--target: ES3</code></li> + <li><code class="language-plaintext highlighter-rouge">--out</code></li> + <li><code class="language-plaintext highlighter-rouge">--noImplicitUseStrict</code></li> + <li><code class="language-plaintext highlighter-rouge">--keyofStringsOnly</code></li> + <li><code class="language-plaintext highlighter-rouge">--suppressExcessPropertyErrors</code></li> + <li><code class="language-plaintext highlighter-rouge">--suppressImplicitAnyIndexErrors</code></li> + <li><code class="language-plaintext highlighter-rouge">--noStrictGenericChecks</code></li> + <li><code class="language-plaintext highlighter-rouge">--charset</code></li> + <li><code class="language-plaintext highlighter-rouge">--importsNotUsedAsValues</code></li> + <li><code class="language-plaintext highlighter-rouge">--preserveValueImports</code></li> + <li><code class="language-plaintext highlighter-rouge">prepend</code> in project references</li> +</ul> + +<p>Typescript 5.5까지는 허용하지만 이후로는 경고가 발생한다. 단, <code class="language-plaintext highlighter-rouge">"ignoreDeperecations": "5.0"</code>을 사용하면 경고를 없앨 수 있다.</p> + +<p>프로젝트 내에서 동일한 파일 이름을 참조하는 모든 참조가 케이스에 대해 동의하도록 보장하는 <code class="language-plaintext highlighter-rouge">--forceConsistentCasingInFileNames</code>는 이제 <code class="language-plaintext highlighter-rouge">true</code>로 기본 설정된다.</p> + +<p><br /></p> + +<h2 id="출처">출처</h2> + +<p>https://velog.io/@hustle-dev/TypeScript-5.0-%EB%B2%88%EC%97%AD#%EB%8D%B0%EC%BD%94%EB%A0%88%EC%9D%B4%ED%84%B0</p> + +<p>https://devblogs.microsoft.com/typescript/announcing-typescript-5-0/#moduleresolution-bundler</p> + + Mon, 17 Apr 2023 22:30:00 +0900 + https://seongil-shin.github.io/posts/typescript5/ + https://seongil-shin.github.io/posts/typescript5/ + + typescript + + + study + + typescript + + + + + next.js appDir + <h1 id="appdir">AppDir</h1> + +<p>next.js에서 layout과 routing 경험을 개선시키고 React 최신 기술을 지원하기위해, next.js 13부터 지원하는 디렉터리 방식. 기존의 <code class="language-plaintext highlighter-rouge">pages</code> 디렉터리를 대체한다.</p> + +<p>23년 04월 기준으로 아직은 베타버전으로 프로덕션에서 사용하는 건 추천하지 않는다고 한다. 하지만, 좋은 기능들이 많기에 학습해봐도 괜찮을 거 같다.</p> + +<h2 id="file-conventions">file conventions</h2> + +<p>app directory에서 파일 컨벤션은 다음과 같다.</p> + +<ul> + <li>page.js : 해당 path의 UI를 담당하며, public하게 접근할 수 있도록 함. + <ul> + <li>route.js : 서버 사이드 API endpoint<br /></li> + </ul> + </li> + <li>layout.js : route segment 간에 공유 가능한 UI. 자식 layout, page를 감쌈 + <ul> + <li>template.js : layout 아래에서 자식 layout, page를 감쌈.</li> + </ul> + </li> + <li>loading.js : 페이지를 로딩 중일 때 보여주는 UI. 페이지나 child segment를 React Suspense로 감싼다.</li> + <li>error.js : page나 child segment에서 에러가 발생했을 경우 보여줌. React Error Boundary 사용. + <ul> + <li>global-error.js : root layout에서 에러를 잡았을 때 보여주는 UI</li> + </ul> + </li> + <li>not-found.js : 해당 route segment 아래에서 매치되는 route가 없을 때 보여주는 UI</li> +</ul> + +<p><br /></p> + +<h2 id="layout">Layout</h2> + +<p>next.js 13부터는 path가 바뀌더라도 리렌더링 없이 이전 레이아웃을 유지할 수 있다. 이때 상태도 유지된다.</p> + +<p>사용법은 다음과 같다.</p> + +<ol> + <li>app directory를 사용한다.</li> + <li>특정 페이지 아래에서 어떤 파일을 <code class="language-plaintext highlighter-rouge">layout.js</code>로 생성하면, 그것이 레이아웃 컴포넌트이다.</li> +</ol> + +<div class="language-react highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +</pre></td><td class="rouge-code"><pre><span class="c1">// app/dashboard/layout.ts</span> +<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">DashboardLayout</span><span class="p">({</span> + <span class="nx">children</span><span class="p">,</span> <span class="c1">// will be a page or nested layout</span> +<span class="p">}:</span> <span class="p">{</span> + <span class="nl">children</span><span class="p">:</span> <span class="nx">React</span><span class="p">.</span><span class="nx">ReactNode</span><span class="p">,</span> +<span class="p">})</span> <span class="p">{</span> + <span class="k">return </span><span class="p">(</span> + <span class="p">&lt;</span><span class="nt">section</span><span class="p">&gt;</span> + <span class="si">{</span><span class="cm">/* Include shared UI here e.g. a header or sidebar */</span><span class="si">}</span> + <span class="p">&lt;</span><span class="nt">nav</span><span class="p">&gt;&lt;/</span><span class="nt">nav</span><span class="p">&gt;</span> + + <span class="si">{</span><span class="nx">children</span><span class="si">}</span> + <span class="p">&lt;/</span><span class="nt">section</span><span class="p">&gt;</span> + <span class="p">);</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>참고</p> + +<ul> + <li>모든 route segment는 자신만의 layout을 가질 수 있다. 이 layout은 그 segment 아래의 모든 페이지에 적용된다.</li> + <li>layout은 디폴트로 nested된다. 각 부모 layout은 자식 layout을 감싼다.</li> + <li>Route Groups를 지정하여 특정한 route segment에만 layout을 공유할 수도 있다.</li> + <li>Layout은 디폴트로 Server Component이다. 다만 Client Component로 설정할 수 있다.</li> + <li>부모 자식 layout 간에 데이터를 주고 받는건 불가능하다. 하지만, 같은 데이터를 fetch 하면 리액트에서 자동으로 요청을 중복제거한다.</li> + <li><code class="language-plaintext highlighter-rouge">layout.js</code>와 <code class="language-plaintext highlighter-rouge">page.js</code>를 같은 폴더에 생성할 수 있다. <code class="language-plaintext highlighter-rouge">layout.js</code>가 <code class="language-plaintext highlighter-rouge">page.js</code>를 감싼다.</li> +</ul> + +<h3 id="root-layout-required">Root Layout (Required)</h3> + +<p>app directory의 최상단에 반드시 있어야함. 이 root layout을 통해 클라이언트에서 최초에 받는 HTML을 수정할 수 있다. (<code class="language-plaintext highlighter-rouge">pages</code> 디렉터리에서의 <code class="language-plaintext highlighter-rouge">_document.js</code> 같은 느낌)</p> + +<p>참고</p> + +<ul> + <li><code class="language-plaintext highlighter-rouge">&lt;html&gt;</code>, <code class="language-plaintext highlighter-rouge">&lt;body&gt;</code>를 반드시 가지고 있어야한다.</li> + <li><code class="language-plaintext highlighter-rouge">&lt;head&gt;</code>를 수정하여 SEO를 지원할 수 있음.</li> + <li>Route Group을 사용하여 여러 root layout을 가질 수 있음.</li> + <li>root layout은 서버컴포넌트로, 클라이언트 컴포넌트로 설정할 수 없다.</li> +</ul> + +<h3 id="template">template</h3> + +<p>layout과 같이 child layout, page를 감싸지만, routing에 거쳐 상태가 유지되지않는다. path가 변경되면 리렌더링된다. 사용하려면, route segment에 <code class="language-plaintext highlighter-rouge">template.js</code>로 생성하면 된다. 그럼 다음과 같이 layout 아래에서 자식 컴포넌트를 감싸도록 적용된다.</p> + +<div class="language-react highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +</pre></td><td class="rouge-code"><pre><span class="p">&lt;</span><span class="nc">Layout</span><span class="p">&gt;</span> + <span class="si">{</span><span class="cm">/* Note that the template is given a unique key. */</span><span class="si">}</span> + <span class="p">&lt;</span><span class="nc">Template</span> <span class="na">key</span><span class="p">=</span><span class="si">{</span><span class="nx">routeParam</span><span class="si">}</span><span class="p">&gt;</span><span class="si">{</span><span class="nx">children</span><span class="si">}</span><span class="p">&lt;/</span><span class="nc">Template</span><span class="p">&gt;</span> +<span class="p">&lt;/</span><span class="nc">Layout</span><span class="p">&gt;</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p><br /></p> + +<h2 id="server-component">Server component</h2> + +<p>app directory를 사용하면, 서버컴포넌트를 간편하게 사용할 수 있다. 서버컴포넌트를 사용하면, 클라이언트에게 최초로 보내지는 JS 코드를 줄일 수 있어 빠른 페이지로딩에 도움을 준다.</p> + +<p>app directory 아래에 있는 모든 컴포넌트는 기본적으로 서버컴포넌트이다. 따라서 서버컴포넌트를 사용하기 위해 별다른 작업이 필요없다.</p> + +<p>route가 next.js에서 로딩되면, 최초의 HTML은 서버에서 렌더링 된다. 그리고 그 HTML는 점진적으로 브라우저에 전달된다. 이때 next.js와 react client-side runtime을 비동기적으로 로딩한다. 이로써 클라이언트가 어플리케이션을 다루고, 인터랙션할 수 있게 된다.</p> + +<p>서버컴포넌트를 사용하면 최초 페이지 로딩은 빨라지고, client-side runtime은 캐시될 수 있으며 사이즈를 예측할 수 있다. 그리고 이 최초로 내려지는 JS 번들 사이즈는 어플리케이션 크기가 커지는만큼 따라 커지지 않는다. 클라이언트 컴포넌트를 통해 어플리케이션에 추가된 client-side interactivity만큼만 증가하게 된다.</p> + +<p>app directory에서 network boundary는 서버 컴포넌트냐, 클라이언트 컴포넌트냐에 달려있다. pages directory에서는 <code class="language-plaintext highlighter-rouge">getStaticProps</code>, <code class="language-plaintext highlighter-rouge">getServerSideProps</code>에 달려있다.</p> + +<h3 id="client-component">Client Component</h3> + +<p>next.js에서 클라이언트 컴포넌트는 서버에서 prerender 되고 클라이언트에서 hydration 된다. <code class="language-plaintext highlighter-rouge">pages/</code> 디렉터리에서 동작하는 방식이다. app 디렉터리에서는 다음과 같이 파일 상단에 <code class="language-plaintext highlighter-rouge">"use client";</code>를 작성하면 클라이언트 컴포넌트가 된다.</p> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +</pre></td><td class="rouge-code"><pre><span class="dl">'</span><span class="s1">use client</span><span class="dl">'</span><span class="p">;</span> +<span class="k">import</span> <span class="p">{</span> <span class="nx">useState</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">react</span><span class="dl">'</span><span class="p">;</span> + +<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">Counter</span><span class="p">()</span> <span class="p">{</span> + <span class="kd">const</span> <span class="p">[</span><span class="nx">count</span><span class="p">,</span> <span class="nx">setCount</span><span class="p">]</span> <span class="o">=</span> <span class="nf">useState</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span> + + <span class="k">return </span><span class="p">(</span> + <span class="o">&lt;</span><span class="nx">div</span><span class="o">&gt;</span> + <span class="o">&lt;</span><span class="nx">p</span><span class="o">&gt;</span><span class="nx">You</span> <span class="nx">clicked</span> <span class="p">{</span><span class="nx">count</span><span class="p">}</span> <span class="nx">times</span><span class="o">&lt;</span><span class="sr">/p</span><span class="err">&gt; +</span> <span class="o">&lt;</span><span class="nx">button</span> <span class="nx">onClick</span><span class="o">=</span><span class="p">{()</span> <span class="o">=&gt;</span> <span class="nf">setCount</span><span class="p">(</span><span class="nx">count</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)}</span><span class="o">&gt;</span><span class="nx">Click</span> <span class="nx">me</span><span class="o">&lt;</span><span class="sr">/button</span><span class="err">&gt; +</span> <span class="o">&lt;</span><span class="sr">/div</span><span class="err">&gt; +</span> <span class="p">);</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>이때 클라이언트 번들로 포함되는 컴포넌트들은 다음과 같다.</p> + +<ul> + <li><code class="language-plaintext highlighter-rouge">"use client";</code>가 작성된 컴포넌트</li> + <li><code class="language-plaintext highlighter-rouge">"use client";</code>가 작성된 컴포넌트에 import된 모든 컴포넌트들</li> + <li>자식컴포넌트</li> +</ul> + +<p>따라서 클라이언트 컴포넌트를 사용할 때는 최대한 말단 컴포넌트에서 사용할 것을 추천하고 있다.</p> + +<h3 id="when-to-use-server-component-vs-client-component">when to use server component vs client component?</h3> + +<p>next.js 팀에서는 서버 컴포넌트를 기본으로 사용하다가 클라이언트 컴포넌트는 필요할 때만 사용할 것을 추천한다. 그럼 언제 클라이언트 컴포넌트가 필요할까?</p> + +<ul> + <li>interactivity나 event listener(onClick(), onChange() 등)가 필요할 때.</li> + <li>lifecycle effect나 state가 필요할 때</li> + <li>browser-only API를 사용할 때</li> + <li>state, effect, browser-only API에 의존하는 custom hook을 사용할 때</li> + <li>React Class Component를 사용할 때</li> +</ul> + +<h3 id="클라이언트-컴포넌트에서-서버컴포넌트-사용하기">클라이언트 컴포넌트에서 서버컴포넌트 사용하기</h3> + +<p>리액트에서 클라이언트 컴포넌트안에 서버컴포넌트를 importing 하는 건 제약이 있다. 서버 컴포넌트는 server-only code 이기 때문이다. 따라서 다음과 같이 클라이언트 컴포넌트에선 서버 컴포넌트를 import할 수 없다.</p> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +</pre></td><td class="rouge-code"><pre><span class="dl">'</span><span class="s1">use client</span><span class="dl">'</span><span class="p">;</span> + +<span class="c1">// ❌ This pattern will not work. You cannot import a Server</span> +<span class="c1">// Component into a Client Component</span> +<span class="k">import</span> <span class="nx">ServerComponent</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./ServerComponent</span><span class="dl">'</span><span class="p">;</span> + +<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">ClientComponent</span><span class="p">()</span> <span class="p">{</span> + <span class="k">return </span><span class="p">(</span> + <span class="o">&lt;&gt;</span> + <span class="o">&lt;</span><span class="nx">ServerComponent</span> <span class="o">/&gt;</span> + <span class="o">&lt;</span><span class="sr">/</span><span class="err">&gt; +</span> <span class="p">);</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>하지만 클라이언트 컴포넌트에 props로 넘기는 것은 가능하다</p> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +</pre></td><td class="rouge-code"><pre><span class="dl">'</span><span class="s1">use client</span><span class="dl">'</span><span class="p">;</span> + +<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">ClientComponent</span><span class="p">({</span><span class="nx">children</span><span class="p">})</span> <span class="p">{</span> + <span class="k">return </span><span class="p">(</span> + <span class="o">&lt;&gt;</span> + <span class="p">{</span><span class="nx">children</span><span class="p">}</span> + <span class="o">&lt;</span><span class="sr">/</span><span class="err">&gt; +</span> <span class="p">);</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +</pre></td><td class="rouge-code"><pre><span class="c1">// ✅ This pattern works. You can pass a Server Component</span> +<span class="c1">// as a child or prop of a Client Component.</span> +<span class="k">import</span> <span class="nx">ClientComponent</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./ClientComponent</span><span class="dl">"</span><span class="p">;</span> +<span class="k">import</span> <span class="nx">ServerComponent</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./ServerComponent</span><span class="dl">"</span><span class="p">;</span> + +<span class="c1">// Pages are Server Components by default</span> +<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">Page</span><span class="p">()</span> <span class="p">{</span> + <span class="k">return </span><span class="p">(</span> + <span class="o">&lt;</span><span class="nx">ClientComponent</span><span class="o">&gt;</span> + <span class="o">&lt;</span><span class="nx">ServerComponent</span> <span class="o">/&gt;</span> + <span class="o">&lt;</span><span class="sr">/ClientComponent</span><span class="err">&gt; +</span> <span class="p">);</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>이렇게 사용하면 리액트는 클라이언트 컴포넌트로 child를 넘기기 전에 <code class="language-plaintext highlighter-rouge">&lt;ServerComponent/&gt;</code>를 서버에서 렌더링해야한다는 것을 안다. 클라이언트 컴포넌트의 입장에서는 child 가 이미 렌더링 되어있다. layout이 이 방식으로 구현되어있다.</p> + +<h3 id="passing-props-from-server-to-client-component">passing props from server to client component</h3> + +<p>서버 컴포넌트에서 클라이언트 컴포넌트로 props를 넘길 땐 serialization 될 필요가 있다. function, Date 등은 직접 클라이언트 컴포넌트로 넘길 수 없다는 것이다.</p> + +<h3 id="server-only-code가-클라이언트-컴포넌트에서-실행되는-것을-막는-방법">server-only code가 클라이언트 컴포넌트에서 실행되는 것을 막는 방법</h3> + +<p><code class="language-plaintext highlighter-rouge">server-only</code> 패키지를 사용하여, 서버에서만 실행되어야하는 코드가 클라이언트 컴포넌트에서 사용되면 build-time error를 내도록 할 수 있다.</p> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +</pre></td><td class="rouge-code"><pre><span class="k">import</span> <span class="dl">"</span><span class="s2">server-only</span><span class="dl">"</span><span class="p">;</span> + +<span class="k">export</span> <span class="k">async</span> <span class="kd">function</span> <span class="nf">getData</span><span class="p">()</span> <span class="p">{</span> + <span class="kd">let</span> <span class="nx">resp</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">fetch</span><span class="p">(</span><span class="dl">"</span><span class="s2">https://external-service.com/data</span><span class="dl">"</span><span class="p">,</span> <span class="p">{</span> + <span class="na">headers</span><span class="p">:</span> <span class="p">{</span> + <span class="na">authorization</span><span class="p">:</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">API_KEY</span><span class="p">,</span> + <span class="p">},</span> + <span class="p">});</span> + + <span class="k">return</span> <span class="nx">resp</span><span class="p">.</span><span class="nf">json</span><span class="p">();</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>마찬가지로 <code class="language-plaintext highlighter-rouge">client-only</code> 패키지도 제공한다. 이 패키지는 <code class="language-plaintext highlighter-rouge">window</code> 객체를 사용하는 함수와 같이 클라이언트에서만 사용되어야하는 함수에서 사용할 수 있다.</p> + +<h3 id="third-party-packages">third party packages</h3> + +<p>현재 많은 서드파티 패키지들은 <code class="language-plaintext highlighter-rouge">useState</code> 등을 사용함에도 <code class="language-plaintext highlighter-rouge">use client</code>를 붙이지 않은 경우가 많다. 이러한 서드파티 패키지를 클라이언트 컴포넌트에서 사용하는 것은 큰 문제가 없다. 클라이언트 컴포넌트에 이미 <code class="language-plaintext highlighter-rouge">use client</code>가 붙어있기 때문이다. 하지만 서버 컴포넌트에서 사용할 땐 오류가 뜰 것이다. 따라서 서버 컴포넌트에서 사용할 경우엔 다음과 같이 서드파티 패키지를 감싸고 사용하면 된다.</p> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +</pre></td><td class="rouge-code"><pre><span class="dl">'</span><span class="s1">use client</span><span class="dl">'</span><span class="p">;</span> + +<span class="k">import</span> <span class="p">{</span> <span class="nx">AcmeCarousel</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">acme-carousel</span><span class="dl">'</span><span class="p">;</span> + +<span class="k">export</span> <span class="k">default</span> <span class="nx">AcmeCarousel</span><span class="p">;</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +</pre></td><td class="rouge-code"><pre><span class="k">import</span> <span class="nx">Carousel</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./carousel</span><span class="dl">'</span><span class="p">;</span> + +<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">Page</span><span class="p">()</span> <span class="p">{</span> + <span class="k">return </span><span class="p">(</span> + <span class="o">&lt;</span><span class="nx">div</span><span class="o">&gt;</span> + <span class="o">&lt;</span><span class="nx">p</span><span class="o">&gt;</span><span class="nx">View</span> <span class="nx">pictures</span><span class="o">&lt;</span><span class="sr">/p</span><span class="err">&gt; +</span> + <span class="p">{</span><span class="cm">/* 🟢 Works, since Carousel is a Client Component */</span><span class="p">}</span> + <span class="o">&lt;</span><span class="nx">Carousel</span> <span class="o">/&gt;</span> + <span class="o">&lt;</span><span class="sr">/div</span><span class="err">&gt; +</span> <span class="p">);</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<h3 id="data-fetching">data fetching</h3> + +<p>클라이언트 컴포넌트에서도 data feching은 할 수 있지만, 가능한 서버 컴포넌트에서 하는 것이 성능상 좋다.</p> + +<h3 id="context">context</h3> + +<p>클라이언트 컴포넌트에서는 context나 provider를 사용하여 context를 공유할 수 있다. 하지만, 서버 컴포넌트에서는 react state를 가지고 있지 않고, context는 보통 리렌더링할 때 필요하기 때문이다.</p> + +<p>따라서 next.js 팀은 서버 컴포넌트 간에 데이터 교환을 위해 native javascript 패턴을 제안한다. 예를들어 만약 여러 서버 컴포넌트 사이에서 DB connection을 공유해야한다면 다음과 같이 사용할 수 있다.</p> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +</pre></td><td class="rouge-code"><pre><span class="c1">// utils/database.js</span> +<span class="k">export</span> <span class="kd">const</span> <span class="nx">db</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DatabaseConnection</span><span class="p">(...);</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +</pre></td><td class="rouge-code"><pre><span class="c1">// app/users/layout.js</span> +<span class="k">import</span> <span class="p">{</span> <span class="nx">db</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@utils/database</span><span class="dl">"</span><span class="p">;</span> + +<span class="k">export</span> <span class="k">async</span> <span class="kd">function</span> <span class="nf">UsersLayout</span><span class="p">()</span> <span class="p">{</span> + <span class="kd">let</span> <span class="nx">users</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">db</span><span class="p">.</span><span class="nf">query</span><span class="p">(...);</span> + <span class="c1">// ...</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +</pre></td><td class="rouge-code"><pre><span class="c1">// app/users/[id]/pages.js</span> +<span class="k">import</span> <span class="p">{</span> <span class="nx">db</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@utils/database</span><span class="dl">"</span><span class="p">;</span> + +<span class="k">export</span> <span class="k">async</span> <span class="kd">function</span> <span class="nf">DashboardPage</span><span class="p">()</span> <span class="p">{</span> + <span class="kd">let</span> <span class="nx">user</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">db</span><span class="p">.</span><span class="nf">query</span><span class="p">(...);</span> + <span class="c1">// ...</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>한편, 서버 컴포넌트에서 fetch 요청을 자동으로 중복제거된다. 따라서 fetch request result를 어떻게 공유할지는 걱정하지 않아도 된다. revalidate 등은 어떻게 하는지는 밑에 data fetching 섹션에서 다룬다.</p> + +<p><br /></p> + +<h2 id="support-for-data-fetching">Support for data fetching</h2> + +<p><code class="language-plaintext highlighter-rouge">fetch</code> Web API는 React와 next.js에서 확장되었다.</p> + +<ul> + <li>React에서는 automatic request deduping을 위해 fetch를 확장하였다.</li> + <li>next.js에서는 options를 확장하여 caching/revalidating 규칙을 설정하였다.</li> +</ul> + +<p>이를 통해 next.js에서는 하나의 API로 SSG, SSR, ISR을 사용가능하다. (<a href="https://beta.nextjs.org/docs/data-fetching/fetching">fetch에 대해서</a>)</p> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +</pre></td><td class="rouge-code"><pre><span class="c1">// This request should be cached until manually invalidated.</span> +<span class="c1">// Similar to `getStaticProps`.</span> +<span class="c1">// `force-cache` is the default and can be omitted.</span> +<span class="nf">fetch</span><span class="p">(</span><span class="nx">URL</span><span class="p">,</span> <span class="p">{</span> <span class="na">cache</span><span class="p">:</span> <span class="dl">'</span><span class="s1">force-cache</span><span class="dl">'</span> <span class="p">});</span> + +<span class="c1">// This request should be refetched on every request.</span> +<span class="c1">// Similar to `getServerSideProps`.</span> +<span class="nf">fetch</span><span class="p">(</span><span class="nx">URL</span><span class="p">,</span> <span class="p">{</span> <span class="na">cache</span><span class="p">:</span> <span class="dl">'</span><span class="s1">no-store</span><span class="dl">'</span> <span class="p">});</span> + +<span class="c1">// This request should be cached with a lifetime of 10 seconds.</span> +<span class="c1">// Similar to `getStaticProps` with the `revalidate` option.</span> +<span class="nf">fetch</span><span class="p">(</span><span class="nx">URL</span><span class="p">,</span> <span class="p">{</span> <span class="na">next</span><span class="p">:</span> <span class="p">{</span> <span class="na">revalidate</span><span class="p">:</span> <span class="mi">10</span> <span class="p">}</span> <span class="p">});</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>next.js에서는 웬만하면 서버 컴포넌트에서 data fetching 수행을 권장한다. 다음과 같은 이점이 있기 때문이다.</p> + +<ul> + <li>데이터 리소스로의 직접 접근</li> + <li>API key와 같은 민감한 정보의 노출 예방</li> + <li>data fetching과 render가 같은 환경에서 이루어짐. 따라서 클라이언트와 서버 사이의 통신이 줄어든다.</li> + <li>multiple data fetching을 하나의 round-trip으로 실행한다. 클라이언트에서 개별적으로 이루어지는 것과 다르다.</li> + <li>클라이언트-서버 waterfall을 줄임</li> + <li>서버가 데이터 리소스에 가까이 있다면 성능 향상의 효과가 있음.</li> +</ul> + +<p>클라이언트 컴포넌트에서도 data fetching은 가능하지만, 그때는 SWR나 React Query 사용을 추천하고 있다. 향후 React의 <code class="language-plaintext highlighter-rouge">use()</code> hook을 사용할 수도 있다고 한다.</p> + +<h3 id="component-level-data-fetching">component-level data fetching</h3> + +<p>컴포넌트에서 data fetching을 수행할때 두가지 모델이 있다.</p> + +<ul> + <li>parallel : route 내 request가 동시에 이루어진다. 이는 client-server waterfall을 줄이며 데이터 로딩에 걸리는 총 시간을 줄인다.</li> + <li>sequential : route 내 request가 순차적으로 이루어진다. 하나의 요청이 다른 요청에 의존적일때 사용할 수 있다.</li> +</ul> + +<p><img src="/assets/img/sequential-parallel-data-fetching.png" alt="sequential-parallel-data-fetching" /></p> + +<p>또한 위에 언급했듯이 <code class="language-plaintext highlighter-rouge">fetch()</code>은 자동으로 deduping 된다. 만약 트리내 여러 컴포넌트에서 같은 데이터를 원한다면, next.js는 자동으로 fetch request(GET)를 캐싱하고 같은 요청이 발생하면 저장했던 것을 돌려준다.</p> + +<ul> + <li>서버에서는 렌더링 프로세스가 종료될때까지 캐시가 유지된다. + <ul> + <li>이는 Layout, Page, Server Component, generateMetadata, generateStaticParams에서 작성된 <code class="language-plaintext highlighter-rouge">fetch</code> 요청에 적용된다.</li> + <li><a href="https://beta.nextjs.org/docs/rendering/fundamentals#static-rendering">static generation</a> 동안에도 적용된다.</li> + </ul> + </li> + <li>클라이언트에서는 세션 동안 캐시가 유지된다.</li> +</ul> + +<p>만약 <code class="language-plaintext highlighter-rouge">fetch()</code> 를 사용할 수 없는 상황이라면 <a href="https://beta.nextjs.org/docs/data-fetching/caching#per-request-caching">cache</a> function을 사용해도 된다.</p> + +<h3 id="static-and-dynamic-data-fetches">static and dynamic data fetches</h3> + +<p>두가지 타입의 데이터가 존재한다.</p> + +<ul> + <li>static data : 자주 변경되지 않는 데이터</li> + <li>dynamic data : 자주 변경되거나 유저 별로 다른 데이터</li> +</ul> + +<p>기본적으로 next.js에서는 static fetch를 한다. 이는 빌드타임에 fetch되어 캐시되고 각 요청마다 사용한다는 말이다. 하지만 dynamic fetch를 해야할 때도 있는데, next.js는 캐시를 무효화하거나 revalidating 하는 방법을 제공한다.</p> + +<ul> + <li>캐시 무효화</li> +</ul> + +<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +</pre></td><td class="rouge-code"><pre><span class="nf">fetch</span><span class="p">(</span><span class="nx">URL</span><span class="p">,</span> <span class="p">{</span> <span class="na">cache</span><span class="p">:</span> <span class="dl">'</span><span class="s1">no-store</span><span class="dl">'</span> <span class="p">});</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<ul> + <li> + <p>revalidating</p> + + <ul> + <li> + <p>background : time interval을 주어 주기적으로 다시 fetch</p> + + <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +</pre></td><td class="rouge-code"><pre><span class="nf">fetch</span><span class="p">(</span><span class="nx">URL</span><span class="p">,</span> <span class="p">{</span> <span class="na">next</span><span class="p">:</span> <span class="p">{</span> <span class="na">revalidate</span><span class="p">:</span> <span class="mi">10</span> <span class="p">}</span> <span class="p">});</span> +</pre></td></tr></tbody></table></code></pre></div> </div> + </li> + <li> + <p>on-demand : update가 있을 때마다 refetch (<a href="https://beta.nextjs.org/docs/data-fetching/revalidating#on-demand-revalidation">예제</a>)</p> + </li> + </ul> + </li> +</ul> + +<p>next.js에서는 <strong>Streaming</strong>과 <strong>Suspense</strong>에 대한 지원도 잘 되어있다. Layout 섹션에서 보았듯이 화면의 일부는 즉시 내려보내고, 일부는 비동기적으로 <a href="https://beta.nextjs.org/docs/routing/loading-ui">loading</a> 화면을 보여주었다가 data fetching이 완료되면 내려보낸다. 이를 통해 유저는 페이지의 모든 부분을 로드할 필요가 없다.</p> + +<h3 id="caching-data">caching data</h3> + +<blockquote> + <p>The <strong>Next.js Cache</strong> is a persistent <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching">HTTP cache</a> that can be globally distributed. This means the cache can scale automatically and be shared across multiple regions depending on your platform (e.g. <a href="https://vercel.com/docs/concepts/next.js/overview">Vercel</a>).</p> +</blockquote> + +<p>next.js는 <code class="language-plaintext highlighter-rouge">fetch()</code>의 options 객체를 확장하여 서버에서 이루어지는 각 요청이 persistent caching 동작이 이루어지도록 설정할 수 있다. 이를 통해 데이터를 사용하는 어플리케이션 코드에서 캐싱 동작을 설정할 수 있다.</p> + +<p><br /></p> + +<h2 id="출처">출처</h2> + +<p>https://beta.nextjs.org/docs/routing/fundamentals</p> + + Thu, 13 Apr 2023 17:00:00 +0900 + https://seongil-shin.github.io/posts/appdir/ + https://seongil-shin.github.io/posts/appdir/ + + next.js + + + study + + next.js + + + + + \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 000000000..17e7b60f9 --- /dev/null +++ b/index.html @@ -0,0 +1 @@ + 디피의 개발일지
Posts
디피의 개발일지
Cancel

JS bundler, package manager, compiler

프론트엔드 관련 아티클을 읽다보면 다양한 bundler, package manger, compiler 도구에 대한 얘기가 나온다. 그때마다 익숙지않은 도구의 이름(pnpm, swc, vite 등)이 나오는데 뭐가 뭔지 잘 이해가 안간다. 따라서 각각의 역할이 무엇인지, 어떤 종류가 있고 어떤 장단이 있는지 간단히 정리해보려고 한다. Bundler ...

How I Use "AI"

원본 작성자가 언제 어떻게 LLM 을 활용하는지 공유하는 포스트 애플리케이션을 만드는 초기 단계에서 전체적인 베이스를 만드는데 도움을 받았음 새로운 기술의 튜터로써 코드 단순화를 위해 단조로운 작업을 빠르게 처리하기 위해 불규칙적인 작업이더라도 LLM이 패턴을 파악하여 결과물을 내게할 수 있음 ...

Esbuild to reduce build times by 90%

원본 기존에 빌드가 오래 걸렸던 원인들 웹팩, 롤업 작업 시간이 대부분이었음 작은 의존성들이 하나씩 빌드됨. 이것들은 동시에 진행될 여지가 있음 몇몇 의존성은 필요한 것보다 오래 빌드됨. 변경되지 않은 파일에 대해서 typeshare 를 재실행하지 않기위해 find를 실행했으나, 그냥 typeshare를 실행하는 것보다 find 실행...

실용주의프로그래머 - 전체 후기

이 책은 프로젝트 과정 전반에 거쳐 소프트웨어 전문가답게 진행하는 방법에 대해 서술되어있다. 전반적으로 이해가 잘되도록 쓰여져 있고, 현인의 어록과도 같은 책이라 참고할만한 부분도 많이 있다. 하지만 조직문화나 권한의 한계, 물리적인 시간의 한계로 모든 팁을 다 따르기는 어렵다. 또한 이러한 내용은 완전히 습관이 되어야하는데, 긴 책 내용을 전부 기...

실용주의프로그래머 9장 실용주의 프로젝트

Topic 49 : 프로젝트 참여인원이 복수가 되었을때 실용적으로 각 부분을 위임하는 법 Topic 50 : 팀에게 잘 맞는 방법론을 사용하고 있는가 Topic 51 : 안정적인 소프트웨어를 지속적으로 생산해내는 방법 Topic 52 : 사용자를 기쁘게하는 방법 Topic 53 : 자신의 작업에 자부심을 가져라 Topic 49 ...

실용주의프로그래머 8장 프로젝트 전에

Topic 45 : 요구사항의 구렁텅이 자신이 뭘 원하는지 정확히 아는 사람은 아무도 없다. 요구사항 미신 무엇을 다루든 정확한 명세란 것은 거의 불가능하다. 이때 프로그래머는 사람들이 자신이 무엇을 원하는지 깨닫도록 도와줄수 있다. 프로그래머는 사람들이 자신이 원하는 바를 꺠닫도록 돕는다. 상담 치료로서의 프로그래밍 신입 개...

잘못된 DRY

https://velog.io/@eunbinn/dry-the-common-source-of-bad-abstractions DRY 원칙을 너무 지키다보니 잘못된 추상화를 하는 경우가 있다 바로 서로 성격이 다른 코드이지만, 비슷한 패턴을 가진다는 이유로 같은 코드로 묶는 경우이다. ex) 네비게이션에 Article, Menu, Buy 버튼...

실용주의프로그래머 7장 코딩하는 동안

코딩은 설계내용을 단순히 코드로 바꾸는 과정이 아니다. 이번 장에서는 프로그램을 정확하게 동작하게 하려면 어떻게 해야하는지 다룬다. 운전할 때와 마찬가지로 코딩도 반복적인 일이지만 정신을 늘 기민하게 유지하여야한다. topic 37 : 본능과 무의식적인 생각을 더 잘 활용할 수 있다 topic 38 : 우연에 맡기는 프로그래밍을 피하는 방법...

실용주의프로그래머 6장 동시성

동시성 : 둘 이상의 코드 조작이 실행될 때 동시에 실행 중인 것처럼 행동하는 것. 소프트웨어 동작 코드의 다른 부분으로 실행을 전환할 수 있는 환경에서 코드를 구동해야함. ex) 스레드, 프로세스 병렬성 : 실제로 동시에 실행되는 것. 하드웨어 동작 두가지 일을 동시에 할 수 있는 하드...

실용주의프로그래머 5장 구부러지거나 부러지거나

이번 장에서는 되돌릴 수 있는 의사결정을 내리는 구체적인 방법을 설명한다. topic 28 : 결합도 줄이기 topic 29 : 실세계를 갖고 저글링하기. 이벤트에 반응하는 네 가지 서로 다른 전략 topic 30 : 변환 프로그래밍. 함수 파이프라인 topic 31 : 상속세. 유연하고 바꾸기 쉬운 코드를 만들 수 있는 대안 t...

diff --git a/norobots/index.html b/norobots/index.html new file mode 100644 index 000000000..5038baf35 --- /dev/null +++ b/norobots/index.html @@ -0,0 +1,11 @@ + + + + Redirecting… + + + + +

Redirecting…

+ Click here if you are not redirected. + diff --git a/page10/index.html b/page10/index.html new file mode 100644 index 000000000..ff27132a6 --- /dev/null +++ b/page10/index.html @@ -0,0 +1 @@ + 디피의 개발일지
Home
디피의 개발일지
Cancel

클래스 초기화 블록

클래스 초기화 블록 어떤 클래스에 static 변수를 추가했더니 github copilot이 다음과 같은 문장을 추천해주었다. public class Crypto { private static CryptoProperties cryptoProperties; // github copilot suggestion static { crypto...

pinpoint

Pinpoint Pinpoint는 대규모 분산 시스템의 성능을 분석하고 문제를 진단, 처리하는 플랫폼입니다. 2012년 7월에 개발을 시작해 2015년 1월 9일에 오픈소스로 공개했습니다 Pinpoint 개발 동기와 Pinpoint의 특징 인터넷 서비스의 시스템 복잡도가 증가함에 따라 장애나 성능 문제가 발생했을 때 해결이 어려워졌다....

istio

Istio istio는 service mesh를 위한 tool입니다. istio를 사용하면 MSA(Micro Service Architecture)를 적용할 때 반복적으로 설정해야하는 L7/모니터링을 쉽고 간편하게 설정할 수 있습니다. Service mesh Service mesh는 API 등을 사용하여 마이크로 서비스 간 통신을 안전하고...

사이드카 패턴

사이드카 패턴 쿠버네티스의 패턴 중 하나로, 어플리케이션 컨테이너와 독립적으로 동작하는 별도의 컨테이너를 붙이는 패턴이다. 어플리케이션 컨테이너의 변경이나 수정 없이 독립적으로 동작하는 컨테이너를 붙였다 뗐다 할 수 있다. 파드에서의 사이드카 파드는 쿠버네티스에서 가장 기본적인 배포 단위로서 자신에게 속한 컨테이너들에게 런타임제약을 걸 수 있...

API 예외처리

예외 발생 시에도 JSON 응답 보내기 ResponseEntity를 사용해서 응답을 보내면 된다. public ResponseEntity<String> errorPage500Api() { String result = "error"; return new ResponseEntity(result, HttpStatus.BAD_REQUEST....

AOP

AOP = Aspect Oriented Programming = 관점 지향 프로그래밍 관점 지향이란 어떤 로직을 기준으로 핵심적인 관점, 부가적인 관점으로 나누어서 보고 그 관점을 기준으로 모듈화 하겠다는 것이다. 여기서 모듈화란 어떤 공통된 로직이나 기능을 하나의 단위로 묶는 것을 말한다. AOP에서 각 관점을 기준으로 모듈화한다는 것은 ...

ModelAttribute와 NoArgsConstructor 추가 시 아무것도 안들어가는 이슈

제목 그대로 다음과 같이 @NoArgsConstructor 애노테이션을 @ModelAttribute로 적용되는 컨트롤러 파라미터 클래스에 추가하니 제대로 쿼리를 날려도 아무것도 안들어가는 이슈가 발생했다. @GetMapping("/article") public ResponseEntity getArticles(@ModelAttribute SelectA...

컨트롤러 매개변수/반환 타입 유형

요청 파라미터(쿼리, form 데이터) HttpServletRequest @RequestMapping("/request-param-v1") public void requestParamV1(HttpServletRequest request, HttpServletResponse response) HttpServletRequest에서 조회해서 꺼내올...

예외 처리와 오류페이지

서블릿 예외처리 서블릿의 예외처리 방식 Exception WAS <- 서블릿 컨테이너 <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러(예외발생) 웹 애플리케이션에서 발생한 예외를 잡지 못하면, 예외는 서블릿 컨테이너에까지 전달된다. 서블릿 컨테이너는 넘어온 예외를 기준으로 등록된 오류페이지를 조회한...

스프링 MVC 구조

과정 클라이언트가 서버로 요청하면 먼저 Dispatcher Servlet 받음 Dispatcher Servlet은 http 요청의 URL과 메소드를 보고 이것을 처리할 수 있는 핸들러(컨트롤러)를 찾기 위해 핸들러 매핑 과정을 진행한다. 핸들러를 찾으면 이 핸들러를 처리할 수 있는 핸들러 어댑터를 조회한다. 핸들러 어댑터를 실행시...

diff --git a/page11/index.html b/page11/index.html new file mode 100644 index 000000000..6ee8cae4f --- /dev/null +++ b/page11/index.html @@ -0,0 +1 @@ + 디피의 개발일지
Home
디피의 개발일지
Cancel

서블릿 필터

서블릿 필터 스프링에서 공통 관심사를 처리하기 위한 방법으로, 스프링 인터셉터와 함께 쓰이는 방법이다. 스프링 AOP를 사용하여 공통관심사를 처리할 수 있지만 웹과 관련된 공통 관심사는 서블릿 필터나 스프링 인터셉터를 사용하는 것이 좋다. 서플릿 필터나 스프링 인터셉터는 HttpServletRequest를 제공해주기 때문이다. 소개 필터 흐름 ...

쿠키 인증 vs 세션 vs JWT

쿠키 사용자가 로그인하면, 사용자 식별자를 암호화 또는 평문으로 쿠키에 담는 방식. 요청이 오면 쿠키에 담긴 식별자를 보고 유저를 구분한다. 장점 유효한 쿠키인지만 확인하면 되기에 서버자원과 비용을 아낄 수 있다. 서버를 무상태(stateless)로 만들어줌. 암호화를 하면 brute force 대응 ...

프론트 아키텍쳐 흐름

프론트 아키텍처 흐름 MVC Model - View -Controller로 나눈 아키텍처 Model 컨트롤러가 호출했을때, 요청에 맞는 역할을 수행한다. 비즈니스 로직을 구현하는 영역으로 응용프로그램에서 데이터를 처리하는 부분 뷰나 컨트롤러에 대해서 어떤 정보도 알지 말아야한다. Contro...

스프링 게층구조

스프링 계층구조 Web 계층 컨트롤러(@Controller)와 JSP/Freemarker 등의 뷰 템플릿 영역 이외에도 필터(@Filter), 인터셉터, 컨트롤러 어드바이스(@Controller Advice) 등 외부 요청과 응답에 대한 전반적인 영역을 나타낸다. 외부 요청과 응답에 대한 전반적인 영역. 어플리케이션의 진입점이기 때...

WAS vs 웹서버

WAS(Web Application Server) DB 조회나 다양한 로직처리를 요구하는 동적인 컨텐츠를 제공하기 위해 만들어진 Application Server HTTP를 통해 컴퓨터나 장치에 애플리케이션을 수행해주는 미들웨어(소프트웨어 엔진)이다. 웹 컨테이너 혹은 서블릿 컨테이너 라고도 불린다. 컨테이너란, js...

Open Graph(OG) 프로토콜

Open Graph 프로토콜 HTML문서의 head에는 <meta> 태그로 사이트의 정보가 표기되어있는 경우가 많다. 이는 크롤러가 무엇이 제목이고, 무엇이 내용인지 파악하기 쉽도록 하기 위해서 표기한 것이다. 페이스북의 Open Graph 프로토콜은 이러한 메타태그의 표기 방법은 통일한 것이다. 이 Open Graph 프로토콜이 우리가...

Mysql Count 속도

Mysql의 Select Count(*) 얼마나 빠를까? 사용하는 데이터베이스 엔진에 따라 다르다. InnoDB : O(n) MyISAM : O(1) 이유는 MyISAM의 경우 row가 몇 개 있는지 저장하고 있기에 Count(*) 쿼리가 들어오면 그 저장한 값을 바로 리턴하여 O(1)이 된다. Count(*) vs Count(c...

리액트 리팩토링

리액트 리팩토링 useEffect 사용 useEffect 올바른 사용 props, state 변경에 따라 다른 데이터, state를 업데이트해야할 때 useEffect의 사용을 자제하자. useEffect vs EventHandler 중 어느 쪽에서 유저 인터랙션을 처리해야할까? useEffect 내에서 data fetching을 할...

자바 기본

Eclipse 프로젝트 생성 new Java project를 하면 여러 선택지가 나옴. Project layout use project folder as root for sources and class files 빌드 전 소스코드와 빌드 후 생성된 결과물이 root에 한번에 생김...

useEffect 올바른 사용

useEffect 올바른 사용 props, state 변경에 따라 다른 데이터, state를 업데이트해야할 때 useEffect를 사용하면 안된다. props, state가 변경이 되면 리렌더링이 발생하는데, 리렌더링이 끝난 후 useEffect가 호출되어 다시 리렌더링이 발생하기 때문이다. 따라서 다음과 같이 코드를 변경해야한다. 데이터...

diff --git a/page12/index.html b/page12/index.html new file mode 100644 index 000000000..9cf00bb39 --- /dev/null +++ b/page12/index.html @@ -0,0 +1 @@ + 디피의 개발일지
Home
디피의 개발일지
Cancel

바벨

바벨 바벨이란? 자바스크립트 트랜스파일러. 보통 모던 자바스크립트를 호환성을 위해 예전 문법으로 변환할때 사용한다. Typescript와 JSX 코드를 변환할 때도 사용한다. 바벨 설정 파일 예시 { "presets": [ [ "@babel/env", { "targets": { ...

선언형프로그래밍

선언형, 명령형 그리고 추상화 선언형이란? 명령형은 어떻게(How)에, 선언형은 무엇(What)에 집중한다. 선언형은 명령형 코드에서 ‘어떻게’를 감추고 ‘무엇을’만 노출하는 방식의 추상화이다. 일종의 리팩토링이다. 예시를 들면 다음과 같다. // 명령형 코드 : 배열에 있는 모든 숫자를 하나씩 제곱해서 result 배열에 넣는다. f...

React-query 에서 cache를 사용하는 방법

useQuery 설정 React-Query 공식문서상 캐싱개념은 stale과 cachetime을 통해 이루어진다 useQuery의 옵션으로 staletime과 cachetime을 보낼 수 있다. staletime : fetch를 통해 전달받은 데이터는 리액트 쿼리의 자료구조 내용중 캐시에 저장되는데, 이 캐시데이터의 ‘신선한 상태’가 언제까...

DB 페이징 기법

페이징 DB 에서 페이징을 구현하는 방법 오프셋 페이징 limit과 offset을 활용하여 페이징을 구현하는 방법. SELECT * FROM table ORDER BY timestamp OFFSET 10 LIMIT 5 장점 구현이 간단하다. 단점 Offset을 정말로 건너 뛰는 게 아니라, 한번은 읽는다. 따라서 만...

자바스크립트 실행 컨텍스트

실행컨텍스트 코드의 실제 진행상황을 추적하는데 필요한 정보들을 모아둔 구조 함수가 ()로 호출되면 실행컨텍스트가 생성됨. 실행 컨텍스트에는 파라미터를 포함한 지역 메모리와 실행문이 있음. 함수가 종료되면 실행 컨텍스트는 사라진다. 자바스크립트의 함수 객체 서브루틴으로 실행할 ...

코테대비 SQL 문법

정렬 SELECT * FROM ANIMAL_INS ORDER BY ANIMAL_ID ASC ORDER BY ANIMAL_ID DESC ORDER BY NAME ASC, DATETIME DESC 조건 SELECT ANIMAL_ID, NAME FROM ANIMAL_INS WHERE INTAKE_CONDITION="Sick" WHERE I...

CSS 최적화

CSS 최적화가 필요한 이유 CSS는 렌더링을 막는다. CSS의 존재만으로도, CSS가 파싱되기 전까지 브라우저는 렌더링이 지연된다. 만약 브라우저가 CSS가 없는 페이지를 그대로 노출하면, 스타일적용이 되지않은 페이지가 나타나기 때문이다. 이를 FOUC라고 불린다. CSS는 HTML 파싱도 막을 수 있다 브라우저가 CSS가 파싱되기 전까지...

클래스형 컴포넌트 vs 함수형 컴포넌트

클래스형 컴포넌트 import React, { Component } from 'react'; class Hello extends Component { static defaultProps = { name: '이름없음' }; render() { const { color, name, isSpecial } = this....

React에서 Key를 사용하는 이유

리액트로 개발을 하다가 리스트를 만들면, 다음과 같이 key를 사용하라는 안내문이 뜬다. 평소엔 무시하고 key를 그냥 넣었지만, 왜 그런지 갑자기 궁금해져 찾아보았다. Recursing On Children 해당 내용은 리액트 공식문서에 Recursing On Children으로 나와있다. 리액트 리렌더링 과정에서 리스트를 처리할때, 리액...

JS 코테 대비 메소드 정리

문법 for … of : 반복가능한 객체 (Array, String, Set) for … in : 객체. 키를 뽑아냄. Set Set.prototype.add(value) Set.prototype.clear() Set.prototype.delete(value) Set.prototype.forEach((value, k...

diff --git a/page13/index.html b/page13/index.html new file mode 100644 index 000000000..293874d72 --- /dev/null +++ b/page13/index.html @@ -0,0 +1 @@ + 디피의 개발일지
Home
디피의 개발일지
Cancel

학습 내용 요약

프론트엔드 Virtual DOM 리액트의 작동방식은 상태변화 시, 그 컴포넌트와 하위 컴포넌트를 모두 교체하기에 많은 DOM 변화가 일어난다. 이때 리액트에서는 DOM을 추상화한 객체인 Virtual DOM을 변경하고, 변경이 완료되면 실제 DOM과의 차이점을 확인한다음 한번의 리렌더로 해결한다. Virtual DOM은 메모리에 존재하여 메모리...

virtual DOM와 리렌더링

Virtual DOM은 DOM을 추상화한 가상의 객체이다. DOM 이란? HTML 문서를 파싱하여 문서의 구성요소들을 객체로 구조화하여 나타낸 것. HTML Elements, 속성, CSS style, Events, Methods 를 구조화해서 나타낸 객체고, 이 객체를 이용해서 웹페이지 구성요소를 제어할 수 있다. Virtual DOM...

React 18로 업데이트 방법, 문제점 정리

기존에 React 17을 사용하던 Forkie Player 프로젝트를 React 18로 업데이트 하는 기록 업데이트 하는 이유 automatic batching 동영상이 끝났을때 다음 영상으로 넘어가거나, 현재 영상의 시작부분으로 돌아가기 위해선 동영상의 timeupdate, ready, pause, ended 등등의 상태...

velog 메모

리액트 리렌더링 https://velog.io/@eunbinn/when-does-react-render-your-component children으로 넘어온 컴포넌트는 사용되는 컴포넌트가 리렌더링되도, 리렌더링의 영향을 받지 않는다. 리렌더링은 현재 컴포넌트에서 변화된 것이 없어도, 자식으로 전파된다. but 현재 컴포...

함수형 프로그래밍 (이해하기 쉽게)

액션, 계산, 데이터 순수함수 vs 부수효과를 대체한 개념들. 데이터 : 이벤트에 대한 사실. 액션에 의해 변화됨 액션 : 데이터를 변화시킬 수 있음. 실행시점이나 횟수에 의존하여 언제하느냐에 따라 결과가 달라지면 액션이다. 계산 : 입력값을 통해 출력을 만들어내는 것. 같은 입력에 대해 항상 같은 출력값만 내놓아야한다. 외부 세계에 영향을 주...

view

View 다른 테이블을 기반으로 만들어진 가상 테이블. 논리적으로만 존재하는 테이블. 장점 질의문을 쉽게 작성할 수 있음 데이터 보안유지에 도움이 됨. 데이터 관리가 편해짐 특징 ALTER 문으로 뷰를 재정의하는 것은 불가능 기본 테이블 삭제 시 같이 삭제된다. 독자적인 인덱스를 가질 수 없음. **뷰생성 ** ...

IPv6

IPv6 128비트 사용. Multicast 대신에 Broadcast를 함. ICMPv6 ARP, IGMP 기능흡수 ARP : IP주소를 MAC 주소로 변환하는 프로토콜 IPv4 에서 IPv...

deadlock 발생 조건

deadlock 발생 조건 아래 4가지 조건을 모두 만족하면 데드락 발생가능성이 있음 상호배제(Mutual Exclusion) 한 리소스는 한번에 한 프로세스만이 사용할 수 있음 Hold and wait No preemption Circular wait deadlock 방지법 Prev...

17088 등차수열 변환

알고리즘 첫번째와 두번째 원소 사이의 공차를 구함 두번째 원소부터 마지막원소까지의 공차를 만족하는 경우만으로 dfs를 진행함 마지막원소까지 공차를 만족하는 모든 경우의 수를 구하고, 그 중 최소 연산으로 만족하는 경우의 수를 반환함. 위 과정을 첫번째 원소와 두번째 원소 사이의 공차의 경우의수만큼 진행하...

서브네팅

서브네팅 사용가능한 IP 주소 범위를 분할하여 사용하는 것 특징 2의 배수로, 같은 크기로 분할 주소할당 서브넷 마스크는 다음과 같음 네트워크 주소사용부분 모두 1 + 호스트 주소 사용부분 모두 0 각 서브넷에서는 다음과 같이 2개의 주소는 예약되어있음 네트워크 주소 : (기존 네트워크 부 + 분할부분 매핑 주소)...

diff --git a/page14/index.html b/page14/index.html new file mode 100644 index 000000000..806cbaa2c --- /dev/null +++ b/page14/index.html @@ -0,0 +1 @@ + 디피의 개발일지
Home
디피의 개발일지
Cancel

관계 데이터 모델

관계 데이터 모델 용어 릴레이션 : 하나의 개체에 관한 데이터를 2차원 테이블로 저장한 것. 릴레이션 스키마 : 릴레이션의 이름과 속성이름으로 정의. 정적. 내포 릴레이션 인스턴스 : 릴레이션에 존재하는 튜플들의 집합. 동적. 외연 도메인 : 하나의 속성이 가질 수 있는 모든...

반정규화

반정규화 시스템의 성능 향상, 개발 및 운영의 편의성을 위해 정규화된 데이터 모델을 통합, 중복, 분리하는 과정으로 의도적으로 정규화 원칙을 위배하는 행위. 과도한 반정규화는 성능 저하를 불러일으킴. 시스셈의 성능과 관리효율성은 증가하지만, 데이터의 일관성 및 정합성을 저하될 수 있음. 따라서 사전에 데이터의 일관성 및 ...

응집도와 결합도

응집도 모듈 내부의 기능적인 집중 정도. 높을 수록 좋다. 우연적 응집도 < 논리적 응집도 < 시간적 응집도 < 절차적 응집도 < 교환적 응집도 < 순차적 응집도 < 기능적 응집도 기능적 응집도(Functional Cohesion) : 모듈 내부의 모든 기능이 단일한 목적을 위해 수행되는 경우 순차적 응집...

스키마

내부 스키마 데이터베이스가 실제로 저장되는 방법을 정의한 것. 개념 스키마 데이터베이스 전체의 논리적 구조를 정의한 것. 데이터베이스 하나에 하나만 존재. 개체, 관계, 제약조건, 보안정책 등을 정의한다. 외부 스키마 사용자에게 필요한 데이터베이스를 정의한 것

블랙박스/화이트박스 테스트

블랙박스 테스트 소프트웨어의 내부 구조나 작동 원리는 모르는 상태에서 동작을 검사하는 방식 기법 동등 분할 기법 : 프로그램 입력 도메인을 테스트 케이스가 산출될 수 있는 데이터 클래스로 분류하는 방법 경계값 분석 기법 : 입력 조건의 중간 값보다 경계값에서 에러가 발생될 확률이 높으므로 오류 예측 기법 : 놓치기 쉬운 오류들을 감각...

전위식/후위식

전위식 연산자를 먼저 표시하고 연산에 필요한 피연산자를 나중에 표시 (A + B) * (C - D) ((A + B) * (C - D)) *(+(AB)-(CD)) *+AB-CD 후위식 피연산자를 먼저 표시하고, 연산자를 나중에 표시 (A+B) * (C-D) ((A+B) * (C-D)) ((AB)+(CD)-)* AB+CD-*

통합 테스트

통합 테스트 상향식 통합 테스트 프로그램의 하위 모듈에서 상위모듈로 통합하면서 테스트하는 기법. 하위 모듈을 클러스터로 결합 더미 모듈인 드라이버 작성 통합된 클러스터 단위로 테스트 테스트 완료 후 클러스터는 프로그램 구조의 상위로 이동해 결합하고, 드라이버는 실제 모듈로 대체 됨. 하향식 통합 테스트 프로그램의 상위모듈...

럼바우 분석기법

럼바우 분석기법 모델링 기법중 하나로 그래픽으로 표현한 분석기법 객체 모델링 기법이라고도 한다. 3단계로 구성 객체 모델링 동적 모델링 기능 모델링 1. 객체 모델링(object modeling) 객체 다이어그램을 표시함. 정보 모델링이라고도 하며, 시스템에서 요구되는 객체를 찾아내어 속성과 연산 식별...

UML

UML(Unified Modeling Language) 통합 모델링 언어를 사용하여 시스템 상호작용, 업무 흐름, 시스템 구조, 컴포넌트 관계 등을 그린 도면. 프로그래밍을 단순화 시켜 표현하여 의사소통 하기 좋고, 대규모 프로젝트에서는 로드맵을 만들거나 개발을 위한 시스템 구축의 기본을 마련한다. 요구사항 모델링에 사용되는 기법 중 하나 ...

객체지향 분석론

Coad Yourdon E-R 다이어그램을 사용하여 객체의 행위를 모델링. 객체 식별, 구조 식별, 주제정의, 속성과 인스턴스 연결 정의, 연산과 메시지 연결 정의 등의 과정으로 주로 관계를 분석 하는 기법 Booch 미시적, 거시적 개발 프로세스를 모두 사용하는 분석 방법. 클래스와 객체들을 분석 및 식별하고 클래스의 속성과 연산을 정의...

diff --git a/page15/index.html b/page15/index.html new file mode 100644 index 000000000..6d36fb14d --- /dev/null +++ b/page15/index.html @@ -0,0 +1 @@ + 디피의 개발일지
Home
디피의 개발일지
Cancel

CASE

CASE(Computer Aided Software Engineering) 소프트웨어 개발 시 사용되는 분석 자동화 도구. 소프트웨어 개발 과정의 일부나 전체를 자동화하는 도구이다. CAD 기기와 유사한 것이라고 생각하면 됨. 요구분석 -> 설계 -> 구현 -> 검사 및 디버깅 과정을 CASE를 활용하여 자동화함 CA...

GoF

GoF(Gang of Four) GoF에서는 23가지 디자인패턴을 3가지 유형으로 분류함. 생성 패턴(Creation pattern) 객체를 생성하는데 관련된 패턴들 객체가 생성되는 과정의 유연성을 높이고, 코드의 유지를 쉽게함 구조 패턴(Structural Pattern) ...

운영체제 소개

운영체제 정의 컴퓨터 시스템의 4가지 요소 유저 어플리케이션 OS 하드웨어 운영체제 유저 관점 : 어플리케이션을 수행함. 컴퓨터 사용을 편리하게 해줌 시스템 관점 : 자원할당자. 어플리케이션과 i/o 장치의 수행을 다루는 프로그램 컨트롤러 커널 OS에 속하며, 컴퓨터에서 항상 수행되는 프로그램. 하드웨어...

Arrow function

화살표함수 기존의 function 표현방식보다 간결하게 함수를 표현할 수 있다. 화살표함수는 항상 익명이며, 자신의 this, arguments, super, new.target을 바인딩하지 않는다. 따라서 생성자로는 사용할 수 없다. 화살표함수 도입 영향 : 짧은 함수, 상위 스코프 this 짧은 함수 var materials = ...

Promise, async/await

Promise 비동기로 처리하기 위한 방법 중 하나. 다음과 같이 사용 //Promise 선언 var _promise = function (param) { return new Promise(function (resolve, reject) { window.setTimeout(function () { if (param) { ...

this에 대해서

this에 대해서 자바스크립트에서 모든 함수는 실행될때마다 함수 내부에 this라는 객체가 추가된다. arguments라는 유사배열 객체와 함께 함수 내부로 암묵적으로 전달되는 것이다. 그렇기 때문에 자바스크립트에서의 this는 함수가 호출된 상황에 따라 그 모습을 달리한다. 상황 1. 객체의 메서드를 호출할때 객체의 프로퍼티가 함수일 경우 ...

closure

Closure 두개의 함수로 만들어진 환경으로 이루어진 특별한 객체의 한 종류이다. 여기서 환경이란, 클로저가 생성될 때 그 범위에 있던 여러 지역변수들이 포함된 context를 말한다. 이 클로저를 통해서 자바스크립트에는 없는 private 속성/메소드, public 속성/메소드를 구현할 수 있다. 클로저 생성하기 다음은 클로저가 생성되는 ...

hoisting

호이스팅은 변수를 끌어올리는 것. var로 선언된 모든 변수 선언을 hoist한다. hoist란 변수의 정의가 그 범위에 따라 선언과 할당으로 분리되는 것을 의미한다. 즉, 함수 내의 선언들을 모두 끌어올려서 해당 함수 유효 범위의 최상단에 선언하는 것 자바스크립트에서 변수의 선언은 끌어올려진다. 다음의 코드를 보자. function getX()...

javascript 이벤트루프

이벤트 루프 javascript를 공부하다보면 아래와 같은 말을 종종 듣는다. 싱글스레드 기반으로 동작하는 자바스크립트 이벤트 루프를 기반으로 하는 싱글스레드 Node.js 정말 싱글 스레드인가? 어떻게 싱글 스레드인가? 이벤트 루프는 무엇인가? 를 간단히 알아보기 위해 자바스크립트가 동작하는 환경과 엔진에 대해 알아보자. Ja...

알고리즘 개요

DP 복잡한 문제를 간단한 여러 하위문제로 나누어 푸는 방법 두가지 구현방식이 존재함 top-down 여러개의 하위문제로 나누고, 하위문제를 푼 다음, 그것들을 결합하여 최종적으로 최적해를 구한다. 이때 하위문제로 나눌때 같은 하위문제를 가지고 있는 경우가 있다. 이때의 최적해를 저장해서 사용하여 같은 하위문제...

diff --git a/page16/index.html b/page16/index.html new file mode 100644 index 000000000..5b85d79d9 --- /dev/null +++ b/page16/index.html @@ -0,0 +1 @@ + 디피의 개발일지
Home
디피의 개발일지
Cancel

singleton

Singleton 패턴 애플리케이션에서 인스턴스를 하나만 만들어 사용하기 위한 패턴이다. 커넥션 풀, 스레드 풀, 디바이스 설정 객체 등의 경우, 인스턴스를 여러 개 만들게하면 자원을 낭비하게 되거나 버그를 발생시킬 수 있으므로 오직 하나만 생성하고 그 인스턴스를 사용하도록 하는 것이 목적이다. 구현 하나의 인스턴스만을 유지하기 위해 인스턴...

데이터베이스 개요

데이터베이스 파일시스템의 문제점 앱과 상호 연동되어있기 때문에 다음과 같은 문제가 발생함. 데이터 종속성 데이터 중복성 데이터 무결성 데이터베이스 특징 데이터 독립성 물리적 독립성 : 데이터베이스를 수정하더라도, 응용 프로그램을 수정할 필요는 없음 ...

React) JS 다운로드 시간 동안의 로딩 화면

최근 프로젝트에서 리액트 앱을 배포하였는데, 인터넷 속도가 느린 환경에서 자바스크립트를 다운로드 받는 시간이 오래 걸린다는 걸 눈치챘다. 눈치챘다는 표현이 어울리는데 왜냐하면 사실 당연한 건데 이제까지 신경쓰지 않았던 부분이기 때문이다. 리액트앱은 매우 큰 자바스크립트 파일 하나로 이루어져있고, 이를 다운로드 받는 것은 당연히 오래 걸리는 일이기 때...

2631 줄세우기

알고리즘 LIS를 구하고, LIS를 구성하지 않는 요소들만 배치해주면 됨 LIS를 구하는 방법으론 n이 최대 200이니 O(n^2)을 써도 충분함 LIS 구하는 코드 O(n^2) 이전 요소들 중에 자기보다 작은 것 중, 가장 큰 LIS를 가진 요...

운영체제 개요

프로세스와 스레드의 차이 프로세스 실행중인 프로그램으로, 메모리에 적재되어 CPU의 할당을 받을 수 있는 것을 말함. OS로부터 주소공간, 파일, 메모리등을 할당받으며, 이것들을 총칭하여 프로세스라고한다. 할당받는 메모리 공간 프로세스 스택 : 함수의 매개변수, 복귀주소, 로컬 변수 같은 임시자료를 저장 데이터 섹션 : 전역변수들을 ...

1522 문자열 교환

알고리즘 문자열에서 a의 개수를 ac 라고 할때, 시작점을 문자열의 맨처음부터 맨 끝까지 이동하면서, 길이가 ac인 문자열을 안에 b가 몇개있는지 검사한다. 이때 검사한 b의 개수 중 최소가 필요한 최소의 교환횟수 쉽게 말해서, 길이가 ac인 문자열 안에 b를 최소로 포함한 문자열을 a로 채우는 문제로 치환한 것이다. 코드 #inc...

network 개요

HTTP의 GET과 POST 비교 GET 데이터가 HTTP Request Message의 헤더부분의 url에 담겨서 전송됨. url의 끝에 ? 뒤에 데이터를 붙여 요청. 데이터의 크기가 제한적임. 데이터가 url에 노출되므로 보안에 약함 POST 데이터가 HTTP Requst Message의 바디부분에 담김 데이터 크기가...

DataStructure 개요

Array vs Linked List Array 논리적 저장 순서와 물리적 저장순서가 일치함. 장점 Random Access : 인덱스로 원하는 원소에 바로 접근이 가능하여, O(1)에 해당원소로 접근가능 언어에따라 다르지만, 필요한 만큼만...

(Typescript) React typescript hack

React 에서 Typescript 사용시 참고할만한 Typing 기법을 기록한 곳 React.HTMLArributes<[HTMLElement]> 한 Element에 부여될 속성 값들을 모두 참조할때 사용하면 좋다. 다음과 같이 기본 HTML 엘리먼트를 스타일링해서 사용할때 유용함 export interface ITextButtonPr...

(CSS)다른 엘리먼트의 이벤트 발생 시, 스타일 적용법

yourlist_web_renewal 중, 한 input 엘리먼트의 placeholder가 보일시와 안보일 시, label 엘리먼트에 각기 다른 스타일을 적용해야하는 일이 있었다. 현재 사용중인 tailwind에선 어떻게 사용해야할지 모르겠으나 순수 CSS 방식으로 하면 다음과 같다. .form_field:placeholder-shown ~ .fo...

diff --git a/page17/index.html b/page17/index.html new file mode 100644 index 000000000..68ae99874 --- /dev/null +++ b/page17/index.html @@ -0,0 +1 @@ + 디피의 개발일지
Home
디피의 개발일지
Cancel

flex, grid로 남은 곳 꽉채우기

최근 프로젝트를 하면서, 다른 요소들의 크기는 정해져있을때 남은 한 요소의 크기를 부모 요소에서 남는 공간만큼 부여하는 부분이 많았다(그리고 앞으로도 많을 것 같다) 위 부분을 구현할때, flex와 grid를 통해 해결했어서 그것을 기록 하는 차원에서 이 글을 적어본다. flex 만약 다음과 같이 div 엘리먼트들이 배치되어있고, 우리는 가운데 ...

13144 List of Unique Numbers

알고리즘(내방식) 숫자를 왼쪽에서 오른쪽으로 살펴가면서, 그 수가 나온 인덱스를 기록(saveIdx)한다. i를 기록할때 이미 기록된 인덱스가 있을 경우, 그 인덱스부터 이전에 체크가 안된 인덱스들은 i 까지만 연속해서 숫자를 뽑을 수 있다. 따라서 이에 해당하는 경우의 수를 정답에 추가시켜준다. 알고리즘(투포인터) 내가...

MVVM, MVP 패턴

MVVM Model - View - View Model 의 약자로, 프로그램의 비지니스 로직과 프레젠테이션 로직을 UI로 명확하게 분리하는 패턴 구성요소 Model : 데이터를 보관하고 있는 부분으로, 데이터를 불러오거나 업데이트하는 로직이 있음. View Model : Model에 데이터를 요청하고 가공함. 비지니스 로직을 처리. ...

Git

Git GIT의 개발 목표 빠른 속도 단순한 구조 비선형적인 개발(수천 개의 동시다발적인 브랜치) 완벽한 분산(DVCS) 대형 프로젝트에도 유용할 것 Inside of Git Git은 기본적으로 파일시스템의 스냅샷을 저장한다.(커밋 당시의 GIT 디렉터리의 모든 파일 정보를 저장) 또한 파일 및 스냅샷을 해시하여 바뀐 버전...

MVC 패턴

MVC 컴포넌트의 역할 Model 컨트롤러가 호출했을때, 요청에 맞는 역할을 수행한다. 비즈니스 로직을 구현하는 영역으로 응용프로그램에서 데이터를 처리하는 부분이다. DB에 연결하고 데이터를 추출하거나 CRUD 등의 작업을 수행한다. 상태의 변화가 있을때, 컨트롤러와 뷰에 통보해 후속 조치 명령을 받을 수 있게 된다. 뷰나 컨트롤러...

TDD

TDD 란? Test-Driven Development의 약자로, 테스트가 코드 작성을 주도하는 개발방식이며, 매우 짧은 개발 사이클의 반복에 의존하는 소프트웨어 개발 프로세스이다. 우선 개발자는 요구되는 새로운 기능에 대한 자동화된 테스트케이스를 작성하고, 해당 테스트를 통과하는 가장 간단한 코드를 작성한다. 일단 테스트를 통과하는 코드를 작성하...

RESTful API

REST란, REprensentational State Transfer의 약자이다. 따라서, RESTful API는 REST의 기본 원칙을 잘 지킨 API를 의미한다. REST는 하나의 아키텍쳐로, Resource Oriented Architecture이다. API 설계의 중심에 자원(resource)가 있고, HTTP Method를 통해 자원을 처...

9935 문자열 폭발

매우 어렵게 구현하여 풀긴하였으나, 스택을 사용하면 매우 직관적으로 편하게 구현할 수 있었다. 스택을 사용하여 문제를 푼 경험이 적어서 스택을 떠오르지 못한 것 같다 스택을 사용한 문자열 문제를 몇문제 풀어봐야할 것 같음 내 풀이 : 빡센 구현 알고리즘 앞에서부터 검사하다가 폭발문자열과 일치하는 문자열이 발견되면, 그 문자열을 폭파시키고...

함수형 프로그래밍

부수 효과가 없는 순수함수를 1급 객체로 간주하여 파라미터로 넘기거나 반환값으로 사용하는 것을 가능하도록 하여, 참조 투명성을 지키도록 하는 프로그래밍 패러다임 순수 함수형 프로그래밍만으로 개발을 하기에는 무리가 있다. 따라서 적절히 조절해가면서 개발을 해나가자. 요약 함수형 프로그래밍의 가장 큰 특징 두 가지 immutable data ...

알고리즘 팁(WIP)

구조 관련 정렬된 배열의 앞 뒤로 새로운 요소를 삽입 해야되는 경우 필요한 길이의 2배길이의 배열을 선언한 다음, (start, to)로 인덱스를 트래킹해가면서 넣는다. 이때 start, to의 초깃값은 2배 길이의 배열의 딱 중간이어야함(그래야 최솟값만 들어가거나, 최댓값만 들어갈때도 배열이 터지지 않음) 소수판별 floor(...

diff --git a/page18/index.html b/page18/index.html new file mode 100644 index 000000000..f700668b0 --- /dev/null +++ b/page18/index.html @@ -0,0 +1 @@ + 디피의 개발일지
Home
디피의 개발일지
Cancel

Object Oriented Programming

OOP는 너무 거대한 개념이라 다 다루지는 못하지만 간단히만 알아보자 간단한 설명 OOP : 중심적 프로그래밍 패러다임. 현실 세계의 사물들을 객체라고 보고, 그 객체로부터 개발하고자 하는 애플리케이션에 필요한 특징들을 뽑아와 프로그래밍하는 것(추상화) 장점 OOP로 작성한 코드는 재사용성이 높다. ...

React v18 주요 변경점

React v18의 정식 출시가 코앞에 있고, 가장 큰 변경점 중 하나가 서버사이드 렌더링에 관한 내용이라는 소식을 듣고 마침 SSR 에 대해 흥미가 있던 터라 한번 React v18의 주요 변경점이 뭔지 공부해보기로 했다. 먼저 기존의 React v17을 v18로 마이그레이션 하는 것은 문제 없다고 한다. 리액트 팀에서 이 부분을 특히 신경써서 만...

좋은 코드란 무엇인가?

좋은 코드란 무엇일까? 일반적으로 좋은 코드라고 하면 다음 세가지를 얘기한다. 읽기 좋은 코드 테스트가 용이한 코드 중복이 없는 코드 그럼 왜 위 세가지가 갖춰진 코드를 좋은 코드라 하는 걸까? 그리고 어떻게 이 세가지를 지키며 코드를 작성할수 있을까? 왜 좋지 않은 코드가 생산되는가? 우리는 항상 좋은 코드를 작성하기 위해 노...

css 선택자

* 아무 표시 없이 사용하면 페이지에 있는 모든 요소가 대상 * { margin : 0; padding : 0; } 특정 요소의 모든 자식 요소에 적용할 수도 있음 #container * { border : 1px solid black; } 주의점 : 남발할 시 성능저하를 불러일으킴 #X id를 대상으로 삼음 #...

redux-saga concepts

Declarative Effects import { takeEvery } from "redux-saga/effects"; import Api from "./path/to/api"; function* watchFetchProducts() { yield takeEvery("PRODUCTS_REQUESTED", fetchProducts); } fu...

generator 문법

Generator 문법 자바스크립트의 기능이며 Redux-saga의 핵심 기능이다. 함수를 특정 구간에 멈춰놓고, 원할때 다시 돌아가게 할 수 있음. 또 반환을 여러번 할수 있다. 예시 function* generate() { yield 1; yield 2; yield 3; return 4; } const generato...

Decision Tree를 이용한 서울 미세먼지 예측 모델

소스코드 깃허브 링크 모델 소개 중국 도시별 미세먼지 PM10 농도와, 서울의 기온, 풍속, 습도를 가지고, 서울의 하루 뒤 미세먼지 PM10 농도를 예측하는 모델 서울의 기온, 풍속, 습도는 현재(중국 미세먼지 데이터 날짜와 일치)와 다음날(서울 미세먼지 데이터 날짜와 일치) 데이터를 모두 넣음. ...

자바스크립트 데코데이터 패턴

데코레이터 패턴 하나의 코드를 다른 코드로 래핑하거나 javascript 함수를 래핑하는 방법 동일한 클래스의 다른 객체에는 영향을 주지 않고, 정적/동적으로 개별 객체에 동작을 추가할 수 있는 디자인 패턴이다. 문법 let variable = function(object) { obj...

14658 하늘에서 별똥별

알고리즘 한 점에서 x축으로의 범위를 정하고, 다른 점에서 y축으로의 범위를 정하는 방식으로 모든 두개의 점 조합으로 트래펄린을 설치한다. 코드 #include <iostream> using namespace std; int n, m, l, k; pair<int, int> s[110]; int max(int a,...

4485 젤다

알고리즘 문제를 해석하면, 한 점에서 다른 한 점까지 가는데 최소경로를 찾는 문제로 해석할 수 있다. 따라서 다익스트라 알고리즘을 적용하면 쉽게 풀수있다. 이때 한 칸에 저장된 값은, 그 칸으로 접근하는 경로의 길이로 치환하여 품. 여담 문제 내에 다익스트라라는 꽤나 많은 힌트가 있었으나, dfs...

diff --git a/page19/index.html b/page19/index.html new file mode 100644 index 000000000..cb056213a --- /dev/null +++ b/page19/index.html @@ -0,0 +1 @@ + 디피의 개발일지
Home
디피의 개발일지
Cancel

1613 역사

알고리즘 BFS 입력으로 들어온, 알려진 전후관계로 그래프를 만듦. 체크를 해야하는 원소 두개가 들어오면, 먼저 첫번째 거에서 bfs를 시작하여 두번째거가 나오면 첫번째가 앞에 있는 것이므로 -1를 출력 찾지못하면, 두번째에서 첫번째를 BFS로 찾음. 찾으면 1을 출력. 찾지못하면, ...

react 작동원리부터 tailwindcss 사용까지

개요 yourlist 웹 리뉴얼 프로젝트에서 tailwind css를 사용하기로 결정하였고, create-react-app을 사용하여 리액트앱을 만든 후, tailwind css를 추가하였다. 이때 tailwind css는 먼저 css 파일을 컴파일 하고, 컴파일 이후의 css 파일을 import 하는 방식으로 동작한다. 따라서 매번 리...

flutter (rn 개발자를 위한 정리 4)

Props ReactNative 에서 대부분의 컴포넌트는 매겨변수나 속성을 props로 전달함 Flutter에서는 매개변수가 있는 생성자에서 받은 속성을 final로 표시된 지역변수나 함수에 할당함 // Flutter class CustomCard extends StatelessWidget { Cus...

flutter (rn 개발자를 위한 정리 3)

Flutter 위젯 Views View 컨테이너 React Native 에서는 View 가 컨테이너이고 Flexbox를 이용한 레이아웃, 스타일, 터치 핸들링, 접근성제어를 지원 Flutter에서는 Container나, Column, Row, Center같은 위젯 라이브러리의 핵심 레이아웃 위젯을 사용할 수 있음....

flutter (rn 개발자를 위한 정리 2)

기본 앱 생성 IDE 에서 생성하는 방법 커맨드라인에서 생성하는 방법 $ flutter create <projectname> 앱 실행 IDE에서 run 클릭 최상위 디렉토리에서 flutter run 입력 import import 'package:flutter/material.dart'; import 'pa...

flutter (rn 개발자를 위한 정리 1)

진입점 main() { // 항상 최상단 앱의 진입점 main() 이 있어야함. } 콘솔 print("hello world") 변수 dart 는 타입검사를 하는 언어 정적 타입 검사와 런타임 타입검사를 동시에 사용하며, 변수의 값이 변수의 정적타입과 항상 일치하는지 검사함. 타입 추론을 하기에 일부 타입표기는 생략가능 ...

LocateC

개발 계기 코로나 전에 커피를 마시면서 자주 길거리를 걸어다녔는데, 항상 다 마시고 남은 빈 통만 들고 한참을 쓰레기통을 찾아다녔던 기억이 있었다. 그런데 학과 내에서 학교 생활에 도움을 줄 서비스를 개발하는 공모전을 하였고, 위 기억을 되살려서 학교 내 흡연장소, 쓰레기통의 위치를 알려주는 서비스를 개발해보기로 하였다. 서비스 소개 ...

Yourlist

서비스 소개 사용자가 원하는 유튜브 영상의, 원하는 부분만을 가지고 재생목록을 만들 수 있도록 하는 서비스 회원가입을 통해 회원이 될 수 있고, 회원은 재생목록을 무제한으로 만들 수 있지만, 비회면원 5개까지만 가능 i18n을 통한 글로벌 언어 제공 앱, 웹에서 모두 사용가능하도록 ...

flutter 첫 걸음

1단계 : Starter Flutter app void main() => runApp(MyApp()); 처럼 화살표 함수 사용가능 한줄 함수에 화살표를 사용한다. 최상단 앱 : StatelessWidget을 상속받아 앱 자체를 위젝으로 만든다. Flutter에서는 정렬, 여백, 레이아웃 등 모든 것이 ...

2629 양팔저울

알고리즘 모든 경우를 따지기 위해서 각 추가 구슬 반대쪽에 올라가는 경우, 저울에 안올라가는 경우, 구슬과 같은 쪽에 올라갈 경우를 모두 고려해야함 배낭문제를 응용하여, 추를 하나씩 모든 무게에 대해 검사를 한다. true이면 구슬 반대쪽 저울에 추만으로 만들 수 있는 무게라는 뜻. 현재 추를 사용하...

diff --git a/page2/index.html b/page2/index.html new file mode 100644 index 000000000..85b28e42e --- /dev/null +++ b/page2/index.html @@ -0,0 +1 @@ + 디피의 개발일지
Home
디피의 개발일지
Cancel

실용주의프로그래머 4장 실용주의 편집증

여러분은 완벽한 소프트웨어를 만들 수 없다 실용주의 프로그래머는 자신의 실수에 대비한 방어책을 마련한다. Topic 23 : 코드의 공급자와 사용자는 권리와 책임에 대해 동의해야한다 Topic 24 : 버그상황에서 헤어나오는 도중에 어떤 손상도 입히지 않도록 보장해야한다. Topic 25 : 확인을 쉽게하는 방법. 가정(assum...

실용주의프로그래머 3장 기본도구

Topic 16 - 일반 텍스트의 힘 일반 텍스트란? 인쇄 가능한 문자로 이루어지고, 정보를 전달하기에 적합한 형식을 갖추어야한다. 또한 사람이 이해할 수 있어야한다. 일반 텍스트 우유 커피 일반 텍스트가 아님 hlj;uijn bfjxrrctvh jkni’pio6p7gu;vh bjxrdi5rgvhj Field...

실용주의프로그래머 2장 실용주의 접근법

Topic 8 - 좋은 설계의 핵심 좋은 설계는 나쁜 설계보다 바꾸기 쉽다. ETC(Easy To Change)는 규칙이 아니라 가치 가치란 결정을 내릴때 도움을 주는 것이다. ETC는 가치로서 내제화되어야하며, 의식적으로 방금 한 일이 ETC 한지 항상 물어봐야한다. 일반적으로 상식선에서 추측이 가능하다. 하지만 결정하기 어려울때가 있는...

실용주의프로그래머 1장 실용주의 철학

Topic 1 - 당신의 인생이다 변화를 피하지 말고 불만이 있는 것이 있으면 고치기 위해 노력하라 업무환경, 적성이 안맞으면 바꾸되, 너무 오래 노력하지는 말기 뒤쳐지는 기분이 들면 여가 시간에 공부하기 기회는 많고 적극적으로 행동해 그 기회를 잡아라 Topic 2 - 고양이가 내 소스 코드를 삼켰어요 실용주의 철학의 초석 중 하나...

next.js 최적화과정

발단 서비스를 운영하던 도중 CPU/Memory 사용량은 낮은데 사용자 응답이 매우 느려지는 상황이 발생하였다. 서비스는 쿠버네티스로 띄워져있었고, HPA metric으로 CPU와 memory가 걸려있었는데, CPU와 memory가 올라가지 않으니 scale-out도 발생하지 않아 적은 pod으로 계속 서비스 되고 있었다. 수동으로 pod을 3~...

next.js caching

next.js app router의 caching에 관해 공부한 내용입니다. Overview next.js 에서 제공하는 caching mechanism에는 다음과 같은 것들이 있다. Mechanism What Where Purpose Duration ...

next.js props drilling

Data sharing (Solving props drilling) next.js 13.4 이상 App router 기준으로 작성하였습니다. props drilling 예시 next.js에서 주로 발생하는 props drilling 형태는 다음과 같습니다. 먼저 필요한 데이터를 서버컴포넌트에서 fetch 합니다. API 노출 방지, 최적화...

React 새로고침/뒤로가기 막기

웹페이지에서 사용자가 무언가를 입력하고 뒤로가기를 눌렀을때 다음과 같은 경고창을 띄우는 걸 본적이 있다. 이러한 스펙을 개발하기위해서는 다음과 같이 두가지 상황으로 나누어야한다. 새로고침/창닫기/링크이동(외부페이지 이동 or 링크 버튼 클릭) 뒤로가기 그렇다면 리액트에서는 구체적으로 어떻게 이 스펙을 구현할 수 있을지 알아보자 ...

IOS 16.5에서 video가 크기 변경이 적용되지 않는 이슈

Spec Swiper (ver 4.5.1) 안에 <video> 태그가 slide로 포함되어있음. 현재 가장 가운데 있는 slide 안의 video는 크기를 키우고, slide가 옆으로 이동하면 다시 원래 사이즈로 돌아가도록 함 Issue IOS 16.5 safari에서는 다음과 같...

es6에서 도입된 문법

let, const 키워드를 통한 변수선언 기존 자바스크립트에서는 var 키워드로만 변수선언이 가능했다. 하지만, let const 키워드를 추가하여 보다 예측가능한 코드를 작성할 수 있게 됐다. // es6 이전. // var는 재선언 가능 var a = "This is string"; var a = 1234; // ES6 이후 // let은 ...

diff --git a/page20/index.html b/page20/index.html new file mode 100644 index 000000000..25f97c29f --- /dev/null +++ b/page20/index.html @@ -0,0 +1 @@ + 디피의 개발일지
Home
디피의 개발일지
Cancel

10986 나머지합

알고리즘 구간합 정의 : i ~ j 까지의 구간합 = (1~ j 까지의 구간합) - (1 ~ i -1 까지의 구간합) 위 정의에 따라 우리가 구해야하는 (i ~ j 까지의 구간합) % m = 0 은 ((1~ j 까지의 구간합) % m - (1 ~ i -1 까지의 구간합) % m) % m 과 같음 ...

12906 새로운 하노이탑

알고리즘 평범하게 BFS하면 되는데, 방문 표시를 어떻게 하느냐가 이번 문제의 관건이다. 난 각 기둥의 문자열 사이에 “/” 라는 문자를 추가하여 상태를 구분짓고, 이것을 set에 저장하였다. 이렇게 상태를 구분지어본 것은 처음이라, 코드를 짜면서도 계속 내 코드를 의심했다 코드 #incl...

9576 책 나눠주기

알고리즘 a, b에서 b의 순서대로 정렬 1 2 1 1 이런식으로 입력이 들어오면, 1 2에서 1에 넣고 1 1에선 넣을 수가 없기 때문에. 정렬된 (a, b)를 앞에서부터 검사하는데, a에서 b까지 1씩 증가하면서 가능하면 책을 배정하고, b까지 검사했는데도 남아있는 책이없으면 패스한다. ...

expo push notification

paasta notification 회의 방법들 expo 서버로 보내는 방법 (https://docs.expo.dev/push-notifications/sending-notifications/) expo에서 제공하는 라이브러리를 사용하면 됨. but 자바 sdk를 제공하긴 ...

타입스크립트

타입스크립트 연습 tsconfig.json compilerOptions:{ ​ “outDir” : “./dist” // 컴파일된 파일 저장. expo 에선 안해도 될듯 } jsx -> tsx, js -> ts ...

1918 후위표기식

알고리즘 괄호치기 / 는 앞에서부터 가까운 두 괄호를 묶음. 그다음 + - 로 마찬가지로 함. 양쪽에 모두 없으면 패스함 출력 여는괄호, 연산자는 스택에 넣기. 닫는 괄호가 나오면 여는괄호가 나올때까지 쭉 빼기. 알파벳은 그냥 출력. ...

expo 배포

EXPO 앱 google playstore 배포 app.json 수정 expo build:android -t app-bundle google play app signing 이 선행되어야함. https://docs.expo.dev/distribution/app-signing/ ...

React-boilterplate 설명

react-boilerplate 설명 react-boilerplate 라는, 리액트 프로젝트를 처음 시작할때 create-react-app을 대체하여 사용하기에 아주 좋아보이는 프로젝트가 있다. https://github.com/react-boilerplate/react-boilerplate 상당히 많은 초기...

9251 LCS

알고리즘 https://velog.io/@emplam27/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EA%B7%B8%EB%A6%BC%EC%9C%BC%EB%A1%9C-%EC%95%8C%EC%95%84%EB%B3%B4%EB%8A%94-LCS-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-Longest-Comm...

2096 내려가기

알고리즘 내려올 수 있는 곳은 굉장히 한정되어있고, 순차적으로 가장 작은값을 or 가장 큰값을 구하기만 하면 되는 문제이기에 일차원 DP로 해결이 가능하다. 이런 종류의 문제를 “슬라이딩 윈도우” 라고 부르는 것 같다 코드 #include <iostream> using namespace std; int n, ansMax =...

diff --git a/page21/index.html b/page21/index.html new file mode 100644 index 000000000..8a0880e8a --- /dev/null +++ b/page21/index.html @@ -0,0 +1 @@ + 디피의 개발일지
Home
디피의 개발일지
Cancel

17387 선분교차 2

알고리즘 양 선분이 평행할 경우, 또는 한 쪽이 x축에 평행하고, 다른 축이 y축에 평행할 경우엔 조건문을 통해 범위를 잘 검사하여 처리 나머지 경우엔 다음과 같은 방법을 사용한다. 첫번째 선분을 이루는 직선이 두번째 선분을 양분하고, 그 반대도 양분하면 두 선분은 교차한다. ...

11779 최소비용구하기 2

알고리즘 일반적인 다익스트라로 푸는데, 풀면서 이전에 어디서 왔는지를 기록한다. 다익스트라가 끝나고, 끝에서부터 왔던길을 되돌아가며 경로를 찾아낸다. 코드 #include <iostream> #include <queue> #include <vector> #include <cstring> #de...

1208 부분수열의 하1 2

알고리즘 수를 두개 집합으로 나눔. 나눈 집합 각각에 대해 모든 경우를 살펴봐서 각 집합 내부에서 s에 도달한 횟수를 세고, 만들 수 있는 수와 그 수가 나온 횟수를 센다. 위에서 센 횟수로, 양 집합에서 만들 수 있는 수가 a, b이고, 이 a, b가 각 집합에서 나온 횟수를 c, d라고 하면, ...

1504 특정한 최단경로

알고리즘 1 -> v1 -> v2 -> n 과 1 -> v2 -> v1 -> n 중 어느것이 더 짧은지 확인하면 된다. 즉, 원점이 1일때, v1 일때, v2일때 다익스트라를 진행하고, 거기서 나온 거리로 위 거리를 계산하여 출력하면 된다. 코드 #include <iostream> #inclu...

115653 구슬탈출4

알고리즘 푸는 방법은 bfs지만, 구현이 더 까다로운 문제이다. visit은 빨간구슬, 파란구슬의 위치를 동시에 기록하여 두 구슬 모두 같은 자리로 다시 가지 않도록 한다. 각 방향으로 기우릴때는 일단 한쪽을 먼저 보낸다고 가정하고, 실제로 먼저 보내는지 검사한 후 아니라면 다른쪽을 먼저 보낸다. 한쪽이 구멍에 먼저 빠지면, (-1,...

12872 플레이리스트

알고리즘 dp를 사용 save[101][101] 로 메모이제이션을 위한 배열을 선언 한 곳에는 이제까지 들어간 새로운 곡의 수, 한 곳에는 현재 깊이 원리 m == 0 일때, 현재 깊이 d에서는 이전 곡의 구성이 어찌됐든 간에, 현재~끝에 나와야할 곡의 구성의 수는 같다. ...

15989 1,2,3 더하기 4

알고리즘 save[0] = 1로 초기화 i : 1~3 에서 j : i~m 까지 save[j] += save[j-i] 를 반복 원리 i = 1에서는 1로만 쭉 더한 것(3 = 1 + 1 + 1) 경우만 따짐. i = 2에서는 1로만 더한 것에 2를 ...

1890 점프

알고리즘 save[0][0] = 1 부터 시작해서, 모든 점을 순서대로 방문하며 자신의 위, 왼쪽 점들 중 자신에게로 올 수 있는 모든 점들을 save[i][j]에 저장한다. 코드 #include <iostream> typedef long long ll; using namespace std; int n, map[101][101...

11048 이동하기

알고리즘 (1,1)에서부터 순서대로 모든 점을 대상으로 visit (i -1, j), (i, j-1), (i-1,j-1) 이 가지고 있는 점 중 가장 큰 점을 visit(i,j) 에 저장한다. 코드 #include <iostream> using namespace std; int visit[1010][1010], n, m, m...

14529 Where's Bessie

알고리즘 그냥 조건에 맞춰서 풀면 되는 문제이다. 가능한 모든 사각형에 대해 검사하고, pcl을 뽑아낸 다음에, 마지막으로 ‘다른 pcl에 포함되는 pcl’을 제거하고 출력하면 된다. ‘한 색은 연속된 지역이 하나이고, 다른 색은 두개 이상이어야한다. ‘ 라는 조건은 다음과 같이 하면된다. 인접한 같은 색에 대해서만 ...

diff --git a/page22/index.html b/page22/index.html new file mode 100644 index 000000000..9af539a33 --- /dev/null +++ b/page22/index.html @@ -0,0 +1 @@ + 디피의 개발일지
Home
디피의 개발일지
Cancel

브라우저 동작원리

기본 구조 사용자 인터페이스 브라우저 엔진 : 사용자 인터페이스와 렌더링 엔진 사이의 동작을 제어 렌더링 엔진 : 요청한 콘텐츠를 표시. HTML 을 요청하면 HTML과 CSS를 파싱하여 화면에 표시함. 통신 UI 백엔드 : 콤보박스와 창 같은 기본적인 장치를 그림. 플랫폼에서 명시하지 않은 일반적인 인터페이스로서 OS 사용자 인...

10875 뱀

알고리즘 뱀이 성장하면서 생기는 가로직선, 세로직선을 따로 저장. 새롭게 생기는 직선이 이전에 저장된 직선에 걸리는지 확인. 이때, 직선은 정렬하여 뱀이 나아가는 방향에서 가장 가까운 직선에 걸리도록 하자. 여담 깔끔하게 구현하기에 매우 애를 먹었고, 가능하지도 않았다. 그만큼 분리하여 생각해야하는 조건들이 많고, 그렇기에 실...

9874 Wormholes

알고리즘 페어링 쌍을 만들고, 사이클이 있는지 확인하는 작업을 모든 페어링 쌍을 대상으로 하면 됨. 헤멘 부분 사이클을 찾을때 같은 페어링쌍을 다시 방문하는게 아니라, 같은 점을 다시 방문하는지 확인해야함. check 를 언제 초기화해야하는지 헷갈림 -> 확실히하려면 매 시작점마다하면 됨. ...

5827 What's Up With Gravity

알고리즘 BFS 사용 시작할때 C는 떨구고 시작. bfs를 진행하는데 왼쪽, 오른쪽, 중력 바꿔서 검사하고 떨군다. 떨어지는 도중에 D를 만나면, 만났다고 표시 -> 끝나고 바로 ans 업데이트 이때 visit은 해당 위치에서 해당 중력일때 최소 flip을 저장함 나는 ...

React native 기초

개발환경 만들기 필요한 것 : 안드로이드 스튜디오, expo, 안드로이드 emulator npm install -g expo-cli 안드로이드 스튜디오 설치 -> 설치 후 sdk도 설치 안드로이드 스튜디오을 키고, configuration에 들어가서 AVD manager을 누르고, cr...

14466 소가 길을 건너간 이유 6

알고리즘 모든 소를 대상으로 ‘길’이 있는 길로 다니지 말고 BFS를 함 다른 소를 만날때마다 count를 1씩 증가시키고, 한 소의 bfs가 다 끝나면 n - count를 ans에 저장 ans / 2를 출력 코드 #include <iostream> #include <list> #include <cstri...

16639 괄호 추가하기 3

알고리즘 브루트포스로 함. 서로 이웃한 정수끼리 연산을 하는 조합을 하는데, 모든 조합을 전부 다 돌린다. 숫자는 최대 10개이고, 브루트포스로 했을때 O(n!) 이므로, 충분히 시간안에 돌릴 수 있다. 코드 #include <iostream> using namespace std; long...

18500 미네랄 2

알고리즘 2933 미네랄과 상당히 유사한 문제이지만, “분리된 클러스터의 각 열중 맨 아래 부분이 아닌 부분이 다른 클러스터 위에 떨어질 수 있다”라는 조건을 하나 더 생각해야함. 막대를 던지고 클러스터가 분리되면 바닥에 닿았는지 판단. 닿지 않았다면 아래로 내리는데, 내려갈 수 있는 미네랄을 다 검사. 가장 적게 내려갈 수 있는 거...

10021 watering the field

알고리즘 크루스칼 모든 필드 사이의 거리를 계산하고, 양 쪽 필드의 번호와 거리를 벡터에 저장한다. 거리순으로 정렬하고, 크루스칼 알고리즘 시행 이때, 단순히 union-find 알고리즘으로 하면 find에서 시간초과가 난다. 따라서 set을 활용해서 크루스칼을 하면 된다. 헤맨 지점 set을 이용해서 할때, 서로 다른...

5213 과외맨

알고리즘 BFS로 하나씩 찾아갈 수 있지만, 나는 연결되어있는 타일을 그래프로 표현한 다음 BFS를 하였다. BFS를 할땐, visit을 -1로 초기화하고, 이전에 온 곳을 기록하는 형식으로 하자. 그러면 마지막에서부터 온곳을 탐색하며 가서 첫번째 타일까지 가면 된다. 코드 #include <io...

diff --git a/page23/index.html b/page23/index.html new file mode 100644 index 000000000..23d0f4938 --- /dev/null +++ b/page23/index.html @@ -0,0 +1 @@ + 디피의 개발일지
Home
디피의 개발일지
Cancel

Graph ql front

설치 yarn add @apollo/react-hooks apollo-boost graphql 사용법 GraphQL API로 요청을 보낼때는, 요청문을 axios나 fetch를 사용하여 POST 메소드로 보내야함. 하지만 Apollo를 사용하면, 위와같이 하지 않아도 된다. 먼저, Apollo ...

Graph ql back

Qraph QL 이 해결할 수 있는 문제 over-fetching : 요청한 것보다 많이 보내줌. ex) username만 필요한데, 프로필 사진도 보냄 under-fetching : 하나의 작업을 위해 여러 요청을 보내게 하는 것. QraphQL에는 URL이 존재하지 않음. 하나의 엔드포인트만 있고, ...

3108 logo

알고리즘 겹치는 사각형을 각기 다른 집합으로 분류 + (0,0) 점이 속하는 집합이 있는지 확인 앞에서부터 겹치는 사각형을 확인하고, 겹치면, 큐의 맨 앞에 넣어서 DFS 방식으로 집합을 분류 매번 모든 사각형을 검사하는데, 이미 분류된 사각형은 다시 검사하지 않아도 됨. 이때 (0,0,0,0)인 사각형(사실은 점)을 1개 더 저장하...

1039 교환

알고리즘 DP 사용. bottom-up 방식으로 끝까지 간 후, 하나씩 올리면서 가장 큰 걸 반환 0<= i < j <= M 일때, array[i] == 0 이고, j == M이면 바꾸지 않음. 10 이하는 무조건 -1, 나머지는 0이 반환되면 한번도 끝까지 도달하지 못한 것이므로 K번 바꾸기 연산이 불가능한 것. 즉...

Junctions/Seoul 2021 해커톤

Junctions/Seoul 2021 해커톤 Junction 은 핀란드에서 시작된 국제 해커톤으로, 2박 3일동안 진행된다. Autocrypto, Microsoft, SIA, AWS game tech 4개의 기업이 파트너로 참가하고, 참가자들은 이 4개의 기업 중 하나를 자신이 참가할 track으로 선택하여 참여한다. 수상은 track ...

로티(lottie) 애니메이션 적용

lottie 애니메이션 Junction 해커톤을 하며 lottie 애니메이션을 접하게 되었다. 간단히 코드로 불러올 수 있으며, json 파일로 불러올 경우 색상 변경 등의 커스터마이징도 가능하여 활용도가 높다. 불러오는 법(react 기준) npm install @lottiefiles/react-lottie-player ...

CSS 단위

em : 부모의 단위에 배수를 더하는 것. body { ​ font-size:14px; } div { ​ font-size:1.2em; } 면 div엔 16.8px로 들어간다. 이때 부모의 크기에서 배수를 더하는 거라서, 자식마다 em을 써서 내려가면 크기는 계속 배수로 증가하게 된다. rem root의 단위에 배수를 더하는 것. ...

CSS position 에 관하여

static 기본 속성 원래 있어야할 위치에 있다. top, left, right, bottom 으로 위치 조절 불가능 relative static 일 때의 위치를 기준으로 조절 가능. 겹치는 element 가 있으면, z-index로 결정한다. absolute position:static 을 가지고 있지 않은 ...

JunctionXSeoul 2021 후기

결과 AWS game tech 트랙 우승! 후기 해커톤 첫 참여였지만 좋은 팀원들과 함께해서 트랙 우승을 이루어내 기뻤다. front-end 참여하여 메인 기능과는 보다는 디자이너님이 만들어주신 뷰 구현에 힘을 쏟았다. 배운 것도 많았고, 더 공부할 것도 많이 보였다. 배운 것 tool 관련 ...

16928 뱀과 사다리게임

알고리즘 BFS로 가능한 경우를 찾아가면 된다. 그런데 주의점은, 뱀을 타는게 더 이득일 경우가 있다는 것만 주의하자 ex) 2 1 2 60 30 99 65 29 코드 #include <bits/stdc++.h> using namespace std; int N, M,...

diff --git a/page24/index.html b/page24/index.html new file mode 100644 index 000000000..5081ddb04 --- /dev/null +++ b/page24/index.html @@ -0,0 +1 @@ + 디피의 개발일지
Home
디피의 개발일지
Cancel

14238 출근기록

알고리즘 DP 사용 앞에서부터 백트래킹처럼 조건이 맞을때 전진 만약 끝까지 갔으면 gotAns = true 설정하여, true이면 바로 종료. 만약 끝까지 안갔으면, save에 저장 이때 save[현재-2 문자][현재-1문자][남은 A][남은 B][남은 C] 로 함 현재 상태를 결정하는 건, 전전문자, 전 문자...

2437 저울

알고리즘 정렬 후 앞에서부터 검사 (이전까지 연속적으로 잴 수 있는 무게의 최댓값 + 1) 보다 현재 추가 더 무거우면 break. 최댓값 + 1이 잴수없는 최소의 값임. 코드 #include <bits/stdc++.h> using namespace std; int n, weights[1010]; int main() {...

15971 두 로봇

알고리즘 BFS로 시작 정점부터, 이제까지 경로 중에 있었던 가장 큰 weight(bigest)와 이제까지의 weight를 모두 합한 값(length)을 저장해나가며 진행. 이때 visit을 표시할 때, true/false 값이 아닌, length을 저장. BFS를 진행할 땐, node에 저장된 length와 현재 로봇의 length와 ...

Firebase/Function firestore, storage 쓰기, 읽기

firebase cloud function에서 firestore와 storage의 이벤트트리거를 등록하는 예제는 firebase 문서에 자세히 설명되어있다. 여기서는 cloud functions에서 firestore와 storage에 접근하여 읽고 쓰는 법을 간단한 코드로 보여줄 것이다. firestore firebase ...

Firebase/Function Puppeteer 사용하기

일반 노드에서의 환경과 같이 사용하면 된다. 홈페이지에 접속 후 스크린샷을 찍어 스토리지에 업로드하는 예시 const puppeteer = require("puppeteer"); const { Storage } = require("@google-cloud/storage"); const storage = new Storage(); rout...

REACT/CORS 개발 환경에서, 외부 API 와 연결할때 쿠키가 생성되지 않는 문제

이미 서버에 올라간 백엔드 API 와 로그인 작업을 하던 도중에, 백엔드에서 보내는 쿠키를 브라우저가 저장하지 않는 문제를 발견했다. 찾아보니 CORS 위반으로 생기지 않는 것이라고 하였다. 그럴때 해결방법은 두가지가 있다. package.json에 proxy 설정 추가 //package.json { ... "proxy" ...

노드 스터디 7장

MySQL 데이터베이스란 DBMS : 데이터베이스 관리시스템 RDBMS : 관계형 DBMS. 대표적으로 Oracle, mysql, mssql 등이 있음. Datagrip 사용 Datagrip : 데이터베이스를 위한 IDE. 학생인증을 하면 무료사용가능 프로젝트 생성 alt+insert 혹은 좌측 + 클릭 -> ...

노드 스터디 6장

Express 로 웹서버 만들기 6.1 익스프레스 프로젝트 시작 const express = require("express"); const app = express(); app.set("port", process.env.PORT || 3000); app.get("/", (req, res) => { res.send("Hello, Ex...

노드 스터디 4장

http 모듈로 서버 만들기 4.1 요청과 응답 이해하기 - req : 요청에 관한 정보 - res : 응답에 관한 정보. - .writeHead : 헤더. 응답에 대한 정보를 기록하는 메서드. - 첫번째 인수 : 코드 - 두번째 인수 : 응답에 대한 정보를 알림. - .write :...

8980 택배

8980 택배 풀이(그리디) 1. 각 도착지별로 정보를 따로 관리함.(같이해도 되긴함) 2. 도착지 1~N번까지 입력받은 C로 트럭 용량을 초기화함. 3. 도착지 2번부터 정보를 하나씩 뽑아가며 검사. 시작지점~현재 도착지-1 까지에서의 최소값을 뽑고, 그만큼을 2의 배열에서 빼줌.(물론 그 최솟값이 현재 정보에서의 택배량보다 많으면 택배량으로 제...

diff --git a/page25/index.html b/page25/index.html new file mode 100644 index 000000000..58b8970ba --- /dev/null +++ b/page25/index.html @@ -0,0 +1 @@ + 디피의 개발일지
Home
디피의 개발일지
Cancel

노드 스터디 5장

패키지매지저 package.json 설치한 패키지의 버전 관리 노드 프로젝트를 시작하기 전에 무조건 package.json부터 만들고 시작해야함. npm init 으로 프로젝트 생성하면 만들어짐. scripts npm 명령어를 저장해두는 부분. 저장된 명령어를 npm run 으로 실행한다. ...

노드 스터디 3장

Node 3장 REPL 사용하기 노드 콘솔은 REPL이라하는데 이유는 Read : 입력한 코드를 읽고 Eval : 해석하고 Print : 결과물을 반환하고 Loop : 종료할 때가지 반복함. 터미널에 node를 입력함으로서 접속가능 간단한 명령어 수행 JS...

13303 장애물 경기

13303 장애물 경기 알고리즘 1. 장애물을 x 좌표 순으로 정렬. 도착지점의 Y좌표와 걸린 길이(y축 이동만 고려)를 저장하는 set을 선언 후, {startY, 0} 를 추가 2. 장애물을 하나씩 꺼내며, 그 장애물에 걸리는 원소만 set에서 꺼내고 하나씩 위로갔을때 거리와 아래로 갔을때 거리를 계산 이때, 꺼낸 것이 10개여도 장애물...

9095 1,2,3 더하기

9095 1,2,3 더하기 코드 #include <iostream> using namespace std; int tc; long long save[12]; long long dp(int n); int main() { cin >> tc; while (tc--) { int n; cin >> n; ...

5373 Samsung sw test

5373 Samsung sw test 코드 #include <iostream> #include <cstring> #define MAX_ROTATE 1000 #define MAX_TESTCASE 100 #define WHITE 100 #define YELLOW 200 #define RED 300 #define ORANGE 400...

3568 iSharp

3568 iSharp 코드 #include <iostream> #include <string> using namespace std; string def, append; char input[130]; void print(); int main() { cin.getline(input, 130); int i = 0; w...

2623 MusicPropram

2623 MusicPropram 코드 #include <iostream> #include <vector> #include <list> #include <cstring> #define MAX_SINGER 1000 #define MAX_PD 100 using namespace std; typedef str...

2473 Three Liquid

2473 Three Liquid 코드 #include <iostream> #include <algorithm> #include <set> #define MAX_LIQUID 5000 using namespace std; int liquid[MAX_LIQUID]; int numOfLiquid; int threeLiq...

20058 Samsung sw test

20058 Samsung sw test 코드 #include <iostream> #include <list> #include <cstring> #define MAX_WIDTH 64 #define MAX_COMMAND 1000 using namespace std; typedef struct Location { i...

20057 Samsung sw test

20057 Samsung sw test 코드 #include <iostream> #define MAX_WIDTH 499 using namespace std; int map[MAX_WIDTH][MAX_WIDTH]; bool visit[MAX_WIDTH][MAX_WIDTH]; int width, amountOfSand = 0; int t...

diff --git a/page26/index.html b/page26/index.html new file mode 100644 index 000000000..88b60744b --- /dev/null +++ b/page26/index.html @@ -0,0 +1 @@ + 디피의 개발일지
Home
디피의 개발일지
Cancel

20056 Samsung sw test

20056 Samsung sw test 코드 #include <iostream> #include <cstring> #include <vector> #define MAX_WIDTH 50 using namespace std; typedef struct Fire { int mass; int direction; //...

19238 Samsung sw test

19238 Samsung sw test 코드 #include <iostream> #include <list> #include <vector> #include <algorithm> #define MAX_WIDTH 20 #define MAX_CUSTOMER 400 #define CUSTOMER 123 usi...

19236 Samsung sw test

19236 Samsung sw test 코드 #include <iostream> #include <cstring> #define WIDTH 4 #define SHARK 100 #define BLANK -1 using namespace std; typedef struct Fish { int num; int row; in...

1799 bishop

1799 bishop 코드 #include <iostream> #include <cstring> #define MAX_N 10 using namespace std; int map[MAX_N][MAX_N]; bool tempMap[MAX_N][MAX_N] = { 0, }; int width, maxCount = 0; voi...

17825 Samsung sw test

17825 Samsung sw test 코드 #include <iostream> #include <vector> using namespace std; typedef struct Map { int score; int next; int blueNext; } Map; int dice[10]; Map map[33] = { {...

17822 Samsung sw test

17822 Samsung sw test 코드 #include <iostream> #include <cstring> #include <vector> #define MAX_BOARD 50 #define MAX_TURN 50 using namespace std; int board[MAX_BOARD][MAX_BOARD]...

17779 Samsung sw test

17779 Samsung sw test 코드 #include <iostream> #define MAX_WIDTH 21 using namespace std; int map[MAX_WIDTH][MAX_WIDTH]; int width; int minDifference(); int getMin(int d1, int d2, int x, in...

1748 수 이어쓰기 1

1748 수 이어쓰기 1 코드 #include <iostream> using namespace std; int N; int main() { cin >> N; long long result = 0; int d = 1, count = 0; while (N / d != 0) { // 1~9 : 1 / 10~99 :...

17142 Samsung sw test

17142 Samsung sw test 코드 #include <iostream> #include <cstring> #include <vector> #include <list> #include <algorithm> #define MAX_WIDTH 50 #define WALL 1 using nam...

17140 Samsung sw test

17140 Samsung sw test 코드 #include <iostream> #include <algorithm> #include <cstring> #include <vector> #define MAX_NUM 101 using namespace std; typedef struct Cell { in...

diff --git a/page27/index.html b/page27/index.html new file mode 100644 index 000000000..f780abef7 --- /dev/null +++ b/page27/index.html @@ -0,0 +1 @@ + 디피의 개발일지
Home
디피의 개발일지
Cancel

1629 곱셈

1629 곱셈 코드 #include <iostream> using namespace std; long long A, B, C; long long result = 1; int main() { cin >> A >> B >> C; while (B != 0) { if (B % 2 == 1) { ...

16236 Samsung sw test

16236 Samsung sw test 코드 #include <iostream> #include <list> #define MAX_WIDTH 20 using namespace std; typedef struct Location { int row; int col; int distance; bool operator &l...

16235 Samsung sw test

16235 Samsung sw test 코드 #include <iostream> #include <algorithm> #include <vector> #define MAX_WIDTH 10 using namespace std; typedef struct Tree { int age; bool isAlive; b...

16234 Samsung sw test

16234 Samsung sw test 코드 #include <iostream> #include <list> #include <cstring> #define MAX_WIDTH 50 using namespace std; typedef struct Location { int row; int col; } Locat...

15686 Samsung sw test

15686 Samsung sw test 코드 #include <iostream> #define MAX_WIDTH 50 #define MAX_CHIKEN 13 using namespace std; typedef struct Location { int row; int col; } Location; int width, maxChike...

15685 Samsung sw test

15685 Samsung sw test 코드 #include <iostream> #include <vector> #define MAP 101 #define MAX_DRAGON 20 #define MAX_GENERATION 10 using namespace std; typedef struct Location { int ro...

15684 Samsung sw test

15684 Samsung sw test 코드 #include <iostream> #define MAX_VERTICAL 10 #define MAX_HORIZONTAL 30 #define MAX_LINE 270 using namespace std; typedef struct Ladder { bool left; bool right; }...

15683 Samsung sw test

15683 Samsung sw test 코드 #include <iostream> #include <cstring> #define MAX_WIDTH 8 #define WALL 6 #define BLINDSPOT 0 using namespace std; typedef struct CCTV { int row; int col;...

15486 퇴사2

15486 퇴사2 코드 #include <iostream> using namespace std; int N, T[1500000], P[1500000], DP[1500000]; int dp(int deep); int max(int a, int b) { return a > b ? a : b; } int main() { ios:...

14891 Samsung sw test

14891 Samsung sw test 코드 #include <iostream> #include <cstdio> #define MAX_ROTATE 100 #define LEFT 6 #define RIGHT 2 using namespace std; int command[MAX_ROTATE][2]; int gear[4][8];...

diff --git a/page28/index.html b/page28/index.html new file mode 100644 index 000000000..d116a1ac0 --- /dev/null +++ b/page28/index.html @@ -0,0 +1 @@ + 디피의 개발일지
Home
디피의 개발일지
Cancel

14890 Samsung sw test

14890 Samsung sw test 코드 #include <iostream> #include <cstring> #define MAX_N 100 using namespace std; int map[MAX_N][MAX_N]; int width; int need; int checkEveryArr(); bool checkSi...

14889 Samsung sw test

14889 Samsung sw test 코드 #include <iostream> #include <cstring> #define MAX_WIDTH 20 using namespace std; int numOfPeople; int map[MAX_WIDTH][MAX_WIDTH]; int minDifferenceBetweenTw...

14888 Samsung sw test

14888 Samsung sw test 코드 #include <iostream> #define MAX_NUM 11 using namespace std; int num[MAX_NUM]; int numOfNum; int operators[4]; // + - * / int operatorPerm[MAX_NUM - 1]; // 1 : +, ...

14863 서울에서 경산까지

14863 서울에서 경산까지 알고리즘 (dp) 1. dp를 평범하게 진행. dp배열은 dp[101][100001] 로 앞에는 현재 위치, 뒤에는 시간 2. 시간이 맞으면 진행하되, 안맞으면 result 를 초기화해둔 -20000000이 그대로 있음. 3. 0보다 낮으면, 현재 시간포함 0초까지 -20000000로 초기화. 같은 상태에 더 낮은 시간...

14503 Samsung sw test

14503 Samsung sw test 코드 #include <iostream> #define MAX_WIDTH 50 using namespace std; int direction; int location[2]; int map[MAX_WIDTH][MAX_WIDTH]; int height, width; int robot(); bool...

14502 Samsung sw test

14502 Samsung sw test 코드 #include <iostream> #include <list> #include <cstring> #define MAX_WIDTH 8 using namespace std; typedef struct Cell { int row; int col; } Cell; int...

14501 Samsung sw test

14501 Samsung sw test 코드 #include <iostream> #define MAX_DAY 15 using namespace std; int day; int time[MAX_DAY]; int proceeds[MAX_DAY]; int DP(int d, int proceed); int max(int a, int b);...

13460 Gold 2

13460 Gold 2 코드 #include <iostream> #include <algorithm> #include <cstring> #define MAX_WIDTH 10 #define MAX_DEEP 10 using namespace std; char map[MAX_WIDTH][MAX_WIDTH]; int w...

12865 평범한 배낭

12865 평범한 배낭 코드 #include <iostream> using namespace std; int numOfObject, weight, DP[101][100001]; int main() { cin >> numOfObject >> weight; for (int i = 1; i <= numOfO...

9527 1의 개수세기

9527 1의 개수세기 알고리즘(수학) 1. 2^n 마다 규칙이 있음 2. f(n)을 2^n ~ 2^(n+1)-1 에서의 1의 개수라고 한다면, f(n) = 2^n + f(i) ( 0 <= i <= n-1 ) 이 성립한다. 3. 그리고 들어오는 수 A,B에 대해 (1~B 까지의 1의 개수) - (1~A-1 까지의 1의 개수) 를 하면 ...

diff --git a/page29/index.html b/page29/index.html new file mode 100644 index 000000000..5205201fd --- /dev/null +++ b/page29/index.html @@ -0,0 +1 @@ + 디피의 개발일지
Home
디피의 개발일지
Cancel

9466 Term project

9466 Term project 알고리즘 (DFS, 그래프) 1. 각 학생들이 가리키는 학생을 저장한 student 배열, cycle에 몇명이 있는지 체크하는 cycleCcount 변수, 사이클이 형성됐는지 확인하는 cycleCheck bool형 배열. 검사중인 학생들을 저장하는 q배열 2. 첫번째 학생부터 검사 -student가 -1 이면...

9328 key

9328 key 알고리즘(BFS, 구현) 1. 방문한 곳을 체크하는 check 이차원 배열, 키를 저장하는 key 배열, 닫힌문을 저장하는 closedDoor 벡터가 필요 2. 먼저 키 배열은 26개의 원소로 이루어진 bool 자료형 배열로 선언. 각 알파벳에 해당하는 키가 들어오면 1을 저장한다. 3. 방문가능한지 체크하는 checkCell 함수...

9252 LCS2

9252 LCS2 알고리즘(최장 공통 부분 수열) 1. LCS 알고리즘 사용하면 됨 구현법 1. 문자열 입력 받을 땐. cin.getline(char *c, size) 넣으면 된다. 코드 #include <iostream> #include <cstring> #define MAX_LENGTH 1000 using nam...

7579 app

7579 app 알고리즘(배낭정리, DP) 1. 배낭정리 응용. 최대 비용을 가방에 담을 수 있는 최대 무게로 보고, 메모리를 가치로 본다 2. 이렇게 2차원 배열을 완성하고, 필요한 메모리 이상이며 비용이 최소인 칸을 판별하고 출력하면 된다. 구현법 1. 최대 앱 개수는 100개이므로, 101행, 앱 당 최대 비용은 100 이므로 10001...

7453 four Integer's sum should be zero

7453 four Integer’s sum should be zero 알고리즘(정렬, 이분탐색) 1. 앞 두개 정수의 합 배열, 뒤 두개 정수의 합 배열을 만듬.(배열 개수^2 가 합 배열의 길이) 2. 두 배열을 정렬 3. 앞 합 배열의 원소를 하나씩 빼며 검사 -> 뒤 합 배열에서, upper_bound - lower_bound 값이, 현...

6087 레이저통신

6087 레이저통신 알고리즘(BFS) 1. visit 배열을 int로 선언. 이제까지 온 것들의 거울 개수를 저장 2. 갈 수 잇고, visit에 저장된 거울 개수와 같거나 작으면 이동 시킴 주의점 같은 레이저에 도달하지 않기위해 설정한 row != start.row && col != start.col 은 작동하지 않음 ->...

6064 카잉달력

6064 카잉달력 알고리즘(수학) 1. n 을 M과 N으로 나누었을때 나온 수가 x와 y 이면 된다. 단, 나누었을때 나오는 수는 0~M-1 or N-1인데 문제에선 1~M, 1~N 까지이므로 이걸 고려해야한다. 2. 하나씩 증가시켜가면서하면 너무 오래걸리니, M과 N 중 더 큰수를 더하는 수로 삼고, 그것과 연결된 수부터 시작한다(M-x, N-...

5014 스타트링크

5014 스타트링크 알고리즘 (BFS) - 직선으로의 BFS 를 사용하면 됨 - DP를 사용하면 시간초과가 발생함. - 도달시 다 종료한다고 해도, 그게 최소로 간건지 몰라서 결국 더 검사해야함. 코드 #include <iostream> #include <list> using namespace std; list<...

4991 로봇청소기

4991 로봇청소기 알고리즘(BFS, 브루트포스) 1. 구현이 더 중요한 문제. 2. 그냥 시작 + 각 더러운 곳(DT) 에서 다른 DT로의 최소 거리를 저장한 2차원 배열을 BFS로 만들고 3. 시작부터 모든 경로를 탐색하여 최소 거리를 뽑아내면 됨. 4. 방문 할 수 없는지 판별은 시작에서 갈 수 없는 DT가 있는지 확인하면 됨 구현 1....

4811 알약

4811 알약 알고리즘 (dp) 1. 하나의 완전한 알약을 one, 반쪽 알약을 half 라고 하면, dp[1001][1001] 를 선언하고, one, half를 기준으로 dp를 저장함 2. dp를 진행하면서 밑으로 내려가면서 one이 1개 이상이면 1 줄이고 half 1 증가하고 밑으로 내려갈 수 있고, half가 1개 이상이면 마찬가지로. ...

diff --git a/page3/index.html b/page3/index.html new file mode 100644 index 000000000..2a94ddcfb --- /dev/null +++ b/page3/index.html @@ -0,0 +1 @@ + 디피의 개발일지
Home
디피의 개발일지
Cancel

영상이 자동재생되지 않는 문제

다음과 같은 요구사항을 가지는 웹페이지를 개발하고 있었다. swiper로 여러 슬라이드를 띄움 슬라이드 내에는 영상이 존재하고, 각 영상은 자동으로 재생되어야함. 단순히 swiper를 연동하고 video 태그에 autoplay 속성을 주면 될거라 생각하여 다음과 같이 코드를 작성했었다. <li className="swiper-sl...

DB index

## 인덱스란? DBMS에서 데이터베이스 테이블의 모든 데이터를 검색하여 데이터를 찾기에는 시간이 오래 걸리기에, 데이터의 칼럼의 값과 그 데이터가 저장된 레코드의 주소를 키와 값의 쌍으로 만든 것. DBMS의 인덱스는 항상 정렬된 상태를 유지하기 때문에, 원하는 값을 탐색하는데는 빠르지만, 추가/삭제/수정에는 쿼리문 실행 속도가 느려진다.(작업...

딥링크

딥링크는 웹사이트에서 사용자 기기에 설치된 앱을 URI로 실행시킬 수 있도록하는 기술이다. 딥링크의 세가지 유형 이러한 딥링크에는 세가지 유형이 있다. URI Scheme (초기 형태) 보완 Universal Link (IOS) App Link (Android) URI Scheme 앱...

bfcache

BFCache 사용자가 브라우저 내에서 뒤로가기/앞으로가기를 할 때 이전 페이지를 자바스크립트 heap 영역까지 전체 캐싱하여 메모리에 저장하는 것 HTTP 캐시는 리소스만을 캐시하고 JS 작업까지 캐시하지 않기에 BFCache보다 빠르지 않다. BFCache 작동을 판단할 수 있는 API pageshow, pagehide pag...

beforeunload vs pagehide

ios 모바일에서 새로고침시 현재 스크롤 위치를 고정하기 위해 다음과 같은 코드를 사용하였다. if (sessionStorage.scrollPosition) { window.scrollTo(0, sessionStorage.scrollPosition); } window.onbeforeunload = function () { sessionSto...

useState 원리

State 변경 시 어떤 일이 벌어질까? 리액트의 함수형 컴포넌트는 최초에 한번 실행이 되면서 초기값으로 설정해놓은 상태를 기억한다. const [state, setState] = useState(0); 이후 setState가 호출되어 상태가 변경된다면 다시 함수형 컴포넌트가 실행되고 virtual DOM을 리턴한다. 그리고 이전에 리턴했던 v...

typescript 조건부타입(extends)

타입 스크립트 2.8부터 다음과 같은 조건부 타입을 사용할 수 있다. T extends U ? X : Y 뜻은 T가 U의 서브타입이거나 같은 타입이면 X, 아니면 Y 타입을 할당한다는 것이다. T가 유니온 타입일 경우 다음과 같이 T가 유니온 타입일 경우가 있다. type T = "A" | "B" | "C" 이러한 경우엔 분배법칙이 성립...

Javscript 메모리 누수 방지 및 성능 개선

Javscript 메모리 관리 이해하기 Garbage collector 자바스크립트 엔진은 더이상 사용하지 않을 메모리를 놓아주기 위해 garbage collector를 사용한다. garbage collector(GC)는 앱에서 더이상 사용하지 않을 객체를 찾아내고 삭제한다. 따라서 GC는 앱의 object와 변수를 계속 모니터링하고 어떤 것이 여...

Typescript 5.0

typescript 5.0이 나왔다고 하여 간단히 어떤 기능들이 추가되었는지 살펴보았다. 자세한 내용은 Typescript 5.0 번역에서 확인 가능하다. 데코레이터 데코레이터는 재사용 가능한 방식으로 클래스와 그 멤버를 사용자 정의하는 ECMAScript의 기능이다. 데코레이터는 이전부터 지원되었으나 Typescript 5.0부터는 공식 기능으로...

next.js appDir

AppDir next.js에서 layout과 routing 경험을 개선시키고 React 최신 기술을 지원하기위해, next.js 13부터 지원하는 디렉터리 방식. 기존의 pages 디렉터리를 대체한다. 23년 04월 기준으로 아직은 베타버전으로 프로덕션에서 사용하는 건 추천하지 않는다고 한다. 하지만, 좋은 기능들이 많기에 학습해봐도 괜찮을 거 같...

diff --git a/page30/index.html b/page30/index.html new file mode 100644 index 000000000..23aa88caa --- /dev/null +++ b/page30/index.html @@ -0,0 +1 @@ + 디피의 개발일지
Home
디피의 개발일지
Cancel

4386 make constellation

4386 make constellation 알고리즘(MST) 1. 모든 별들 사이의 거리를 구한 다음에, 두 별의 위치, 거리를 저장한 구조체 배열을 거리순으로 오름차순 정렬 2. 구조체 배열에서 하나씩 빼가며 크루스칼 시행 -> edge == numOfStar -1 이면 중지 후 출력 코드 #include <iostream>...

3197 백조의 호수

3197 백조의 호수 알고리즘(구현) 처음 방법 1. 한 백조에서 BFS 실시하여 다른 백조를 만날 수 있는지 검사 2. 전체 맵을 검사해서 녹일 수 있는 걸 녹임 3. day 추가하고 다시 1부터 -> 시간이 너무 걸림. 특히 맵 초기화하는 부분이 많이 걸림 두번째 방법 1. 1번은 유지 2. 녹을 수 있는 얼음만 저장한 q에서 ...

3190 Samsung sw test

3190 Samsung sw test 알고리즘 1. 그냥 대가리 옮기고 꼬리 잡히는지, 칸 밖인지 확인하고, 또 사과있는지 확인하고, 또 시간이 바꿀때가 됐는지 확인 2. 뱀 몸은 vector로 저장 / 사과 위치는 2차원 bool 배열로 저장하여 메모리랑 참조시간 아낌. / 시간은 Direction 구조체에 저장하고, directionInde...

2933 미네랄

2933 미네랄 알고리즘(구현, 시뮬레이션) 1. 들어온 위치의 미네랄을 지움 2. 지운 위치에서 상하좌우에 인접한 칸들을 root로 BFS 시행 -> 바닥에 닿는 칸이 있으면 공중에 뜬 클러스터가 아님 3. 공중에 뜬 클러스터이면 BFS 시 넣은 칸들을 열순, 열이 같으면 행이 큰 순으로 정렬 4. 각 열별로 가장 밑에 있는 칸만 검사하여 ...

2931 가스관

2931 가스관 알고리즘(구현) 1. M에서 시작해 갈 수 있는 방향으로 가다가 빈칸을 마주하면 스탑. 만약 빈칸이 없으면 아예 처음부터 연결이 안된 것. 이때는 Z 부터 시작해 빈칸을 찾는다. 2. 빈칸을 찾으면 빈칸 주변을 검색. 인접한 칸에 바로 연결되는 파이프가 있으면 그 칸을 true로 표시. 표시된 것에 따라 빈칸에 넣을 수 있는 가...

2887 planet tunnel

2887 planet tunnel 알고리즘(정렬, MST) 1. 모든 행성들의 x좌표, y좌표, z좌표를 꺼내 따로 저장하고, 오름차순으로 정렬 2. 정렬한 것에서 인접한 것끼리 비용을 계산, (인접한 점 두개 + 비용)을 구조체로 n-1개의 원소를 가지는 배열 3개를 선언하고 집어넣음 3. 그 구조체배열 3개는 비용을 기준으로 오름차순으로 정렬 ...

2568 전기줄

2568 전기줄 알고리즘(LIS) 1. LIS를 구하면 그것들이 겹치지 않고 최대로 연결가능한 전깃줄들이다. 2. 따라서 LIS를 구하고 전체 전깃줄에서 LIS를 빼면 빼야하는 전깃줄의 수와 전깃줄의 종류가 나오게 된다. 코드 #include <bits/stdc++.h> #define MAX_WIRE 100001 using nam...

2529 부등호

2529 부등호 알고리즘(브루트포스, 백트래킹) 1. 모든 경우를 탐색하되, 앞에서부터 하나씩 채워가면서 재귀형태로 하면 백트래킹도 적용하기 쉽다. 2. 조건에 맞는 경우만 탐색하면 됨. 3. 낮은 거 먼저 탐색하도록하면 굳이 뭘 저장할 필요없이 맨처음것만 minimum에 저장하고, 제일 나중 것을 maxnimum에 저장하면 된다. 코드 #i...

2342 Dance Dance Revolution

2342 Dance Dance Revolution 알고리즘(DP) 1. 왼발 위치, 오른발 위치, 명령 번호를 기준으로 dp 자료를 만듬 dp[5][5][MAX_COMMAND] 2. 명령과 같은 칸에 있을 경우 그대로, 다른 위치에 있을 경우 가능한 위치엔 전부 옮기는 식으로 DP 시행. 코드 #include <iostream> ...

2252 줄세우기

2252 줄세우기 알고리즘(위상정렬) 1. 그냥 위상정렬하면된다. 구현 1. Student 구조체 : beforeCount : 이전에 몇개있는지 셈, next : 큰 학생들 나열, isChecked : 라인에 넣어졌는지 확인 2. 처음에 beforeCount가 0인 학생들을 큐에 넣고, isCheked에 체크함 3. 하나씩 큐에서 빼면서 아래...

diff --git a/page31/index.html b/page31/index.html new file mode 100644 index 000000000..a78aa2b07 --- /dev/null +++ b/page31/index.html @@ -0,0 +1 @@ + 디피의 개발일지
Home
디피의 개발일지
Cancel

2239 sudoku

2239 sudoku 알고리즘(백트래킹) 1. 칸을 하나씩 탐방하면서, 0으로 기록돼있다면 수직, 수평, 3x3사각형을 검사하여 가능한 숫자를 하나씩 뽑아서 넣고 다음 칸 검사 2. 만약 0으로 기록되지않은 칸으로 갔다면 다음칸으로 넘겨주되, 마지막 칸일 경우 스도쿠가 완성된 것이므로 true를 반환한다. 3. 마찬가지로, 넣고 다음 칸으로 이동하...

2186 문자판

2186 문자판 알고리즘(DFS, DP) 1. 문자판을 주어진 조건대로 순회하되, 결과값이 int 안이라는 걸보고 엄청 클 수도 잇다는 걸 예측 or 빙글빙글 계속 돌 수 있으니 DP가 잇어야겠다고 예측 2. DP를 DP[r][c][d] 로 구현하되, -1로 초기화를 시키고, 0인것도 기록 -> 문자열이 아예 없을 수도 잇으니 0도 기록하여 ...

2166 areaOfPolygon

2166 areaOfPolygon 알고리즘(기하학) 1. 사선정리 사용 구현법 1. 최대 4000000000000이 나올 수 있으므로, int대신 long long, float은 쓰지말고 double로 통일 시킨다. 2. double은 최대 15자리수를 표현가능하므로, 위 값도 충분히 들어갈 수 있다. 코드 #include <ios...

2151 거울설치

2151 거울설치 알고리즘 (BFS) 1. 자기 방향으로만 쭉 가다가 ! 를 만나면 자기방향 + 양옆으로 이동. 양옆으로 이동할 땐 거울을 설치하는 것이므로 거울 + 1. 2. 이때 visit으로 이미왔던곳을 안가면, 다른 루트로 가는 빛은 중간에 끊기기에, visit에 빛이 그곳에 도착했을 때의 거울의 개수를 저장함. 그리고 다음 빛이 그곳에...

2143 sumOfTwoArray

2143 sumOfTwoArray 알고리즘(이분탐색, 누적합) 1. A배열은 그대로 두고, B 배열은 가능한 모든 누적합의 집합을 만들고, 그것을 정렬함 -> 이때 그냥 배열에 집어넣으면 최대 500500개의 원소가 있어서 메모리에서 박살나고, 찾는데 시간도 박살남 -> 난 set을 택했음 -> Pair 구조체를 선언해서...

2098 TSP

2098 TSP 알고리즘(외판원 순회, DP, 비트마스크) 1. 비트마스크로 각 비트마다 하나의 도시라고 치고, 검사를 함. 2. 방문하지 않았고, 갈수 있는 도시를 방문하는 식으로 끝까지감. -> 끝까지 갔을 때 다시 돌아갈 수 있는 길이 있으면 그 값 반환, 없으면 IMPOSSIBLE 반환 -> 그 전 재귀로 돌아와서 가장 최솟값...

2056 task

2056 task 알고리즘(위상정렬) 1. Task 구조체를 선언하여, time : 현재 task가 걸리는 시간을 저장 / timeTaken : 현재까지 오는데 선행으로 필요한 작업을 수행하면서 걸리는 시간 중 가장 긴 시간 저장 next : 다음으로 이어지는 작업들을 저장 2. Task 구조체 배열을 앞에서부터 검사 -> 현재 tas...

20055 Samsung sw test

20055 Samsung sw test 알고리즘 (구현) 1. durability와 isThereRobot 변수로 구성된 Cell 구조체로 컨베이어 벨트를 만듦 2. 무한 루프 안에서, 이동시키고 -> 내리는 위치 로봇빼고 -> 로봇이동 시키고(이동시 내구도 1감소) -> 내리는 위치 로봇빼고 -> 올리는 위치 로봇 올리고(내...

1987 Alphabet

1987 Alphabet 알고리즘(백트래킹) 1. 어떤 알파벳을 검사했는지 기록하는 건 check[26] 배열을 선언하고, 각 알파벳에서 65 값을 빼서 각 자리로 접근한다.('A' - 65 == 0) 2. 인접한 칸을 검사하고, 가능하면 check 표시하고 인접한 칸으로 이동. 안되면 돌아와서(Back Tracking) 다른 인접한 칸 검사 3....

1956 운동

1956 운동 알고리즘(플로이드 워셜) 1. 문제 파악 : 그래프 상의 어떤 점들 집합이건 최소로 사이클을 이루는 집합이면 됨 2. 그래프 관련 알고리즘 1. 다익스트라 : 출발점에서 모든 정점까지의 거리 -> 해당 x 2. 밸만포드 : 다익스트라와 마찬가지 3. MST : 최소신장트리 -> 사이클은 감지할 수 있으나, 그것이 최소...

diff --git a/page32/index.html b/page32/index.html new file mode 100644 index 000000000..60528ce4d --- /dev/null +++ b/page32/index.html @@ -0,0 +1 @@ + 디피의 개발일지
Home
디피의 개발일지
Cancel

19235 모노미노도미노

19235 모노미노도미노 알고리즘(구현) 1. 파란 맵과 초록 맵을 따로 만듬.(bool 형으로 해서 true면 블록이 있고, false면 없는 거로 취급) 2. 매 실행마다 블록을 잘 놓고 -> 업데이트 하면서 점수 얻고 -> 연한부분 있으면 당긴 다음에 -> 다음 실행 3. 실행마다 얻은 점수를 잘 기록해서 리턴함. 그리고 남은...

1865 웜홀

1865 웜홀 코드 #include <bits/stdc++.h> using namespace std; int tc, N, M, W, road[501][501]; long long d[501]; bool check[501][501]; vector<int> connect[501]; bool belmanford(); int ...

1806 partial sum

1806 partial sum 알고리즘 (브루트포스) 1. 길이를 1부터 N까지 검사 2. 한 길이를 검사할 때 다음을 실행 1. 수열의 처음부터 길이만큼 더하고 sum에 저장, 첫번째를 따로 first에 저장. -> S를 넘는지 확인하고 안넘으면 2 실행 2. 수열의 (길이)번째부터 검사. 위에서 구한 sum에서 first를 빼고, 현재...

17837 새로운게임2 Samsung sw test

17837 새로운게임2 Samsung sw test 알고리즘(구현) 1. 색깔을 저장한 map, 말을 저장하는 3차원 배열 맵(각 칸마다 4칸만 있으면 됨), 각 칸에 말이 몇개있는지 저장하는 인덱스맵 2. 턴을 계속 진행함. 각 말을 순차적으로 검사함. 이때 각 말들을 몇 번 검사했는지 저장하는 temp 배열을 생성. 방향과 색깔에 맞춰 이...

1753 최단경로

1753 최단경로 알고리즘 1. 다익스트라 코드 #include <iostream> #include <queue> #include <vector> #define INF 2000000000; using namespace std; int V, E, s, length[20001]; vector<pair<...

17404 RGB Distance 2

17404 RGB Distance 2 알고리즘 : DP 1. bottom-top 방식으로 품 2. DP 구성 : (현재 깊이, 이전에 고른 색깔)칸에 현재 깊이에서의 최솟값 저장. 3. DP 작동 a. 첫번째 집은 for문으로 하나씩 들어감. b. 두번째 집부터 이전에 고른 색이 아닌 색으로 이동 c. 마지막집 + 1에 도착하면 0을 리턴 ...

17144 Samsung sw test

17144 Samsung sw test 알고리즘 1. 기존 map에서 미세먼지가 있는 부분만, 기존에 있던 미세먼지만 확산시키는 게 관건 2. 따라서 tempMap을 만들어서 map을 복사한 다음에, map에 있는 미세먼지 양을 받아서, tempMap에 확산시키고, 다 확산하면 tempMap을 map에 복사하고 순환하여야 한다. 3. 자꾸 갱신되는...

16954 움직이는 미로탈출

16954 움직이는 미로탈출 알고리즘(BFS) 1. q와 nextq를 선언 2. q엔 현재 노드를 저장. 하나식 꺼내서 이동가능한 인접 칸을 nextq에 추가. 3. 다 추가하면, 벽을 한칸 내림 4. 이후 q에 nextq를 넣고, nextq는 비운채 다음 루프 시작. 5. 루프 중에 q에서 꺼낸 노드가 벽이 있는 칸이라면 추가안하고 다음 노드로,...

16946 벽부수고 이동하기 4

16946 벽부수고 이동하기 4 알고리즘 (BFS) 1. 각 맵의 빈공간마다 각자의 크기를 BFS로 구함 2. 이때, 각자의 id를 지정하여 같은 집합에 속하면 같은 id를 갖도록 하자 3. BFS가 끝나면 각 벽을 검사 -> 주변에 인접한 모든 빈공간의 아까 구한 크기를 모두 더하고 자기자신 + 1해서 리턴 -> 이때, 주변에 인접...

16724 피리부는 사나이

16724 피리부는 사나이 알고리즘 (DFS) 1. 맵에 있는 서로 이어져있는 그래프의 개수를 세면 됨. 2. visit을 만들어, 이미 검사한 그래프의 원소엔 true를 표시한다. 그리고 tempVisit을 만들어, 현재 검사하고 있는 그래프의 원소에 true를 표시한다. 3. 먼저, 첫번째 셀부터 검사하지 않았으면 DFS를 실시한다. ->...

diff --git a/page33/index.html b/page33/index.html new file mode 100644 index 000000000..6536c3766 --- /dev/null +++ b/page33/index.html @@ -0,0 +1 @@ + 디피의 개발일지
Home
디피의 개발일지
Cancel

16566 카드게임

16566 카드게임 알고리즘(이분탐색, 정렬, 분리집합, 세그먼트트리?) 1. 고른 카드를 버킷정렬함(400만개이므로 버킷정렬이 훨씬 빠르다. sort는 NlogN) 2. 범위를 저장하는 set<pair<int,int>>을 선언하고 첫 범위를 넣음(0-m) 3. 철수가 뽑은 카드의 수가 저장된 범위의 끝의 수보다 작을 때의 범...

16562 친구비

16562 친구비 알고리즘 (union find) 1. unionfind 를 구현하되, 루트에 친구비가 적은 것이 오도록함 2. 루트만 골라 친구비를 정산한다. 코드 #include <iostream> #include <vector> #include <cstring> #include <algorithm&g...

1655 가운데를 말해요

1655 가운데를 말해요 내 방식 (multiset) 1. 멀티셋으로 가운데를 트래킹해가면서 구함 2. 첫번째 원소를 넣고, s.begin()으로 첫번째 iter를 middle로 받고 그대로 출력 3. 두번째부턴 middle과 같은 원소가 들어오면, s.insert(s.upperbound(*middle), input) 으로 가장 앞에 넣음 아...

1647 split town

1647 split town 알고리즘(MST) 1. MST를 만들고, 가장 거리가 긴 길을 빼면 된다. 2. n개의 노드에 대해 n-1개의 간선만 연결되어있으므로, 1개만 빼면 딱 두개로 나눌 수 있는데, 뺀다면 가장 거리가 긴 길이기 때문이다.(2개 이상 빼면 3개이상으로 나눠짐) 코드 #include <iostream> #inc...

1644 소수의 연속합

1644 소수의 연속합 알고리즘(에라토스테네스의 체, 두 포인터) 1. 에라토스테네스의 체로, N이하의 소수를 모두 구함 2. 두 포인터로 일치하는 해당하는 연속합을 구함 초기 : start = 0, end = 1, sum = prime[0] while start < end if sum == N count++ ...

1562 계단수

1562 계단수 알고리즘(DP, 비트마스킹) 1. 숫자를 기록하는 num[101] 배열과, DP[101][10][1 << 10] 배열을 이용하여 DP로 품 2. 앞에서부터 길이에 맞게 숫자를 하나하나 기록해감. 기록하면서 비트마스킹으로 0~9가 있는지 표시 -> 이때 이미 표시된 비트에 또 표시하면 기록이 망가짐. 따라서 &...

1516 Developing game

1516 Developing game 알고리즘(위상정렬, 그래프. DP) 1. 어떤 건물을 짓기 위해 필요한 건물을 짓는 시간의 최대 + 이 건물을 짓는데 걸리는 시간을 출력하면 됨. 2. 예를 들어 A -> -> D C B -> -> E 로 그래프가 이루어져 있을 때, A : A를 짓는 시간 B ...

1509 팰린드롬 분할

1509 팰린드롬 분할 알고리즘(Manacher’s Algorithm, DP) 1. 2차원 bool 형 배열 palindrome에 모든 팰린드롬을 저장. -> palindrome[i][j] 엔 시작이 i이고 끝이 j인 문자열이 팰린드롬인지 아닌지 저장돼있음 -> 구하는 법은 처음부터 i = 0~length, j = i~length; ...

14939 불끄기

14939 불끄기 알고리즘(브루트, 그리디, 비트마스킹) 1. 최소한으로 눌러야하는 경우 : 한 칸은 한번만 눌러야함. 두번 이상누르면 최소가 아니고, 무한 루프가 될 수 잇음 2. 모든 방법을 다 검사해봐야 정답을 얻을 수 있음 -> 브루트 포스 3. 한번씩만 누르면 누르는 순서는 중요하지않음 -> 첫줄부터 차례대로 누름 4. 이때 첫...

14938 서강그라운드

14938 서강그라운드 알고리즘(플로이드워셜) 1. 접근 방법 - 처음엔 DFS 또는 BFS로 접근 - 근데 해보니 이전에 왔던 것보다 적은 거리를 써서온건 보내고, 보낸 다음부터 이어지는 건 겹치면 빼야함. - 결국 플로이드워셜로 최소거리를 모두 구한 후, 탐색범위 안에 들어오는 지역의 아이템을 모두 더하는 방식과 동일함. 코드 #in...

diff --git a/page34/index.html b/page34/index.html new file mode 100644 index 000000000..4cc7aacd7 --- /dev/null +++ b/page34/index.html @@ -0,0 +1 @@ + 디피의 개발일지
Home
디피의 개발일지
Cancel

14500 Samsung sw test

14500 Samsung sw test 알고리즘 1. 가능한 모든 모양으로 검사. 코드 #include <iostream> #define MAX_WIDTH 501 using namespace std; int width, height; int map[MAX_WIDTH][MAX_WIDTH]; void getProperties();...

14499 Samsung sw test

14499 Samsung sw test 알고리즘 1. 각 명령별로 검사. 초기 주사위 상태 (top : 1, north : 2, east : 3, south : 5, western : 4, bottom : 6) 2. 명령대로 주사위를 굴리고 -> 그 위치가 맵 밖이면 다음 명령 실행 맵 안이면 이동시키고, 주사위 상태 변경 ->...

14003 LIS5

14003 LIS5 알고리즘 (LIS) 1. LIS 로 구함 2. 수열 출력 1. LIS구하면서, 현재 LIS의 인덱스를 항상 저장함 2. LIS를 구하면, 가장 뒤에있는 LIS의 가장 뒤 원소의 인덱스가 저장됨 3. 그 인덱스로부터 길이를 하나씩줄여가며 앞으로 가면서 길이가 맞는걸 뒤에서부터 저장 4. 길이가 0이되면 종료 5. 이렇게하...

13549 숨바꼭질 3

13549 숨바꼭질 3 알고리즘(BFS) 1. BFS로 가능한 곳은 가되, check엔 이전에 온 것보다 적은 시간을 걸리며 온 것을 받아주기 위해 시간을 저장하자. 코드 #include <iostream> #include <cstring> #include <list> using namespace std; ...

13458 Samsung sw test

13458 Samsung sw test 알고리즘 1. 오직 1명은 무조건 1명 있어야한다는 말 2. 총감독관이 감시할 수 있는 응시자 수만큼 빼고, 감독관 수 1명 늘린다음에, 넣어야하는 부감독관 수를 더함. -> 이를 모든 시험장에 적용 3. 이때 총 필요한 감독관을 저장하는 변수는 long long으로 해야함(응시자 수 100만명, 시험장...

1339 단어 수학

1339 단어 수학 알고리즘(브루트포스, 그리디) - 브루트포스 1. 어떤 알파벳이 나왔는지 기록하고, 거기에 맞춰 알파벳의 종류의 개수만큼 모든 가능한 수를 뽑아 검사한다. - 그리디 1. 들어오는 단어를 알파벳 단위로 분류하고 다음처럼 자릿수를 곱해준다. ABC -> 100A + 10B + C BCA -> 100B + 10...

12969 ABC

12969 ABC 알고리즘(dp) 1. 현재 깊이 d, 이전에 나왔던 a의 개수, 이전에 나왔던 b의 개수, 이제까지의 k 수를 기준으로 dp를 만들어야함. k도 결과에 영향을 주기 때문. 2. 즉, dp[d][a][b][k] 를 선언하고, 이 상태가 검사되면 true로 지정하여 다시 검사하지않음. 3. 정답을 찾았을 때는 전역변수 got에 tru...

12849 본대산책

12849 본대산책 알고리즘(DP) 1. a라는 건물에 x분을 남기고 들어가는 것은, a에 연결된 모든 건물 b에 x+1분을 남기고 들어온 것과 같음 2. 즉, 정보과학관에 0분을 남기고 들어오는 것을 시작으로 모든 경우를 탐색하면 된다. 3. 쭉쭉 내려가다가 D분에 도달했을때, 장소가 정보과학관이면 1, 아니면 0을 리턴하여 바텀업으로 함 4. ...

1248 맞춰봐

1248 맞춰봐 알고리즘 (백트래킹) 1. 그냥 앞에서부터 가능한 수를 채워나가는 백트래킹 방식을 사용하면 된다. 2. 이때, 가능한 수는 ans를 채워간다는 얘기다. 즉, 가능한 경우만을 탐색하여 가서 정답만을 하나하나 채워가는 식으로 모든 경우를 탐색하면 됨. 코드 #include <bits/stdc++.h> using nam...

12100 Samsung sw test

12100 Samsung sw test 알고리즘(DFS 재귀) 1. 위, 오른쪽, 아래, 왼쪽으로 차례로 보냄. 보낼때, 보내는 쪽에 가까운 곳부터 하나씩 검사. 2. 주의 사항 : 2 4 8 2 8 16 4 2 32 에서 위로 한번 보내면 맨 왼쪽 맨 위는 4가 되야하고 그 밑에 4가 와야함. 하지만 내가 처음 짠 코드에서는 한번 업데이...

diff --git a/page35/index.html b/page35/index.html new file mode 100644 index 000000000..cef002835 --- /dev/null +++ b/page35/index.html @@ -0,0 +1 @@ + 디피의 개발일지
Home
디피의 개발일지
Cancel

1202 Jewelry thief

1202 Jewelry thief 알고리즘 1. 보석을 가치순으로 내림차순으로 정렬 2. 가방을 multiset에 저장함 3. 가치가 큰 보석부터 차례대로 꺼내면서 적절한 가방을 선택하고, 집어넣음 -적절한 가방 : 무게가 같으면 가장 적절. 같지 않으면 남는 무게가 가장 적은 가방 -찾는 법 1. 같은 게 있는지 확인 2. 없으면, 현...

1197 MST

1197 MST 알고리즘(크루스칼 알고리즘 사용) 1. 가장 가중차가 작은 간선부터 검사 2. 사이클 검사 1. 간선의 양단이 전부 이미 안들어갔으면 양단을 같은 벡터에 집어넣고, set 벡터에 집어넣음 2. 한쪽만 들어갔다면, 들어가 있는 한쪽의 set벡터에 안들어간 쪽을 집어넣음 3. 두개 다 들어가 있고, 들어간 집합이 같다면, 사이클 ...

1167 트리의 지름

1167 트리의 지름 알고리즘 (dfs, dp) 1. 1번 노드를 최상위 노드라고 가정하고 품 2. 1번 노드부터 시작하여 트리를 밑으로 탐색함. 3. 끝까지 닿으면, 거리를 올리면서(bottom-top) 다시 위로 올라감(재귀로 탐색하면 됨) 4. 한 노드에서 재귀로 반환하는 건, 자신의 자식 노드에서 올라오는 것중 가장 큰 값(끝까지 갔을 때의...

11657 타임머신

11657 타임머신 알고리즘(밸만포드) 1. 밸만포드로 푼다. 2. 길이는 long long으로 저장한다. 코드 #include <bits/stdc++.h> using namespace std; struct Road { int to; int time; }; struct City { vector<Road> next...

1149 RGB 거리

1149 RGB 거리 알고리즘(dp) 1. 그냥 현재 노드에서 이전의 상태가 X였을때의 최솟값을 저장하는 dp 배열 DP[1000][3] 을 선언하고 DP를 해주면 된다. 2. 첫번째와 마지막이 상관이없으므로 매번 값을 초기화 시켜줄 필요는 없다. 코드 #include <iostream> #define INF 2000000000 u...

11437 LCA

11437 LCA 알고리즘 (BFS, LCA) 1. 트리만들기 1. check 배열을 만들고, 1번 노드만 true 표시함 2. 정점 쌍이 들어왔을 때, 둘중에 check 표시된 것이 A, 안된 것이 B라고 할때, B의 부모엔 A를, 깊이엔 A+1을 넣고, check[B] 에 true 표시함 3. true 표시가 끝나고, B에 인접한 정점들을...

11401 이항계수 3

11401 이항계수 3 알고리즘(페르마의 소정리, 거듭제곱) 1. 페르마의 소정리 적용 nCk = n! / k!(n-k)! % 1000000007 에서 A = n!, B = k!(n-k)!, p = 1000000007 이라고 할 때 위 식은 (A * B^(-1)) % p 를 성립 페르마의 소정리는 p가 소수일때, p와 서로소인 a가 있으면,...

1107 리모컨

1107 리모컨 알고리즘(브루트 포스) 1. 0~100만까지 하나씩 올라가며, 되는지 검사하고, 가장 적게 누르는 건지 저장 2. 되는지 검사할 때는, 고장난 버튼을 bool 배열에 저장하여 바로바로 참조가능하게 하고, 뒤에서부터 하나씩 검사하자 헛발질 기록 1. 처음엔 들어온 버튼의 자릿수에 맞게, 모든 숫자를 검사하되 재귀로 수를 하나씩 ...

11066 파일합치기

11066 파일합치기 알고리즘(DP) 1. 긴 한 줄의 파일들을 두조각으로 분리해서 찾는 방식으로 DP를 함 2. 먼저 0, k-1를 넣음 3. (0, k-1)의 파일 크기의 합을 구한 후, (0,0)-(1,k-1), (0, 1)-(2,k-1) ... 로 모든 두 조각을 만든 후, 그 조각의 비용을 구함 -> start == end 일 경우...

11054 가장 긴 바이토닉 부분수열

11054 가장 긴 바이토닉 부분수열 알고리즘(LIS) 1. 왼쪽에서 오른쪽으로 LIS, 오른쪽에서 왼쪽으로 LIS 후, 모든 점에 대하여 l[i] + r[i] - 1 (길이가 1이면 1일때, 0일경우는 + 1 해줌) 가 최대가 되는 점을 찾고 그점에서의 l[i] + r[i] -1 를 출력하면 됨. 2. 길이가 최대1000이므로 dp로 안해도 ...

diff --git a/page36/index.html b/page36/index.html new file mode 100644 index 000000000..4ac16d308 --- /dev/null +++ b/page36/index.html @@ -0,0 +1 @@ + 디피의 개발일지
Home
디피의 개발일지
Cancel

11049 행렬곱셈순서

11049 행렬곱셈순서 알고리즘(DP) 1. 연속된 원소를 크게 이등분하여 계산할 수 있음 ex) (start ~ i, i+1~end) 2. dp(start, end)는 start에서 end까지의 범위를 계산할 때 최소비용. 3. 현재 비용은, matrixt[start][0]*matrix[i][1]*matrix[end][1] 로 계산할 수 있음. ...

10942 Palindrome

10942 Palindrome 알고리즘(DP) 1. 입력으로 받은 것이 팰린드롬인지 확인 2. 팰린드롬이면 from-to 사이에 있는 모든 건 다 팰린드롬이다. -> DP에 팰린드롬임을 표시 아니면, from-to에서 팰린드롬이 아님을 판별한 곳까지 팰린드롬이 아니다 -> DP에 팰린드롬이 아님을 표시 주의점 아무리 잘 짜도 i...

10830 행렬제곱

10830 행렬제곱 알고리즘 (거듭제곱, 행렬곱) 1. 그냥 행렬 거듭제곱으로 풀면됨 주의점 1. 입력의 B를 long long으로 받아야한다 2. AB = A 가 나오도록 하는 B행렬은 대각선이 모두 1이고 나머지는 0인 행렬이다 코드 #include <iostream> #include <cstring> using...

10775 airport

10775 airport 알고리즘(자료구조, 트리를 이용한 이진탐색) 1. 게이트의 개수에 맞게 1~G를 set에 넣음 2. bool형 check 배열에서, gi에 아무것도 없으면 gi에 넣고 set에서 gi 삭제 3. 이미 gi에 들어가 있으면, set에서 upperbound로 gi초과하는 첫번째를 찾음 -> 만약 그 첫번째가 begin...

1043 거짓말

1043 거짓말 알고리즘(DFS) 1. 처음 진실을 알고있는 사람과 연결돼있는 파티 그룹과, 절대 연결되지 않은 그룹을 나눠야함. 2. 나는 단순히 변화가 발생하지 않을 때까지 반복하였음. 그리고 그런 사람이 없는 그룹의 개수만 세서 출력함. 다른 풀이 1. 내꺼에서 좀더 최적화된 풀이 2. 처음에 입력을 받으면서, 각 사람들이 어디 파티와...

10217 KCM Travel

10217 KCM Travel 알고리즘(다익스트라, DP) 1. 기본적으로 다익스트라를 사용하여 최소경로를 찾아야하는 것을 맞으나, 비용이 넘어버릴경우 이전의 최소경로로 간 것이 의미가 없어진다. 따라서 현재 큐에서 꺼낸 노드가, 이전에 지나간 노드보다 같은 비용일때 더 시간이 많이 걸렸을 경우만 패스. DP[101][10001] 선언 2...

10165 KOI 2014_Elementary

10165 KOI 2014_Elementary 알고리즘 1. 각 버스노선의 출발점, 도착점, 번호를 기록한 구조체를 만들고, 출발점을 기준으로 정렬. 출발점이 같으면 도착점이 작은 것이 앞에 위치하도록함. 2. 버스노선인덱스를 저장하는 배열을 만들어, 버스노선을 하나씩 검사함. -초기 : 첫번째 버스노선을 넣음 3. 검사내용 -출발점이 이전 버...

1007 vector matching

1007 vector matching 알고리즘 1. 각 테스트 케이스 별로 모든 x좌표와 y좌표를 더함 2. n/2개의 점을 고르고, 그 고른 점들의 모든 x좌표와 y좌표를 더함 3. 1에서 구한 x,y 에 2에서 구한 x,y를 2곱하고 뺌. (sumOfEveryX - 2*sumOfChoosedX) 4. 3.에서 구한 벡터의 길이를 구한 후 반환....

1005 ACM Craft

1005 ACM Craft 알고리즘(위상정렬, DP) 1. 1516 게임개발과 비슷한 유형이다 2. 위상정렬하여 푼다. 단, 1516과는 다르게 목표건물을 지었다는게 확인되면 while문을 종료하고 출력해도 됨. 3. 단, 진짜로 위상정렬한 배열을 만들 필요는 없고, 위상정렬식으로 풀어도 시간은 비슷하다. 오히려 위상정렬을 굳이 안만드는 편이 더 ...

diff --git a/page4/index.html b/page4/index.html new file mode 100644 index 000000000..3c1d593b1 --- /dev/null +++ b/page4/index.html @@ -0,0 +1 @@ + 디피의 개발일지
Home
디피의 개발일지
Cancel

성능 최적화를 위한 next.js case study

원글에서는 TMDB의 클라이언트 웹 어플리케이션을 만들고, 여러번의 실험을 거쳐 성능 최적화에 달성하는 과정을 서술하였습니다. 이 글은 기본적으로 원글을 학습하면서 제가 읽기 편한 방식으로 번역/정리한 글입니다. 정확한 내용을 알고 싶으시면 원글을 보시는 걸 추천드립니다. 용어 정의 FCP : First Contentful Paint. ...

Vite

최근 create-react-app 대신에 vite를 공식으로 사용하자는 얘기가 나오고 있다. https://github.com/reactjs/reactjs.org/pull/5487 번역본 여기서 지적한 문제점들은 다음과 같다 SSR/SSG 지원의 부족 빈 페이지 로드 -> 리액트 번들 로드 -> 리...

Headless CMS

Headless CMS 란? 웹사이트를 만들 때는 반드시 컨텐츠(데이터)가 필요하다. Headless CMS는 컨텐츠를 보여줄 수단인 Head와 컨텐츠를 분리한 구조로, 컨텐츠가 Head에 독립적으로 동작할 수 있도록 하는 구조이다. ![1E3qz8MZ8zR7Y3NRghFOvJQ](https://miro.medium.com/v2/resize:fit...

module federation

webpack module federation 여러 개의 개별 빌드가 단일 어플리케이션을 형성할 수 있도록 해주는 webpack의 기능이다. 개별 빌드는 컨테이너처럼 작동하며, 빌드 간에 코드를 노출하고 소비하여 단일 통합 애플리케이션을 생성할 수 있다. Low-Level concepts 로컬 모듈과 원격 모듈을 구별한다. 로컬모듈 : 현...

SWR

SWR은 vercel에서 제작한 React Hooks로, 먼저 캐시(stale)로부터 데이터를 반환한 후, fetch 요청(revalidate)을 하고, 최종적으로 최신화된 데이터를 가져오는 전략이다. 이름은 HTTP 캐시 무효 전략인 stale-while-revalidate에서 유래되었다. 공식사이트 전체적인 기능은 React Query와...

next.js Compiler

Next.js의 컴파일러는 바벨 대신에 Rust 기반의 SWC를 JS 번들링에 사용한다. 이는 바벨보다 17배 빠르며, Next.js 12부터 디폴트로 쓰인다. 만약 바벨을 사용하고 싶다면, 다음을 참고하면 된다. Next.js Compiler의 자세한 내용은 공식 가이드에 나와있다. 이 글에서는 주로 사용할만한 요소만 요약하여 정리하였다. S...

next.js - Output File Tracing

next.js는 빌드 시 자동으로 모든 페이지와 그 의존성을 트랙킹하여 배포시 필요한 파일을 알아낸다. 이렇게 함으로써 배포될 파일의 크기를 줄일 수 있다. 이전에 도커로 배포할 땐, 모든 의존성을 가져온 후 next start를 실행해야했다. 하지만 next.js 12부터는 Output File Tracing을 통해 .next/ 디렉터리만 있으면 ...

next.js - Deployment

Next build API next build를 실행하면, 작성한 소스코드가 최적화된 상태도 빌드된다. 빌드된 결과물 .next 아래에 위치하며 구조는 다음과 같다. .next/static/chunks/pages : 라우트되는 이름 그대로 생성된 js 파일이 위치한다. (/about –> .next/static/chunks/pages/a...

next.js - production 배포 전 체크리스트

next.js로 만든 앱을 배포하기 전 체크리스트 가능한만큼 캐싱을 적용했는가? 데이터베이스와 백엔드가 같은 region에 배포되어있는가? 가능한 최소한의 Javascript를 사용하는 것을 목표로 해라 Javascript 로딩을 가능한 연기하라 logging이 구현되어 있는가? errorHandling이 설정되어있는가? ...

next.js - Script 컴포넌트

next.js 에서는 페이지 성능 개선을 위해 서드파티 script를 가져올 수 있는 next/script 컴포넌트를 제공한다. 사용법 import import Script from 'next/script' Page script 페이지 컴포넌트에서 사용될 수 있으며, 페이지가 브라우저에 로드되면 script를 fetch하고 실행한다. im...

diff --git a/page5/index.html b/page5/index.html new file mode 100644 index 000000000..5c9a1842e --- /dev/null +++ b/page5/index.html @@ -0,0 +1 @@ + 디피의 개발일지
Home
디피의 개발일지
Cancel

next.js - Satic HTML Export

next export 명령어를 통해 next.js 어플리케이션을 static HTML으로 만들 수 있다. 이렇게 만들어진 Static HTML은 Node.js 서버없이 운영이 가능하다. 다음과 같은 기능이 필요없다면, next export를 사용하는 것이 권장된다. Image Optimization (default loader) Intern...

next.js - Automatic Static 최적화

next.js에서는 어떤 페이지가 getServerSideProps 또는 getInitialProps를 가지고 있지 않다면, static 페이지로 결정한다. 이렇게 static 페이지로 결정되면, 빌드시 그 페이지는 static 페이지로 빌드된다. static 페이지는 서버사이드에서 렌더되지 않고 바로 유저에게 전달된다. end-user와 서버 사이...

next/image 컴포넌트

next.js에서는 next/image로 자체적인 이미지 컴포넌트를 제공한다. 일반적인 img 태그와는 달리 여러 성능 최적화기법이 내장되어있다. 기본적으로 다음과 같은 기능을 제공한다. 성능 향상 : 각 디바이스에 정확히 맞는 이미지를 모던 웹 포맷에 맞게 제공한다. Visual Stability : Cumulative Layout Shi...

container 안에서 pm2로 next.js 앱 실행하기

next.js는 기본적으로 node.js runtime에서 작동된다. 하지만 node.js 는 싱글 스레드 기반이고, 따라서 트래픽이 많은 환경에서 multi-core를 사용하여 node.js 앱을 구동하기 위해선 다음과 같은 방법이 있다. worker thread child process cluster 필자는 next.js에서 가장...

next.js - data fetching

next.js에서 사용자의 요청을 받고, 페이지를 만들어낼 때 외부 서버로 필요한 데이터를 요청해야할 때 사용하는 기법이다. data fetching은 기본적으로 페이지 컴포넌트에서 사용할 수 있고 경우에 따라 Custom App 컴포넌트에서도 사용할 수 있다. next 13부터는 app/ 구조를 사용한다면 일반 컴포넌트에서도 가능하다고 하지만, 현...

php 기초 강의

PHP 기초 PHP의 원리 웹브라우저에서 웹 서버로 index.php을 달라고 요청보냄 웹서버는 index.php을 처리할 수 있는 프로그램인 php에게 위임함 php는 index.php를 찾고, 해석해서 html 파일을 만듬. 웹 서버는 html을 받고 웹브라우저로 보내줌. PHP의 데이터 타입 숫자와 산술연산자 ...

쿠버네티스 Ingress

쿠버네티스 Ingress ingress는 일반적으로 외부로부터 서버 내부로 유입되는 네트워크 트래픽을 뜻한다. 쿠버네티스에서는 ingress라는 리소스 오브젝트가 존재한다. 쿠버네티스의 ingress는 외부에서 쿠버네티스 클러스터 내부로 들어오는 http, https 요청을 어떻게 처리할지 정의한다. 즉, 외부에서 쿠버네티스에서 실행 중인 depl...

nginx에서 환경변수 사용하는 방법

nginx.conf 에서 환경변수 사용 nginx.conf에서 환경변수를 사용하여 동적으로 nginx 설정을 변경하는 방법을 알아보자. envsubst 사용하는 방법 envsubst는 인풋으로부터 $VARIABLE 또는 ${VARIABLE}로 되어있는 값을 읽어 환경변수로 바꾸어주는 프로그램이다. envsubst을 활용해서 nginx.templa...

kubernetes 실습

이 포스트는 subicura 님의 kubenetes 안내서을 읽으며 요약/기록한 게시글입니다. Kubenetes 실습 Pod 생성하기 YAML로 설정파일 작성하여 생성하기 apiVersion: v1 kind: Pod metadata: name: echo labels: app: echo spec: containers: -...

kubernetes

이 포스트는 subicura 님의 kubenetes 게시글을 읽으며 요약/기록한 게시글입니다. Kubenetes 쿠버네티스(kubenetes, k8s)는 도커 컨테이너를 쉽고 빠르게 배포/확장하고 관리를 자동화해주는 오픈소스 플랫폼이다. 단순한 컨테이너 플랫폼이 아닌 마이크로 서비스, 클라우드 플랫폼을 지향하고 컨테이너로 이루어진 것들을 손쉽게 담...

diff --git a/page6/index.html b/page6/index.html new file mode 100644 index 000000000..7c22f3511 --- /dev/null +++ b/page6/index.html @@ -0,0 +1 @@ + 디피의 개발일지
Home
디피의 개발일지
Cancel

nginx + spring boot+ react로 구성된 앱 dockerfile 작성

nginx + spring boot+ react로 구성된 앱 dockerfile 작성 docker flow 도커에서 이미지를 빌드하고, 앱이 실행되는 컨테이너를 실행하는 과정은 크게 이미지 빌드, 이미지 푸시, 컨테이너 실행 단계로 나뉜다. nginx + spring boot + react로 구성된 앱을 도커 이미지로 관리하려면 먼저 이미지를 ...

SockJS

SockJS 주로 Spring을 사용할 때, WebSocket Emulation 을 위한 라이브러리이다. WebSocket Emulation 이란 우선 웹 소켓으로 소켓 연결을 시도하고, 실패할 경우 HTTP Streaming, Long-Polling 같은 HTTP 기반의 다른 기술로 전환해 다시 연결을 시도하는 것을 말한다. 따라서 다음과 같은 ...

서블릿 컨테이너

서블릿 컨테이너는 서블릿들의 생성, 실행, 파괴를 담당하며 서블릿을 관리해준다. 서블릿 컨테이너는 클라이언트의 요청을 받고 응답할 수 있게, 웹 서버와 소켓을 만들어 통신한다. 서블릿 컨테이너의 대표적인 무료 서비스로 톰캣이 있다. 톰캣은 웹 서버와 소켓을 만들어 통신하며 JSP와 서블릿이 작동할 수 있는 환경을 제공한다. 서블릿이란? ...

ElasticSearch

Elasticsearch는 시간이 갈수록 증가하는 문제를 처리하는 분산형 RESTful 검색 및 분석 엔진입니다. Elastic Stack의 핵심 제품인 Elasticsearch는 데이터를 중앙에 저장하여 손쉽게 확장되는 광속에 가까운 빠른 검색, 정교하게 조정된 정확도, 강력한 분석을 제공합니다. (ElasticSearch 공식 문구) ...

Apache Kafka

Apache Kafka는 빠르고 확장 가능한 작업을 위해 데이터 피드의 분산 스트리밍, 파이프 라이닝 및 재생을 위한 실시간 스트리밍 데이터를 처리하기 위한 목적으로 설계된 오픈 소스 분산형 게시-구독 메시징 플랫폼이다. Kafka는 서버 클러스터 내에서 데이터 스트림을 레코드로 유지하는 방식으로 작동하는 브로커 기반 솔루션이다. Kafka 서버는...

커스텀 Annotation

Annotation 애노테이션은 Java 5 부터 등장한 기능으로, 사전적의미는 “주석”이지만, 클래스나 메서드 등 타켓에 라벨을 붙여준다. 비즈니스 로직에는 영향을 주지는 않지만, 해당 타켓의 연결 방법이나 소스 코드의 구조를 변경할 수도 있다. 애노테이션은 소스코드에 메타데이터를 삽입하는 것이기 때문에 잘 이용하면 구독성뿐만 아니라 체계적인 소스...

STOMP

STOMP(Simple Text Oriented Messaging Protocol)란? 웹소켓 위에서 동작하는 서브 프로토콜로, 클라이언트와 서버가 서로 통신하는데 있어 메시지의 형식, 유형, 내용 등을 정의해주는 프로토콜이다. 웹 소켓 프로토콜은 Text 또는 binary 두가지 유형의 메시지 타입을 정의하지만 메시지의 내용에 대해서는 정의하지 ...

mybatis.type-aliases-package

mybatis mapper에서 requestType이나 parameterType으로 클래스를 참조할 때 다음과 같이 패키지명을 다 써줘야하는 불편함이 있다. <mapper namespace="com.hello.spring.article.mapper.ArticleMapper"> <select id="selectArticles" ...

좋은 로깅을 위해 알아야할 13가지

이 글은 Logging Best Pratices : The 13 You Should Know를 기반으로 요약, 학습한 글입니다. 좋은 로깅을 위해 알아야할 13가지 Don’t write logs by yourself printf을 사용하거나 파일에 직접 입력하지마라. 로깅을 위한 표준 라이브러리를 사용하라. 표준 라이브러리를 사용할 때 장점...

SpEL - Spring Expression Language

이 문서는 개인적인 목적이나 배포하기 위해서 복사할 수 있다. 출력물이든 디지털 문서든 각 복사본에 어떤 비용도 청구할 수 없고 모든 복사본에는 이 카피라이트 문구가 있어야 한다. (출처) SpEL - Spring Expression Language 객체를 조회하고 조작하는 기능을 제공하며, 메서드 호출, 물자열 템플릿 기능 등의 여러가지 추가...

diff --git a/page7/index.html b/page7/index.html new file mode 100644 index 000000000..c140415df --- /dev/null +++ b/page7/index.html @@ -0,0 +1 @@ + 디피의 개발일지
Home
디피의 개발일지
Cancel

빈 스코프

빈 스코프는 말 그대로 빈이 존재할 수 있는 범위를 뜻한다. 스프링에서는 다음과 같은 스코프를 지원한다 싱글톤 : 기본 스코프. 스프링 컨테이너의 시작과 종료까지 유지되는 가장 넓은 범위의 스코프 프로토타입 : 스프링 컨테이너는 프로토타입 빈의 생성과 의존관계 주입까지만 관여하고 더는 관리하지 않는 매우 짧은 범위의 스코프이다. 웹 관련...

ResponseStatus vs ResponseEntity

​ @ResponseStatus 응답으로 보낼 데이터의 HttpStatus를 명시해주는 방법이다. 컨트롤러에서 Body 데이터만 반환하는 경우 HttpStatus를 명시하기 위해 사용한다. @ResponseStatus(HttpStatus.OK) @GetMapping("/article") public ArticleListDto getArticles...

단일책임원칙(SRP)

좋은 OOP 설계에서 지켜져야하는 규칙인 SOLID 중 S인 규칙으로, 하나의 모듈은 하나의 책임을 가져야한다는 원칙을 말한다. 클래스는 단 한개의 책임을 가져야한다. 하나의 모듈은 오직 하나의 액터에 대해서만 책임져야한다. 클래스를 변경하는 이유는 단 한개여야 한다. 여기서 책임은 액터에 대한 책임을 의미한다. 하나의 액터에 ...

RestTemplate

스프링에서 지원하는 객체로, 간편하게 Rest 방식 API를 호출할 수 있는 스프링 내장 클래스다. 스프링 3.0부터 지원하였고, json, xml 응답을 모두 받을 수 있다. 특징 Blocking I/O 기반의 동기방식을 사용하는 템플릿 RESTful 형식에 맞추어진 템플릿 Header, Content-Type 등을 설정하여 외부 ...

lombok 사용시 주의점

lombok은 @Getter, @Setter 같은 애노테이션 기반으로 Getter/Setter 메서드를 자동으로 생성해주는 편리한 라이브러리이다. 하지만 편리함에 남용하는 애노테이션들이 있다. lombok 사용 시 주의해야할 애노테이션 @AllArgsConstructor 클래스에 존재하는 모든 필드에 대한 생성자를 자동으로 생성한다. @All...

json 응답 시 특정 필드 빼고 보내기

스프링에서는 Http 메시지 컨버터를 통해 사용자가 보낸 데이터와 서버에서 내보내는 데이터를 자동으로 변환해준다. 만약 다음과 같은 객체를 응답으로 보낼 경우 Content-Type이 application/json이라면, 스프링은 다음 객체를 자동으로 Json 형식으로 바꾸어 내보낸다. @AllArgsConstructor @Getter public...

junit - spy

Mock 객체와는 달리 객체의 특정 메서드만 stub으로 대체할 수 있는 방법을 제공한다. 다음과 같이 만약 테스트 대상 객체의 특정한 메서드를 stub 처리하고 싶을 때 사용한다. @ExtendWith(MockitoExtension.class) public class BoardServiceTest{ @Spy private BoardSe...

equals(), hashCode()

equals()와 hashCode() 메서드는 모든 자바 객체의 부모인 Object 클래스에 정의되어있다. 따라서 모든 자바 객체는 equals()와 hashCode() 메서드를 가지고 있다. equals() 현재 객체와 파라미터로 들어온 객체가 같은 지 검사하기 위해 사용한다. 기본적으로는 두 객체의 메모리 주소가 같아야 동일한 객체가 된다. ...

Content-Disposition

HTTP Response Body에 오는 컨텐츠의 기질/성향을 알려주는 HTTP 헤더 속성이다. inline : 디폴트 값. body에 있는 값이 웹 페이지에 표시되어야한다는 뜻. Content-Disposition : inline 최신 브라우저에서 <a> 태그의 download 속성은 Cont...

스프링 캐시

[spring] 스프링 캐시 캐시란 반복적으로 데이터를 불러올 때 지속적으로 DBMS 혹은 서버에 요청하는 것이 아닌 메모리에 데이터를 저장하였다가 데이터를 불러다가 쓰는 것을 말한다. 서버나 DBMS의 부담을 줄여주고, 메모리에 저장되어있기 때문에 많은 시스템에서 사용할수 있다. 캐시는 Long Tail 법칙에 따라 시스템 리소스 사용의 대부분을...

diff --git a/page8/index.html b/page8/index.html new file mode 100644 index 000000000..f23f2738f --- /dev/null +++ b/page8/index.html @@ -0,0 +1 @@ + 디피의 개발일지
Home
디피의 개발일지
Cancel

PathPattern과 servletPath

[spring] PathPattern과 servletPath 문제 다음과 같이 interceptor를 등록할 때, addPathPatterns()에 /api/**를 등록하였지만, /api/article을 요청했을 때 인터셉터가 제대로 동작하지 않았다. 하지만 /**로 등록하니 제대로 동작하였다. public void addInterceptors(...

checked vs unchecked exception

[java] checked vs unchecked exception 자바에서 프로그램에 이상이 있을 때 던져지는 Throwable은 Error와 Exception이 있다. Error : 시스템이 비정상적인 상황에 있다는 것을 의미. 시스템 레벨에서 발생하는 심각한 오류이기 때문에 개발자가 미리 예측하거나 처리할 수 없다. ...

HPA

[devops] HPA HorizontalPodAutoscaler의 약자로, k8s에서 CPU 사용률을 체크하여 Pod의 개수를 스케일링하는 기술이다. 지정한 메트릭을 controller가 체크하여 부하에 따라 필요한 pod의 replica 수가 되도록 자동으로 pod 수를 늘리거나 줄일 수 있는 기술이다. Auto scaling Auto s...

Helm

Helm Kubernetes 패키지 관리를 도와주는 도구. node.js의 npm과 같은 역할을 수행한다. Helm을 사용하면 쿠버네티스 클러스터에서 동작하도록 작성된 패키지들을 관리할 수 있다. 즉, Helm을 사용하면 클러스터에 배포한 애플리케이션을 쉽게 설치, 업데이트, 삭제할 수 있다. 일반적으로 쿠버네티스는 여러 오브젝트로 구성되어있는데...

Canary 테스트

[devops] Canary 테스트 안정적인 버전을 릴리즈하기 전에 테스트 버전을 일부 사용자에게 배포하는 것을 말한다. 만약 카나리 버전에 심각한 버그가 발생한다해도 사용하는 사용자가 적기 때문에 피해를 최소화할 수 있다. 또한 안정적인 버전과 테스트 버전이 모두 배포된 상태이기 때문에 A/B 테스트가 가능하다. 유저가 직접 카나리 버전을 ...

blue-green 배포

[devops] blue-green 배포 애플리케이션의 이전 버전에 있던 사용자 트래픽을 이전 버전(blue)과 거의 동일한 새 버전(green)으로 점진적으로 이전하는 애플리케이션 배포 모델이다. 이때 두 버전 모두 프로덕션 환경에서 실행 상태를 유지한다. blue에서 green으로 완전히 이전되면 blue는 롤백에 대비하여 대기 상태에 두거나 ...

스프링 파일업다운로드

[spring] 파일 업/다운로드 HTML 폼 전송 방식 HTML에서 폼을 전송하는 방식(Content-Type)에는 다음 두가지가 있다. application/x-www-form-urlencoded : 문자와 같은 데이터를 키와 함께 전송하는 방식 ex) username=Kim&age=20 ...

Spring 파라미터 Validation

[spring] 파라미터 validation BindingResult HTTP 메세지 컨버터에서 발생한 데이터 바인딩 오류를 담아 컨트롤러에서 이용할 수 있는 객체. 다음과 같이 바인딩 오류가 발생할 수 있는 파라미터 바로 다음에 파라미터로서 추가한다. @PostMapping("/add") public String addItemV1(@ModelA...

유닛테스트 구현 검증이란?

비즈니스 코드를 먼저 작성하고 유닛테스트를 작성할 때, 완성된 코드를 보고 테스트 코드를 작성하다보면 내부 구현에 맞추어 테스트를 작성하게 되고, 구현 자체를 검증하게 되는 일이 많이 발생하였다. 유닛 테스트에서는 구현이 아닌 행동을 검증(test behaviour, not implementation)해야한다. 따라서 구현을 검증한 테스트 코드를 고...

Keep-Alive

아래와 같이 http 헤더를 보다보면 keep-alive라고 명시된 부분이 보일 때가 있다. connection : keep-alive Keep-Alive:timeout=5, max=1000 이 keep-alive는 무슨 뜻일까 궁금하여 찾아보았다. keep-alive connection : keep-alive connec...

diff --git a/page9/index.html b/page9/index.html new file mode 100644 index 000000000..62cf53345 --- /dev/null +++ b/page9/index.html @@ -0,0 +1 @@ + 디피의 개발일지
Home
디피의 개발일지
Cancel

CORS

CORS : Cross-Origin Resource Sharing(교차 출처 리소스 공유) 서로 다른 출처(Origin)에서 리소스를 공유할 때 적용되는 정책이다. 출처(Origin)이란? URL은 다음과 같이 여러 개의 구성요소로 이루어져있다. 여기서 출처란 protocol + host + port 을 뜻한다. 이 조합이 같아야...

mybatis dynamic field

게시글 검색을 개발하는 도중 검색의 대상이 되는 field(title | nickname | content)를 동적으로 mybatis 쿼리문 상에 사용해야할 일이 있었다. 따라서 다음과 같이 #{} 를 사용하여 변수를 넣었으나 오류가 발생했다. <where> <when test="type != 'hashtag'"> atc....

InvalidDefinitionException

@ResquestBody로 받는 매개변수의 클래스에 @NoArgsConstructor를 빼니 InvalidDefinitionException 에러가 발생했다. @PostMapping("/login") public String login(@Valid @RequestBody LoginRequestBody loginRequestBody) @Getter...

interceptor

스프링 인터셉터는 스프링 MVC가 제공하는 기술로, 서블릿 필터하고는 적용순서, 범위, 사용방법이 다르다. 소개 스프링 인터셉터 흐름 HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 스프링 인터셉터 -> 컨트롤러 스프링 인터셉터는 디스패처 서블릿과 컨트롤러 사이에서 컨트롤러 호출 직전에...

HTTP 메시지 컨버터

스프링에서 컨트롤러를 개발하다보면, url 파라미터를 long 으로 받아도 문제없이 작동된다. @GetMapping(path = "/article/{articleId}") public String getArticleDetail(@PathVariable long articleId) { 위와 같은 컨트롤러가 있을 때, 유저는 /article/123라...

Pure DI - IoC가 없는 DI

이 포스트는 참고문서를 필자가 이해하기 쉽게 정리한 내용입니다. DI는 IoC를 사용하지 않아도 된다 DI(Dependency Injection) 다음 PizzaStore 클래스는 정해진 개수의 Pizza인스턴스를 가지고 있다. 만약 Pizza가 더 필요하면 PizzaStore를 직접 수정해야하다. 이는 OCP를 위반하며 DI로 해결해줄 수 있다...

converter

controller 인자로 enum 받는 문제를 해결했던 것은 enum 타입에 맞춘 Converter를 bean에 등록하는 것이었다. 단순히 bean에 등록하는 것으로 controller에서 httpConverter 동작할 때 이것을 적용한 것이다. 이 뒤에 무엇이 있을까 하여 조사해보았다. 스프링에서는 기본적인 타입 변환은 자동으로 지원한다. 따라...

controller에서 enum을 인자로 받기

검색 API를 개발하는 과정에서 다음과 같이 검색 타입을 enum 으로 지정하였고, controller의 param으로 받으려고 했다. @Getter @AllArgsConstructor public enum SearchType { TITLE("title"), CONTENT("content"), HASHTAG("hashtag"), NICKNAM...

ArgumentResolver 활용

ArgumentResolver 활용 ArgumentResolver는 스프링 MVC 구조의 어댑터 핸들러에서 핸들러에 필요한 파라미터를 만들어주는데 호출하는 부분이다. 이 ArgumentResolver를 직접 구현하여 활용하면 다양한 상황에서 편리하게 적용할 수 있다. 로그인 회원 정보를 편리하게 받아오는 예제 컨트롤러 @GetMapping("/...

Valid vs Validatedated

@Valid JSR-303 표준 객체 제약조건 검증 어노테이션 ArgumentResolver에 의해 처리된다. 검증에 오류가 있다면 MethodArgumentNotValidException 예외가 발생하며 디스패처 서블릿에 기본으로 등록된 예외 리졸버인 DefaultHandlerExceptionResolver에 의해 400 에러가 발생한...

diff --git "a/posts/(CSS)\353\213\244\353\245\270-\354\227\230\353\246\254\353\250\274\355\212\270\354\235\230-\354\235\264\353\262\244\355\212\270-\353\260\234\354\203\235-\354\213\234,-\354\212\244\355\203\200\354\235\274-\354\240\201\354\232\251\353\262\225/index.html" "b/posts/(CSS)\353\213\244\353\245\270-\354\227\230\353\246\254\353\250\274\355\212\270\354\235\230-\354\235\264\353\262\244\355\212\270-\353\260\234\354\203\235-\354\213\234,-\354\212\244\355\203\200\354\235\274-\354\240\201\354\232\251\353\262\225/index.html" new file mode 100644 index 000000000..26e6e0750 --- /dev/null +++ "b/posts/(CSS)\353\213\244\353\245\270-\354\227\230\353\246\254\353\250\274\355\212\270\354\235\230-\354\235\264\353\262\244\355\212\270-\353\260\234\354\203\235-\354\213\234,-\354\212\244\355\203\200\354\235\274-\354\240\201\354\232\251\353\262\225/index.html" @@ -0,0 +1,37 @@ + (CSS)다른 엘리먼트의 이벤트 발생 시, 스타일 적용법 | 디피의 개발일지
Posts (CSS)다른 엘리먼트의 이벤트 발생 시, 스타일 적용법
Post
Cancel

(CSS)다른 엘리먼트의 이벤트 발생 시, 스타일 적용법

yourlist_web_renewal 중, 한 input 엘리먼트의 placeholder가 보일시와 안보일 시, label 엘리먼트에 각기 다른 스타일을 적용해야하는 일이 있었다.

현재 사용중인 tailwind에선 어떻게 사용해야할지 모르겠으나 순수 CSS 방식으로 하면 다음과 같다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+
.form_field:placeholder-shown ~ .form__label {
+  font-size: 1rem;
+  top: 0;
+  cursor: text;
+}
+.form_field:focus ~ .form__label {
+  top: -1.2rem;
+  font-size: 1rem;
+  font-weight: 700;
+  color: var(--primary-redrose-light);
+}
+.form_field:not(:focus) ~ .form__label {
+  color: var(--blackberry-lightest);
+}
+.form_field:not(:placeholder-shown) ~ .form__label {
+  top: -1.2rem;
+  font-size: 1rem;
+}
+

위와 같이, [이벤트 발생] [선택자] [대상] 로 한 엘리먼트의 이벤트 발생 시 선택자로 적용할 엘리먼트를 선택하면 된다.

This post is licensed under CC BY 4.0 by the author.

flex, grid로 남은 곳 꽉채우기

(Typescript) React typescript hack

Comments powered by Disqus.

diff --git a/posts/10021-watering-the-field/index.html b/posts/10021-watering-the-field/index.html new file mode 100644 index 000000000..ea735066d --- /dev/null +++ b/posts/10021-watering-the-field/index.html @@ -0,0 +1,195 @@ + 10021 watering the field | 디피의 개발일지
Posts 10021 watering the field
Post
Cancel

10021 watering the field

알고리즘

  • 크루스칼
  • 모든 필드 사이의 거리를 계산하고, 양 쪽 필드의 번호와 거리를 벡터에 저장한다.
  • 거리순으로 정렬하고, 크루스칼 알고리즘 시행
  • 이때, 단순히 union-find 알고리즘으로 하면 find에서 시간초과가 난다.
  • 따라서 set을 활용해서 크루스칼을 하면 된다.

헤맨 지점

  1. set을 이용해서 할때, 서로 다른 집합이었다가 합쳐질때, 하나만 옮기면 안된다. 그 set에 속해있는 모든 점을 다 옮겨야함
  2. 꼭 첫번째 집합으로 합쳐지지 않는다. 따라서, 모든 점들이 같은 집합에 있는지 하나하나 확인해봐야한다.

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+
#include <iostream>
+#include <vector>
+#include <algorithm>
+#include <cstring>
+#include <set>
+using namespace std;
+
+struct node {
+	int first;
+	int second;
+	int length;
+
+	bool operator< (node a) {
+		return length < a.length;
+	}
+};
+
+vector<pair<int, int>> field;
+vector<node> lengths;
+vector<set<int>> unionset;
+int N, C, parent[2000];
+long long result;
+
+void calcLengths();
+bool kruskal();
+int power(int a) {
+	return a * a;
+}
+
+int main() {
+	cin >> N >> C;
+	int a, b;
+	while (N--) {
+		cin >> a >> b;
+		field.push_back({ a, b });
+	}
+	N = field.size();
+
+	calcLengths();
+	memset(parent, -1, sizeof(parent));
+	sort(lengths.begin(), lengths.end());
+
+	if (kruskal())
+		cout << result;
+	else
+		cout << "-1";
+}
+
+void calcLengths() {
+	for (int i = 0; i < N; i++)
+		for (int j = i + 1; j < N; j++)
+			lengths.push_back({ i, j, power((field[i].first - field[j].first)) + power((field[i].second - field[j].second)) });
+}
+
+bool kruskal() {
+	for (int i = 0; i < lengths.size(); i++) {
+		int f = lengths[i].first, s = lengths[i].second, l = lengths[i].length;
+		int sp = parent[s], fp = parent[f];
+		if (l < C)
+			continue;
+
+		if (fp == -1 && sp == -1) {
+			set<int> t;
+			t.insert(f);
+			t.insert(s);
+			unionset.push_back(t);
+			parent[f] = unionset.size()-1;
+			parent[s] = parent[f];
+			result += l;
+		}
+		else if (fp == -1) {
+			unionset[sp].insert(f);
+			parent[f] = sp;
+			result += l;
+		}
+		else if (sp == -1) {
+			unionset[fp].insert(s);
+			parent[s] = fp;
+			result += l;
+		}
+		else if(fp != sp) {
+			for (auto iter = unionset[sp].begin(); iter != unionset[sp].end(); iter++) {
+				parent[*iter] = fp;
+				unionset[fp].insert(*iter);
+			}
+				result += l;
+			unionset[sp].clear();
+		}
+	}
+	int root = parent[0];
+	for (int i = 0; i < N; i++) {
+		if (parent[i] != root)
+			return false;
+	}
+
+	return true;
+}
+
This post is licensed under CC BY 4.0 by the author.

5213 과외맨

18500 미네랄 2

Comments powered by Disqus.

diff --git a/posts/1005/index.html b/posts/1005/index.html new file mode 100644 index 000000000..01c63851f --- /dev/null +++ b/posts/1005/index.html @@ -0,0 +1,137 @@ + 1005 ACM Craft | 디피의 개발일지
Posts 1005 ACM Craft
Post
Cancel

1005 ACM Craft

1005 ACM Craft

알고리즘(위상정렬, DP)

1
+2
+3
+4
+
1. 1516 게임개발과 비슷한 유형이다
+2. 위상정렬하여 푼다. 단, 1516과는 다르게 목표건물을 지었다는게 확인되면 while문을 종료하고 출력해도 됨.
+3. 단, 진짜로 위상정렬한 배열을 만들 필요는 없고, 위상정렬식으로 풀어도 시간은 비슷하다. 오히려 위상정렬을 굳이 안만드는 편이 더 빠를 수있다.
+4. 더 빠르게 하는 방법은, 목표건물이 속한 그래프만을 검사하는 것.
+

기타

1
+
1.  DP : 왜 알고리즘 분류에 DP가 들어갔나 했더만, 이미 검사한 거는 check표시하여 검사하지않고, 최댓값을 다음 노드로 계속 넘겨주는 형태가 DP인거 같다.
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+
#include <iostream>
+#include <cstring>
+#include <vector>
+#define MAX_CRAFT 1000
+
+using namespace std;
+
+typedef struct Craft {
+	int time;
+	int timeTaken;
+	int before;
+	bool isChecked;
+	vector<int> next;
+} Craft;
+
+Craft craft[MAX_CRAFT];
+vector<int> answer;
+
+int main() {
+	int numOfTc;
+	cin >> numOfTc;
+	for (int i = 0; i < numOfTc; i++) {
+		memset(craft, 0, sizeof(craft));
+
+		//입력
+		int numOfCraft, numOfOrder, targetCraft;
+		cin >> numOfCraft >> numOfOrder;
+		for (int j = 0; j < numOfCraft; j++)
+			cin >> craft[j].time;
+		for (int j = 0; j < numOfOrder; j++) {
+			int temp, temp2;
+			cin >> temp >> temp2;
+			temp--;
+			temp2--;
+			craft[temp].next.push_back(temp2);
+			craft[temp2].before++;
+		}
+		cin >> targetCraft;
+		targetCraft--;
+
+		//계산
+		bool check = true;
+		while (check) {
+			for (int j = 0; j < numOfCraft; j++) {
+				if (!craft[j].isChecked && craft[j].before == 0) {
+					int tempTime = craft[j].time + craft[j].timeTaken;
+					for (int k = 0; k < craft[j].next.size(); k++) {
+						if (craft[craft[j].next[k]].timeTaken < tempTime)
+							craft[craft[j].next[k]].timeTaken = tempTime;
+						craft[craft[j].next[k]].before--;
+					}
+					craft[j].isChecked = true;
+					if (j == targetCraft)
+						check = false;
+				}
+			}
+		}
+
+		answer.push_back(craft[targetCraft].time + craft[targetCraft].timeTaken);
+	}
+	for (int i = 0; i < answer.size(); i++)
+		cout << answer[i] << endl;
+}
+
This post is licensed under CC BY 4.0 by the author.

-

1007 vector matching

Comments powered by Disqus.

diff --git a/posts/1007/index.html b/posts/1007/index.html new file mode 100644 index 000000000..001352a20 --- /dev/null +++ b/posts/1007/index.html @@ -0,0 +1,173 @@ + 1007 vector matching | 디피의 개발일지
Posts 1007 vector matching
Post
Cancel

1007 vector matching

1007 vector matching

알고리즘

1
+2
+3
+4
+
1. 각 테스트 케이스 별로 모든 x좌표와 y좌표를 더함
+2. n/2개의 점을 고르고, 그 고른 점들의 모든 x좌표와 y좌표를 더함
+3. 1에서 구한 x,y 에 2에서 구한 x,y를 2곱하고 뺌. (sumOfEveryX - 2*sumOfChoosedX)
+4. 3.에서 구한 벡터의 길이를 구한 후 반환. 반환 받은 곳은 최솟값을 다시 반환
+

설명

1
+2
+
들어오는 점들이 짝수이고, 두개씩 짝 지어준다면 벡터를 구했을 때 반은 더하는 형태이고, 반은 빼는 형태이다.
+따라서 빼줄 반을 고르고, 전체 X의 합에서 빼면 됨. 근데 전체 X의 합이므로 빼줄 반은 두배로 곱하고 빼야한다.
+

삽질과정

1
+2
+3
+4
+5
+
처음에 모든 쌍을 지어주고, 모든 방향을 검사하여 길이를 구해도 시간안에 될 줄 알았음
+-> 하지만 계산해보니 미친듯이 시간이 많이 걸림
+-> 다른 방법으로 빠르게 전환했어야했다
+
+그리고 문제를 푸는데 머리로만 푸는 버릇이 들었는데 이걸 고치고 이런 수학적인 문제는 적으면서 풀어보자.
+

교훈

1
+2
+
1. 생각난 알고리즘이 있으면 한번 복잡도 계산해보자
+2. 수학적인 문제의 경우 식을 적으면서 풀어보자.
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+
#include <iostream>
+#include <cstdio>
+#include <cmath>
+#include <vector>
+#include <cstring>
+
+using namespace std;
+
+typedef struct Point {
+	int x;
+	int y;
+} Point;
+
+vector<vector<Point>> tc;
+vector<Point> currentPoint;
+int sumOfEveryX, sumOfEveryY;
+
+void generateTc();
+long double solveSingleTC(vector<int> choosed, int deep);
+
+int main() {
+	int numOfTc;
+	cin >> numOfTc;
+	for (int i = 0; i < numOfTc; i++) {
+		int numOfPoint;
+		cin >> numOfPoint;
+		vector<Point> tempPoint;
+		for (int j = 0; j < numOfPoint; j++) {
+			int x, y;
+			cin >> x >> y;
+			tempPoint.push_back({ x, y });
+		}
+		tc.push_back(tempPoint);
+	}
+	generateTc();
+}
+
+void generateTc() {
+	for (int i = 0; i < tc.size(); i++) {
+		currentPoint = tc[i];
+		sumOfEveryX = 0;
+		sumOfEveryY = 0;
+		for (int j = 0; j < currentPoint.size(); j++) {
+			sumOfEveryX += currentPoint[j].x;
+			sumOfEveryY += currentPoint[j].y;
+		}
+		vector<int> choosed;
+		cout << "test" << endl;
+		cout.precision(50);
+		cout << solveSingleTC(choosed, 0) << endl;
+	}
+}
+
+long double solveSingleTC(vector<int> choosed, int deep) {
+	if (choosed.size() == currentPoint.size() / 2) {
+		long long sumOfChoosedX = 0;
+		long long sumOfChoosedY = 0;
+		for (int i = 0; i < choosed.size(); i++) {
+			sumOfChoosedX += currentPoint[choosed[i]].x;
+			sumOfChoosedY += currentPoint[choosed[i]].y;
+		}
+		long long length = (sumOfEveryX - 2 * sumOfChoosedX) * (sumOfEveryX - 2 * sumOfChoosedX) +
+							(sumOfEveryY - 2 * sumOfChoosedY) * (sumOfEveryY - 2 * sumOfChoosedY);
+		return sqrt(length);
+	}
+	else if (currentPoint.size() - deep < (currentPoint.size() / 2) - choosed.size())
+		return 2000000000;
+
+	long double a = solveSingleTC(choosed, deep + 1);
+	choosed.push_back(deep);
+	long double b = solveSingleTC(choosed, deep + 1);
+	return a < b ? a : b;
+}
+
This post is licensed under CC BY 4.0 by the author.

1005 ACM Craft

10165 KOI 2014_Elementary

Comments powered by Disqus.

diff --git a/posts/10165/index.html b/posts/10165/index.html new file mode 100644 index 000000000..ee9aa1879 --- /dev/null +++ b/posts/10165/index.html @@ -0,0 +1,341 @@ + 10165 KOI 2014_Elementary | 디피의 개발일지
Posts 10165 KOI 2014_Elementary
Post
Cancel

10165 KOI 2014_Elementary

10165 KOI 2014_Elementary

알고리즘

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+
1. 각 버스노선의 출발점, 도착점, 번호를 기록한 구조체를 만들고, 출발점을 기준으로 정렬. 출발점이 같으면 도착점이 작은 것이 앞에 위치하도록함.
+2. 버스노선인덱스를 저장하는 배열을 만들어, 버스노선을 하나씩 검사함.
+	-초기 : 첫번째 버스노선을 넣음
+3. 검사내용
+	-출발점이 이전 버스노선과 같은가
+		-이전 버스노선이 0을 지나치고, 현재 버스노선도 0을 지나치는가	(정렬로 인해 자동으로 현재버스노선이 이전버스노선을 감싸는 형태가 됨.)
+			-현재버스 노선을 넣었을 때 지워지는 노선을 전부 지움
+			-이전버스노선도 지워졌으므로, 이전 버스노선인덱스 위치에 현재 버스노선의 인덱스를 넣음
+		-이전 버스노선이 0을 지나치지만, 현재 버스노선은 0을 지나치지 앟음
+			-현재버스노선 스킵
+		-이전 버스노선이 0을 지나치지 않음
+			-이전 버스노선을 지우고, 현재 버스노선을 저장
+	-출발점이 이전 버스노선과 다른가
+		-현재 버스노선이 0을 지나치는가
+			-이전 버스노선이 0을 지나치고, 현재 버스노선을 감싸는가
+				-스킵
+			-그 외
+				-현재버스 노선을 넣었을 때 지워지는 노선을 전부 지움
+				-이전 버스노선이 지워졌으면 이전 위치에 넣고, 아니면 이어서 넣자
+		-현재 버스노선이 0을 지나치치 않는가
+			-이전 버스노선이 0을 지나치지 않고, 현재 버스노선을 감싸지 않을 때
+				-현재 버스노선을 이어서 넣음
+			-그 외 스킵
+4. 검사 끝나고, 버스노선 인덱스들로부터 남은 버스노선의 번호들을 추출하고, 번호들을 정렬하여 출력.
+

다른 풀이법

1
+2
+3
+
1. 0번 정류장을 안지나는 그룹을 A, 지나는 그룹을 B라고 하자.
+2. A그룹 먼저 내 방식대로 정렬 후 겹치는 거 있는지 확인 후 집어넣어 기록
+3. B그룹을 적절하게 정렬 후 검사 -> A그룹을 포함하는지, 같은 B그룹 내 다른 노선을 포함하는지 확인하여 기록.
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+
//busLine.java
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.util.Arrays;
+import java.util.LinkedList;
+
+public class BusLine {
+	private int numOfBusStop;
+	private BusLineInfo[] busLines;
+
+	private class BusLineInfo implements Comparable<BusLineInfo> {
+		int num;
+		int start;
+		int end;
+
+		public BusLineInfo(int num, int start, int end) {
+			this.num = num;
+			this.start = start;
+			this.end = end;
+		}
+		@Override
+		public int compareTo(BusLineInfo info) {
+			// TODO Auto-generated method stub
+			int temp = this.start - info.start;
+			return temp != 0 ? temp : (this.end - info.end);
+		}
+	}
+
+	public BusLine() {
+		getProperties();
+	}
+
+	private void getProperties() {
+		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
+		try {
+			this.numOfBusStop = Integer.parseInt(br.readLine());
+		} catch(Exception e) {
+			e.printStackTrace();
+		}
+
+		int numOfBusLine = 0;
+		try {
+			numOfBusLine = Integer.parseInt(br.readLine());
+		} catch(Exception e) {
+			e.printStackTrace();
+		}
+
+		this.busLines = new BusLineInfo[numOfBusLine];
+		try {
+			for(int i = 0; i<numOfBusLine; i++) {
+				String[] str = br.readLine().split(" ");
+				this.busLines[i] = new BusLineInfo(i+1, Integer.parseInt(str[0]), Integer.parseInt(str[1]));
+			}
+		} catch(Exception e) {
+			e.printStackTrace();
+		}
+	}
+
+	public int[] deleteDuplicateLine() {
+		LinkedList<Integer> lineAfterDelete = new LinkedList<Integer>();
+
+		Arrays.sort(this.busLines);
+
+		lineAfterDelete.add(0);
+		for(int i = 1; i<this.busLines.length; i++) {
+			int beforeIndex = lineAfterDelete.getLast();
+			boolean isBeforeExceed0 = this.busLines[beforeIndex].end < this.busLines[beforeIndex].start;
+			boolean isCurrentExceed0 = this.busLines[i].end < this.busLines[i].start;
+
+			if(this.busLines[i].start == this.busLines[beforeIndex].start) {
+				if(isBeforeExceed0 && !isCurrentExceed0)
+					continue;
+				else if(isBeforeExceed0 && isCurrentExceed0) {
+					lineAfterDelete = deleteAnyCan(lineAfterDelete, i);
+				}
+				lineAfterDelete.removeLast();
+				lineAfterDelete.add(i);
+			}
+			else {
+				if(isCurrentExceed0) {	//출발지점이 더 나중인 게 0을 넘을 경우
+					//이전게 0을 넘고, 현재 end보다 이전 end이 더 크면 현재게 잡아먹힘. continue
+					if(isBeforeExceed0 && this.busLines[beforeIndex].end >= this.busLines[i].end)
+						continue;
+
+					//위같은 경우가 아니면, 잡아 먹지 않음. 현재가 이전에 먹을 게 있는지 확인하고 먹은 건 -1로 표시
+					lineAfterDelete = deleteAnyCan(lineAfterDelete, i);
+
+					//바로 이전에 -1로 표시되었다면 이전위치에 현재 위치를 집어넣고, 아니면 현재위치에 넣고 1증가
+					lineAfterDelete.add(i);
+				}
+				else {		//안넘을 경우
+					//이전에 넘으면 무조건 잡아먹힘
+					//넘지않으면 이전 end가 현재 end보다 클때 잡아먹음.
+					//아니면 현재걸 기록.
+					if(!isBeforeExceed0 && this.busLines[i].end > this.busLines[beforeIndex].end)
+						lineAfterDelete.add(i);
+				}
+			}
+		}
+
+		int[] numsLine = new int[lineAfterDelete.size()];
+		for(int i = 0; i<numsLine.length; i++)
+			numsLine[i] = lineAfterDelete.get(i);
+
+		Arrays.sort(numsLine);
+
+		return numsLine;
+	}
+
+	private LinkedList<Integer> deleteAnyCan(LinkedList<Integer> lineAfterDelete, int current) {
+		int currentEnd = this.busLines[current].end;
+		LinkedList<Integer> temp = new LinkedList<Integer>();
+
+		for(int i = 0; i<lineAfterDelete.size(); i++) {		//현재의 end가 어떤 것의 start와 end보다 크면 -1로 바꿈. 이때 어떤 것은 0을 넘지 말아야한다.
+			int currentIndex = lineAfterDelete.get(i);
+			if(currentEnd >= this.busLines[currentIndex].end && this.busLines[currentIndex].end > this.busLines[currentIndex].start) {
+				;
+			}
+			else
+				temp.add(lineAfterDelete.get(i));
+		}
+
+		return temp;
+	}
+
+}
+
+//Main.java
+
+public class Main {
+
+	public static void main(String[] args) {
+		// TODO Auto-generated method stub
+		BusLine test = new BusLine();
+		int[] temp = test.deleteDuplicateLine();
+		for(int i : temp)
+			System.out.print(i + " ");
+	}
+
+}
+
+
+
This post is licensed under CC BY 4.0 by the author.

1007 vector matching

10217 KCM Travel

Comments powered by Disqus.

diff --git a/posts/10217/index.html b/posts/10217/index.html new file mode 100644 index 000000000..e3f1a1a0a --- /dev/null +++ b/posts/10217/index.html @@ -0,0 +1,163 @@ + 10217 KCM Travel | 디피의 개발일지
Posts 10217 KCM Travel
Post
Cancel

10217 KCM Travel

10217 KCM Travel

알고리즘(다익스트라, DP)

1
+2
+3
+4
+5
+6
+
1. 기본적으로 다익스트라를 사용하여 최소경로를 찾아야하는 것을 맞으나, 비용이 넘어버릴경우 이전의 최소경로로 간 것이 의미가 없어진다.
+   따라서 현재 큐에서 꺼낸 노드가, 이전에 지나간 노드보다 같은 비용일때 더 시간이 많이 걸렸을 경우만 패스.
+  DP[101][10001] 선언
+2. 첫번째를 제외하고, 모든 노드를 INF 로 초기화
+3. 다익스트라를 진행하면서, 현재 큐에서 꺼낸 노드의 시간이 DP[현재노드][현재노드의 비용] 보다 클 때 패스 (같은 거는 현재 일 수 있음)
+4. 인접한 노드를 업데이트할때, (다음노드로 가는비용 ~ 최대비용) 까지 이미 저장된 값보다 작을 때 DP[다음노드][비용] 을 다음 시간으로 업데이트
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+
#include <bits/stdc++.h>
+#define INF 2000000000
+
+using namespace std;
+
+struct Line {
+	int to;
+	int m;
+	int time;
+};
+
+vector<Line> airport[101];
+int N, M, K, DP[101][10001];
+queue<Line> q;
+
+void dijkstra();
+void init() {
+	for (int i = 1; i <= N; i++) {
+		airport[i].clear();
+		for(int j = 1; j<=M; j++)
+			DP[i][j] = INF;
+ 	}
+}
+
+int main() {
+	ios::sync_with_stdio(0);
+	cin.tie(0);
+	int tc;
+	cin >> tc;
+	for (int i = 0; i < tc; i++) {
+		cin >> N >> M >> K;
+		init();
+
+		for (int j = 0; j < K; j++) {
+			int a, b, c, d;
+			cin >> a >> b >> c >> d;
+			airport[a].push_back({ b, c, d });
+		}
+
+		dijkstra();
+
+		int answer = INF;
+		for (int j = 1; j <= M; j++)
+			answer = min(answer, DP[N][M]);
+
+		if (answer == INF)
+			cout << "Poor KCM" << "\n";
+		else
+			cout << answer << "\n";
+	}
+}
+
+void dijkstra() {
+	q.push({1, 0, 0});
+
+	while (!q.empty()) {
+		Line a = q.front();
+		q.pop();
+
+		if (a.time > DP[a.to][a.m])
+			continue;
+
+		for (int j = 0; j < airport[a.to].size(); j++) {
+			Line next = airport[a.to][j];
+			int nm = a.m + next.m, nt = a.time + next.time, to = next.to;
+
+			if (nm <= M && nt < DP[to][nm]) {
+				q.push({ to, nm, nt });
+				for (int k = nm; k <= M; k++)
+					if(nt < DP[to][k])
+						DP[to][k] = nt;
+			}
+		}
+	}
+}
+
This post is licensed under CC BY 4.0 by the author.

10165 KOI 2014_Elementary

1043 거짓말

Comments powered by Disqus.

diff --git "a/posts/1039-\352\265\220\355\231\230/index.html" "b/posts/1039-\352\265\220\355\231\230/index.html" new file mode 100644 index 000000000..7fd8a71bb --- /dev/null +++ "b/posts/1039-\352\265\220\355\231\230/index.html" @@ -0,0 +1,157 @@ + 1039 교환 | 디피의 개발일지
Posts 1039 교환
Post
Cancel

1039 교환

알고리즘

  • DP 사용.
  • bottom-up 방식으로 끝까지 간 후, 하나씩 올리면서 가장 큰 걸 반환
  • 0<= i < j <= M 일때, array[i] == 0 이고, j == M이면 바꾸지 않음.
  • 10 이하는 무조건 -1, 나머지는 0이 반환되면 한번도 끝까지 도달하지 못한 것이므로 K번 바꾸기 연산이 불가능한 것. 즉, -1 출력

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+
#include <iostream>
+#include <cstring>
+#include <algorithm>
+using namespace std;
+
+int N, K, length, inputArray[7];
+int dp[1000001][11];
+
+void nIntoArray(int n, int* a);
+int arrayIntoN(int* a);
+int dynamic(int deep);
+void swap(int* a, int* b);
+
+int main() {
+	cin >> N >> K;
+
+	int index = 7;
+	for (int i = 1000000; i >= 1; i /= 10) {
+		if (N >= i) {
+			length = index;
+			break;
+		}
+		index--;
+	}
+
+	nIntoArray(N, inputArray);
+	if (N < 10)
+		cout << -1;
+	else {
+		int ans = dynamic(0);
+		if (ans == 0)
+			cout << -1;
+		else
+			cout << ans;
+	}
+}
+
+void nIntoArray(int n, int* a) {
+	for (int i = 0; i < length; i++) {
+		a[i] = n % 10;
+		n /= 10;
+	}
+}
+int dynamic(int deep) {
+	int num = arrayIntoN(inputArray);
+	if (deep == K)
+		return num;
+	if (dp[num][deep] != 0)
+		return dp[num][deep];
+
+	int ans = 0;
+	for (int i = 0; i < length-1; i++) {
+		for (int j = i+1; j < length; j++) {
+			if (inputArray[i] != 0 || j != length - 1) {
+				swap(&inputArray[i], &inputArray[j]);
+				ans = max(dynamic(deep + 1), ans);
+				swap(&inputArray[j], &inputArray[i]);
+			}
+		}
+	}
+	dp[num][deep] = ans;
+	return ans;
+}
+
+void swap(int* a, int* b) {
+	int temp = *a;
+	*a = *b;
+	*b = temp;
+}
+
+int arrayIntoN(int* a) {
+	int result = 0, multi = 1;
+	for (int i = 0; i < length; i++) {
+		result += a[i] * multi;
+		multi *= 10;
+	}
+	return result;
+}
+
This post is licensed under CC BY 4.0 by the author.

Junctions/Seoul 2021 해커톤

3108 logo

Comments powered by Disqus.

diff --git a/posts/1043/index.html b/posts/1043/index.html new file mode 100644 index 000000000..357355ba3 --- /dev/null +++ b/posts/1043/index.html @@ -0,0 +1,177 @@ + 1043 거짓말 | 디피의 개발일지
Posts 1043 거짓말
Post
Cancel

1043 거짓말

1043 거짓말

알고리즘(DFS)

1
+2
+3
+
1. 처음 진실을 알고있는 사람과 연결돼있는 파티 그룹과, 절대 연결되지 않은 그룹을 나눠야함.
+
+2. 나는 단순히 변화가 발생하지 않을 때까지 반복하였음. 그리고 그런 사람이 없는 그룹의 개수만 세서 출력함.
+

다른 풀이

1
+2
+3
+4
+5
+6
+
1. 내꺼에서 좀더 최적화된 풀이
+2. 처음에 입력을 받으면서, 각 사람들이 어디 파티와 연결되어있는지 벡터에 저장.
+3. 처음부터 진실을 알고 있는 사람들이 있는 그룹을 큐에 넣고, 거짓말을 할 수 있는 그룹인지 체크하는 배열에 표시
+4. 큐에 넣어진 그룹들을 하나씩 빼가며, 그 그룹에 있는 사람들이 속한 그룹도 큐에 추가.
+	이미 검사한 사람이거나, 검사한 그룹이면 패스
+5. 그렇게해서 탐색이 끝나면, 표시안된 그룹의 개수를 세서 출력
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+
#include <iostream>
+
+using namespace std;
+
+int N, M, party[51][51];
+bool knowTruth[51], alreadyCheck[51];
+
+int getAns();
+void makeKnow();
+
+int main() {
+	cin >> N >> M;
+	int t;
+	cin >> t;
+	while(t--) {
+		int a;
+		cin >> a;
+		knowTruth[a] = true;
+	}
+
+	for (int i = 0; i < M; i++) {
+		int n;
+		cin >> n;
+		for (int j = 0; j < n; j++)
+			cin >> party[i][j];
+	}
+	makeKnow();
+	cout << getAns();
+}
+
+void makeKnow() {
+	bool change = true;
+	while (change) {
+		change = false;
+
+		for (int i = 0; i < M; i++) {
+			if (alreadyCheck[i])
+				continue;
+
+			int index = 0;
+			bool check = false;
+			while (party[i][index] != 0) {
+				if (knowTruth[party[i][index]]) {
+					check = true;
+					break;
+				}
+				index++;
+			}
+
+			if (check) {
+				change = true;
+				alreadyCheck[i] = true;
+				index = 0;
+				while (party[i][index] != 0) {
+					knowTruth[party[i][index]] = true;
+					index++;
+				}
+			}
+		}
+	}
+}
+
+int getAns() {
+	int result = 0;
+	for (int i = 0; i < M; i++) {
+		int index = 0;
+		bool check = true;
+		while (party[i][index] != 0) {
+			if (knowTruth[party[i][index]]) {
+				check = false;
+				break;
+			}
+			index++;
+		}
+		if (check)
+			result++;
+	}
+	return result;
+}
+
This post is licensed under CC BY 4.0 by the author.

10217 KCM Travel

10775 airport

Comments powered by Disqus.

diff --git a/posts/10775/index.html b/posts/10775/index.html new file mode 100644 index 000000000..1101b19ca --- /dev/null +++ b/posts/10775/index.html @@ -0,0 +1,131 @@ + 10775 airport | 디피의 개발일지
Posts 10775 airport
Post
Cancel

10775 airport

10775 airport

알고리즘(자료구조, 트리를 이용한 이진탐색)

1
+2
+3
+4
+5
+
1. 게이트의 개수에 맞게 1~G를 set에 넣음
+2. bool형 check 배열에서, gi에 아무것도 없으면 gi에 넣고 set에서 gi 삭제
+3. 이미 gi에 들어가 있으면, set에서 upperbound로 gi초과하는 첫번째를 찾음
+		-> 만약 그 첫번째가 begin()이면 없는 것 -> false 반환 후 입력 종료
+		-> begin()이 아니면 그 곳에서 한칸 아래에 도킹하고, true반환
+

주의점

1
+
1. set의 erase는 iterator를 받는다.
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+
#include <iostream>
+#include <set>
+#define MAX 100001
+
+using namespace std;
+
+int numOfGate, numOfPlane, planeCount;
+bool gate[MAX];
+set<int> s;
+
+//gateIndex에 넣을 자리에 넣기
+//넣을 곳은 최대한 큰 곳에 넣는게 좋다.
+//따라서 5가 들어오면, gateIndex에서 5를 조회해서, 가리키는 위치에 넣기
+//가리키는 위치는 처음엔 자기자신, 그 후엔 넣어질때마다 1칸씩 앞으로 당기기.
+//대신 그 앞에 것도 적절히 조절
+//ex) 5 5 5 계속 들어오면, gateIndex는 1 2 3 4 5 6 -> 1 2 3 4 4 6-> 1 2 3 3 3 6-> 1 2 2 2 2 6-> 1 1 1 1 1 6-> 0  0 0 0 0 6-> 0자리엔 못넣게 함.
+//1씩 줄여주는과정에서 시간초과가 뜸.
+bool canPut(int max);
+void makeSet();
+
+int main() {
+	cin >> numOfGate >> numOfPlane;
+	makeSet();
+	int temp;
+	bool check = true;
+	for (int i = 0; i < numOfPlane; i++) {
+		cin >> temp;
+		if (check && canPut(temp))
+			planeCount++;
+		else
+			check = false;
+	}
+	cout << planeCount << endl;
+
+}
+
+void makeSet() {
+	for (int i = 1; i <= numOfGate; i++)
+		s.insert(i);
+}
+
+bool canPut(int max) {
+	if (!gate[max]) {
+		gate[max] = true;
+		s.erase(s.find(max));
+		return true;
+	}
+	else {
+		set<int>::iterator iter = s.upper_bound(max);
+		if (iter == s.begin())
+			return false;
+		else {
+			iter--;
+			gate[*iter] = true;
+			s.erase(iter);
+			return true;
+		}
+	}
+}
+
This post is licensed under CC BY 4.0 by the author.

1043 거짓말

10830 행렬제곱

Comments powered by Disqus.

diff --git a/posts/10830/index.html b/posts/10830/index.html new file mode 100644 index 000000000..ba0636b5f --- /dev/null +++ b/posts/10830/index.html @@ -0,0 +1,129 @@ + 10830 행렬제곱 | 디피의 개발일지
Posts 10830 행렬제곱
Post
Cancel

10830 행렬제곱

10830 행렬제곱

알고리즘 (거듭제곱, 행렬곱)

1
+
1. 그냥 행렬 거듭제곱으로 풀면됨
+

주의점

1
+2
+
1. 입력의 B를 long long으로 받아야한다
+2. AB = A 가 나오도록 하는 B행렬은 대각선이 모두 1이고 나머지는 0인 행렬이다
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+
#include <iostream>
+#include <cstring>
+using namespace std;
+
+long long N, B;
+long long map[5][5], result[5][5] = { {1,0,0,0,0}, {0,1,0,0,0}, {0,0,1,0,0}, {0,0,0,1,0}, {0,0,0,0,1} };
+
+void multiMap(bool isResult);
+void powerMap();
+
+int main() {
+	cin >> N >> B;
+	for (int i = 0; i < N; i++)
+		for (int j = 0; j < N; j++)
+			cin >> map[i][j];
+
+	powerMap();
+	for (int i = 0; i < N; i++) {
+		for (int j = 0; j < N; j++)
+			cout << result[i][j] << " ";
+		cout << endl;
+	}
+}
+
+void powerMap() {
+	while (B >= 1) {
+		if (B % 2 == 1) {
+			multiMap(true);
+			if (B == 1)
+				break;
+		}
+		multiMap(false);
+		B /= 2;
+	}
+}
+
+void multiMap(bool isResult) {
+	long long temp[5][5];
+	memset(temp, 0, sizeof(temp));
+
+	if (isResult) {
+		for (int i = 0; i < N; i++) {
+			for (int j = 0; j < N; j++) {
+				for (int k = 0; k < N; k++)
+					temp[i][j] = ((temp[i][j] % 1000)+ ((result[i][k] * map[k][j]) % 1000));
+				temp[i][j] %= 1000;
+			}
+		}
+		memcpy(result, temp, sizeof(temp));
+	}
+	else {
+		for (int i = 0; i < N; i++) {
+			for (int j = 0; j < N; j++) {
+				for (int k = 0; k < N; k++)
+					temp[i][j] = ((temp[i][j] % 1000) + ((map[i][k] * map[k][j]) % 1000));
+				temp[i][j] %= 1000;
+			}
+		}
+		memcpy(map, temp, sizeof(temp));
+	}
+}
+
This post is licensed under CC BY 4.0 by the author.

10775 airport

10942 Palindrome

Comments powered by Disqus.

diff --git "a/posts/10875-\353\261\200/index.html" "b/posts/10875-\353\261\200/index.html" new file mode 100644 index 000000000..2ec62c1ab --- /dev/null +++ "b/posts/10875-\353\261\200/index.html" @@ -0,0 +1,277 @@ + 10875 뱀 | 디피의 개발일지
Posts 10875 뱀
Post
Cancel

10875 뱀

알고리즘

  • 뱀이 성장하면서 생기는 가로직선, 세로직선을 따로 저장.
  • 새롭게 생기는 직선이 이전에 저장된 직선에 걸리는지 확인.
  • 이때, 직선은 정렬하여 뱀이 나아가는 방향에서 가장 가까운 직선에 걸리도록 하자.

여담

  • 깔끔하게 구현하기에 매우 애를 먹었고, 가능하지도 않았다. 그만큼 분리하여 생각해야하는 조건들이 많고, 그렇기에 실수를 할 가능성이 매우 크다.
  • 알고리즘 자체를 간단하므로, 틀렸습니다가 뜬다면 실수를 하지 않았는지를 중점으로 찾아보면 될 것 같다.

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+
#include <iostream>
+#include <set>
+
+typedef long long LL;
+
+using namespace std;
+
+set<pair<LL, pair<LL, LL>>> v, h;
+LL l, n, ans, curDir = 1, cr, cc;
+
+bool add(int, int);
+bool check(LL, LL);
+
+int main() {
+	cin >> l >> n;
+
+	pair<LL, pair<LL, LL>> t[3] = { { l + 1, {-l, l} },{ -l - 1, {-l, l} }, { 0,{0,0} } };
+	for (int i = 0; i < 3; i++) {
+		h.insert(t[i]);
+		v.insert(t[i]);
+	}
+
+	int a;
+	char b;
+	bool check = false;
+	while (n--) {
+		cin >> a >> b;
+		if (!add(a, b)) {
+			check = true;
+			break;
+		}
+	}
+
+	if(!check)
+		add(2000000010, 'L');
+
+	cout << ans;
+	return 0;
+}
+
+bool add(int t, int dir) {
+	LL nr = curDir == 0 ? cr + t : cr - t, nc = curDir == 1 ? cc + t : cc - t;
+	if (curDir % 2 == 0) {
+		cr += curDir == 0 ? 1 : -1;
+		if (!check(nr, cc))
+			return false;
+
+		curDir == 0 ? v.insert({ cc, {cr, nr} }) : v.insert({ cc, {nr, cr} });
+		cr = nr;
+	}
+	else {
+		cc += curDir == 1 ? 1 : -1;
+		if (!check(cr, nc))
+			return false;
+
+		curDir == 1 ? h.insert({ cr, {cc, nc} }) : h.insert({ cr, {nc, cc} });
+		cc = nc;
+	}
+	ans += t;
+
+	if ((curDir == 1 && dir == 'L') || (curDir == 3 && dir =='R'))
+		curDir = 0;
+	else if ((curDir == 2 && dir == 'L') || (curDir == 0 && dir == 'R'))
+		curDir = 1;
+	else if ((curDir == 3 && dir == 'L') || (curDir == 1 && dir == 'R'))
+		curDir = 2;
+	else if ((curDir == 0 && dir == 'L') || (curDir == 2 && dir == 'R'))
+		curDir = 3;
+	return true;
+}
+
+bool check(LL nr, LL nc) {
+	if (curDir % 2 == 0) {
+		LL higher = curDir == 0 ? nr : cr, lower = curDir == 0 ? cr : nr;
+		if (curDir == 0) {
+			for (auto iter = h.begin(); iter != h.end(); iter++) {
+				if (lower <= (*iter).first && higher >= (*iter).first && (*iter).second.first <= cc && (*iter).second.second >= cc) {
+					ans += (*iter).first - lower + 1;
+					return false;
+				}
+			}
+		}
+		else {
+			auto iter = h.end();
+			iter--;
+			for (; iter != h.begin(); iter--) {
+				if (lower <= (*iter).first && higher >= (*iter).first && (*iter).second.first <= cc && (*iter).second.second >= cc) {
+					ans += higher - (*iter).first + 1;
+					return false;
+				}
+			}
+			if (lower <= (*iter).first && higher >= (*iter).first && (*iter).second.first <= cc && (*iter).second.second >= cc) {
+				ans += higher - (*iter).first + 1;
+				return false;
+			}
+		}
+		for (auto iter = v.begin(); iter != v.end(); iter++) {
+			int vc = (*iter).first, vlh = (*iter).second.first, vhh = (*iter).second.second;
+
+			if (cc == vc && ((higher >= vlh && higher <= vhh) || (lower >= vlh && lower <= vhh) || (higher > vhh && lower < vlh))) {
+				ans += curDir == 0 ? vlh - lower + 1 : higher - vhh + 1;
+				return false;
+			}
+		}
+	}
+	else {
+		LL higher = curDir == 1 ? nc : cc, lower = curDir == 1 ? cc : nc;
+		if (curDir == 1) {
+			for (auto iter = v.begin(); iter != v.end(); iter++)
+				if (lower <= (*iter).first && higher >= (*iter).first && (*iter).second.first <= cr && (*iter).second.second >= cr) {
+					ans += (*iter).first - lower + 1;
+					return false;
+				}
+		}
+		else {
+			auto iter = v.end();
+			iter--;
+			for (; iter != v.begin(); iter--)
+				if (lower <= (*iter).first && higher >= (*iter).first && (*iter).second.first <= cr && (*iter).second.second >= cr) {
+					ans += higher - (*iter).first + 1;
+					return false;
+				}
+			if (lower <= (*iter).first && higher >= (*iter).first && (*iter).second.first <= cc && (*iter).second.second >= cc) {
+				ans += higher - (*iter).first + 1;
+				return false;
+			}
+		}
+		for (auto iter = h.begin(); iter != h.end(); iter++) {
+			int hh = (*iter).first, hlc = (*iter).second.first, hhc = (*iter).second.second;
+
+			if (cr == hh && ((higher >= hlc && higher <= hhc) || (lower >= hlc && lower <= hhc) || (higher > hhc && lower < hlc))) {
+				ans += curDir == 1 ? hlc - lower + 1: higher - hhc + 1;
+				return false;
+			}
+		}
+	}
+	return true;
+}
+
This post is licensed under CC BY 4.0 by the author.

9874 Wormholes

브라우저 동작원리

Comments powered by Disqus.

diff --git a/posts/10942/index.html b/posts/10942/index.html new file mode 100644 index 000000000..15b56e196 --- /dev/null +++ b/posts/10942/index.html @@ -0,0 +1,167 @@ + 10942 Palindrome | 디피의 개발일지
Posts 10942 Palindrome
Post
Cancel

10942 Palindrome

10942 Palindrome

알고리즘(DP)

1
+2
+3
+
1. 입력으로 받은 것이 팰린드롬인지 확인
+2. 팰린드롬이면 from-to 사이에 있는 모든 건 다 팰린드롬이다. -> DP에 팰린드롬임을 표시
+  아니면, from-to에서 팰린드롬이 아님을 판별한 곳까지 팰린드롬이 아니다 -> DP에 팰린드롬이 아님을 표시
+

주의점

1
+2
+3
+4
+
아무리 잘 짜도
+ios::sync_with_stdio(false);
+cin.tie(0);
+를 써줘야 통과가 됨.
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+
#include <iostream>
+#include <cstring>
+#define MAX_LENGTH 2001
+
+using namespace std;
+
+int n[MAX_LENGTH], numOfN, DP[MAX_LENGTH][MAX_LENGTH];
+
+bool isPalindrome(int from, int to);
+
+int main() {
+	ios::sync_with_stdio(false);
+	cin.tie(0);
+	cin >> numOfN;
+	for (int i = 1; i <= numOfN; i++) {
+		cin >> n[i];
+	}
+	int numOfQ;
+	cin >> numOfQ;
+	for (int i = 0; i < numOfQ; i++) {
+		int from, to;
+		cin >> from >> to;
+		if (isPalindrome(from, to))
+			cout << 1 << '\n';
+		else
+			cout << 0 << '\n';
+	}
+}
+
+bool isPalindrome(int from, int to) {
+	if (DP[from][to] == 1)
+		return true;
+	else if (DP[from][to] == 2)
+		return false;
+	else {
+		int fromEnd = (from + to) / 2, toEnd, tempFromEnd, temptoEnd;
+		if ((from + to) % 2 == 0)
+			toEnd = (from + to) / 2;
+		else
+			toEnd = fromEnd + 1;
+
+		bool check = true;
+		for (int i = from, j = to; i <= fromEnd && j >= toEnd; i++, j--) {
+			if (DP[i][j] == 2) {
+				check = false;
+				tempFromEnd = i;
+				temptoEnd = j;
+				break;
+			}
+			if (DP[i][j] == 1)
+				break;
+			if (n[i] != n[j]) {
+				check = false;
+				tempFromEnd = i;
+				temptoEnd = j;
+				break;
+			}
+		}
+
+		if (check) {
+			for (int i = from, j = to; i <= fromEnd && j >= toEnd; i++, j--) {
+				if (DP[i][j] != 1)
+					DP[i][j] = 1;
+				else
+					break;
+			}
+			return true;
+		}
+		else {
+			for (int i = from, j = to; i <= tempFromEnd && j >= temptoEnd; i++, j--) {
+				DP[i][j] = 2;
+			}
+			return false;
+		}
+	}
+}
+
This post is licensed under CC BY 4.0 by the author.

10830 행렬제곱

11049 행렬곱셈순서

Comments powered by Disqus.

diff --git "a/posts/10986-\353\202\230\353\250\270\354\247\200\355\225\251/index.html" "b/posts/10986-\353\202\230\353\250\270\354\247\200\355\225\251/index.html" new file mode 100644 index 000000000..2ae32325e --- /dev/null +++ "b/posts/10986-\353\202\230\353\250\270\354\247\200\355\225\251/index.html" @@ -0,0 +1,63 @@ + 10986 나머지합 | 디피의 개발일지
Posts 10986 나머지합
Post
Cancel

10986 나머지합

알고리즘

  • 구간합 정의 : i ~ j 까지의 구간합 = (1~ j 까지의 구간합) - (1 ~ i -1 까지의 구간합)

  • 위 정의에 따라 우리가 구해야하는 (i ~ j 까지의 구간합) % m = 0 은

    ((1~ j 까지의 구간합) % m - (1 ~ i -1 까지의 구간합) % m) % m 과 같음

  • 따라서 (1~ j 까지의 구간합) % m = (1 ~ i -1 까지의 구간합) % m 인 (i, j) 가 조건을 만족함.

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+
#include <iostream>
+
+typedef long long ll;
+using namespace std;
+
+int n, m, num[1000100], save[1100];
+ll sum[1000100];
+
+int main() {
+	ios::sync_with_stdio(0);
+	cin.tie(0);
+
+	cin >> n >> m;
+	cin >> num[0];
+	sum[0] = num[0];
+	for (int i = 1; i < n; i++) {
+		cin >> num[i];
+		sum[i] = sum[i - 1] + num[i];
+	}
+
+	save[0] = 1;
+	for (int i = 0; i < n; i++)
+		save[sum[i] % m]++;
+
+	ll cnt = 0;
+	for (int i = 0; i < m; i++) {
+		ll x = save[i] -1;
+		cnt += (x * (x + 1) / 2);
+	}
+	cout << cnt;
+}
+
This post is licensed under CC BY 4.0 by the author.

12906 새로운 하노이탑

2629 양팔저울

Comments powered by Disqus.

diff --git "a/posts/11048-\354\235\264\353\217\231\355\225\230\352\270\260/index.html" "b/posts/11048-\354\235\264\353\217\231\355\225\230\352\270\260/index.html" new file mode 100644 index 000000000..30f7009e3 --- /dev/null +++ "b/posts/11048-\354\235\264\353\217\231\355\225\230\352\270\260/index.html" @@ -0,0 +1,51 @@ + 11048 이동하기 | 디피의 개발일지
Posts 11048 이동하기
Post
Cancel

11048 이동하기

알고리즘

  • (1,1)에서부터 순서대로 모든 점을 대상으로 visit (i -1, j), (i, j-1), (i-1,j-1) 이 가지고 있는 점 중 가장 큰 점을 visit(i,j) 에 저장한다.

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+
#include <iostream>
+
+using namespace std;
+
+int visit[1010][1010], n, m, map[1010][1010];
+
+int max(int a, int b) {
+	return a > b ? a : b;
+}
+
+int main() {
+	ios::sync_with_stdio(false);
+	cin.tie(NULL);
+	cin >> n >> m;
+	for (int i = 1; i <= n; i++)
+		for (int j = 1; j <= m; j++) {
+			cin >> map[i][j];
+		}
+
+	for (int i = 1; i <= n; i++)
+		for (int j = 1; j <= m; j++)
+			visit[i][j] = map[i][j] + max(max(visit[i - 1][j], visit[i][j - 1]), visit[i - 1][j - 1]);
+
+	cout << visit[n][m];
+}
+
This post is licensed under CC BY 4.0 by the author.

14529 Where's Bessie

1890 점프

Comments powered by Disqus.

diff --git a/posts/11049/index.html b/posts/11049/index.html new file mode 100644 index 000000000..f3289b75a --- /dev/null +++ b/posts/11049/index.html @@ -0,0 +1,79 @@ + 11049 행렬곱셈순서 | 디피의 개발일지
Posts 11049 행렬곱셈순서
Post
Cancel

11049 행렬곱셈순서

11049 행렬곱셈순서

알고리즘(DP)

1
+2
+3
+4
+
1. 연속된 원소를 크게 이등분하여 계산할 수 있음 ex) (start ~ i, i+1~end)
+2. dp(start, end)는 start에서 end까지의 범위를 계산할 때 최소비용.
+3. 현재 비용은, matrixt[start][0]*matrix[i][1]*matrix[end][1] 로 계산할 수 있음.
+4. start == end 일때 비용은 0
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+
#include <iostream>
+#define MAX_MATRIX 501
+
+using namespace std;
+
+int numOfMatrix, matrix[MAX_MATRIX][2], DP[MAX_MATRIX][MAX_MATRIX];
+
+int getMinCalulate(int start, int end);
+long long min(long long a, int b) {
+	return a < b ? a : b;
+}
+
+int main() {
+	cin >> numOfMatrix;
+
+	for (int i = 0; i < numOfMatrix; i++)
+		cin >> matrix[i][0] >> matrix[i][1];
+
+	cout << getMinCalulate(0, numOfMatrix - 1);
+}
+
+int getMinCalulate(int start, int end) {
+	if (start == end)
+		return 0;
+	if (DP[start][end] != 0)
+		return DP[start][end];
+
+	long long result = 3000000000;
+	for (int i = start; i < end; i++) {
+		result = min(result, getMinCalulate(start, i) + getMinCalulate(i + 1, end) + (matrix[start][0]*matrix[i][1]*matrix[end][1]));
+
+	}
+
+	return DP[start][end] = result;
+}
+
This post is licensed under CC BY 4.0 by the author.

10942 Palindrome

11054 가장 긴 바이토닉 부분수열

Comments powered by Disqus.

diff --git a/posts/11054/index.html b/posts/11054/index.html new file mode 100644 index 000000000..98fb683dd --- /dev/null +++ b/posts/11054/index.html @@ -0,0 +1,175 @@ + 11054 가장 긴 바이토닉 부분수열 | 디피의 개발일지
Posts 11054 가장 긴 바이토닉 부분수열
Post
Cancel

11054 가장 긴 바이토닉 부분수열

11054 가장 긴 바이토닉 부분수열

알고리즘(LIS)

1
+2
+3
+4
+
1. 왼쪽에서 오른쪽으로 LIS, 오른쪽에서 왼쪽으로 LIS 후, 모든 점에 대하여 l[i] + r[i] - 1 (길이가 1이면 1일때, 0일경우는 + 1 해줌) 가 최대가 되는 점을 찾고
+ 그점에서의 l[i] + r[i] -1 를 출력하면 됨.
+
+2. 길이가 최대1000이므로 dp로 안해도 되는데 해도 무방
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+
#include <iostream>
+#include <algorithm>
+#include <cstring>
+
+using namespace std;
+
+int N, num[1001], l[1001], r[1001], t[1001], ind;
+
+int generateLIS();
+void left();
+void right();
+
+int main() {
+	cin >> N;
+	for (int i = 0; i < N; i++)
+		cin >> num[i];
+
+	cout << generateLIS() << endl;
+}
+
+int generateLIS() {
+	left();
+	memset(t, 0, sizeof(t));
+	right();
+
+	for (int i = 0; i < N; i++) {
+		cout << l[i] << " ";
+	}
+	cout << endl;
+	for (int i = 0; i < N; i++) {
+		cout << r[i] << " ";
+	}
+	cout << endl;
+
+	int result = 0;
+	for (int i = 0; i < N; i++) {
+		int c = l[i] + r[i] + 1;
+		result = result > c ? result : c;
+	}
+	return result;
+}
+
+void left() {
+	l[0] = 0;
+	t[0] = num[0];
+	ind = 1;
+	for (int i = 1; i < N; i++) {
+		int* p = lower_bound(&t[0], &t[ind], num[i]);
+
+		if (*p == num[i])
+			l[i] = p - t;
+		else {
+			l[i] = p - t;
+			*p = num[i];
+			if (p - t == ind)
+				ind++;
+		}
+	}
+	for (int i = 0; i < N; i++)
+		cout << t[i] << " ";
+	cout << endl;
+}
+
+void right() {
+	r[N - 1] = 0;
+	t[0] = num[N - 1];
+	ind = 1;
+	for (int i = N - 2; i >= 0; i--) {
+		int* p = lower_bound(&t[0], &t[ind], num[i]);
+
+		if (*p == num[i])
+			r[i] = p - t;
+		else {
+			r[i] = p - t;
+			*p = num[i];
+			if (p - t == ind)
+				ind++;
+		}
+	}
+	for (int i = 0; i < N; i++)
+		cout << t[i] << " ";
+	cout << endl;
+}
+
This post is licensed under CC BY 4.0 by the author.

11049 행렬곱셈순서

11066 파일합치기

Comments powered by Disqus.

diff --git a/posts/11066/index.html b/posts/11066/index.html new file mode 100644 index 000000000..d6abe2e19 --- /dev/null +++ b/posts/11066/index.html @@ -0,0 +1,111 @@ + 11066 파일합치기 | 디피의 개발일지
Posts 11066 파일합치기
Post
Cancel

11066 파일합치기

11066 파일합치기

알고리즘(DP)

1
+2
+3
+4
+5
+6
+
1. 긴 한 줄의 파일들을 두조각으로 분리해서 찾는 방식으로 DP를 함
+2. 먼저 0, k-1를 넣음
+3. (0, k-1)의 파일 크기의 합을 구한 후, (0,0)-(1,k-1), (0, 1)-(2,k-1) ... 로 모든 두 조각을 만든 후, 그 조각의 비용을 구함
+ -> start == end 일 경우 합치지 않으므로 비용은 0
+ ex) (0,1) 일 경우, 0 + 0 + file[0] + file[1]
+4. 각 조각의 비용은 dp[start][end]에 저장함.
+

주의점

1
+
이전 tc의 결과가 dp를 오염시킬 수 있으므로, dp배열은 항상 초기화한다.
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+
#include <iostream>
+#include <cstring>
+#define MAX_FILE 501
+
+using namespace std;
+
+int k, file[MAX_FILE], DP[MAX_FILE][MAX_FILE], fileSum[MAX_FILE][MAX_FILE], output;
+
+int dp(int start, int end);
+int min(int a, int b) {
+	return a < b ? a : b;
+}
+
+int main() {
+	int tc;
+	cin >> tc;
+	for (int i = 0; i < tc; i++) {
+		cin >> k;
+		for (int j = 0; j < k; j++) {
+			cin >> file[j];
+		}
+		cout << dp(0, k - 1) << endl;
+		memset(DP, 0, sizeof(DP));
+		memset(fileSum, 0, sizeof(DP));
+	}
+}
+
+int dp(int start, int end) {
+	if (start == end)
+		return 0;
+	if (DP[start][end] != 0)
+		return DP[start][end];
+
+	int cur = fileSum[start][end];
+	if (cur == 0) {
+		for (int i = start; i <= end; i++)
+			cur += file[i];
+		fileSum[start][end] = cur;
+	}
+
+	int result = 2000000000;
+	for (int i = start; i < end; i++) {
+		result = min(result, dp(start, i) + dp(i + 1, end));
+	}
+
+	//cout << start << "\t" << end << "\t" << result << "\t" << cur <<endl;
+	return DP[start][end] = result + cur;
+}
+
This post is licensed under CC BY 4.0 by the author.

11054 가장 긴 바이토닉 부분수열

1107 리모컨

Comments powered by Disqus.

diff --git a/posts/1107/index.html b/posts/1107/index.html new file mode 100644 index 000000000..05c47f35a --- /dev/null +++ b/posts/1107/index.html @@ -0,0 +1,133 @@ + 1107 리모컨 | 디피의 개발일지
Posts 1107 리모컨
Post
Cancel

1107 리모컨

1107 리모컨

알고리즘(브루트 포스)

1
+2
+
1. 0~100만까지 하나씩 올라가며, 되는지 검사하고, 가장 적게 누르는 건지 저장
+2. 되는지 검사할 때는, 고장난 버튼을 bool 배열에 저장하여 바로바로 참조가능하게 하고, 뒤에서부터 하나씩 검사하자
+

헛발질 기록

1
+2
+3
+
1. 처음엔 들어온 버튼의 자릿수에 맞게, 모든 숫자를 검사하되 재귀로 수를 하나씩 채워하는 식으로 하여 안되는 건 미리 제거하는 식으로 함
+	-> ex) 2528이면 모든 4자리수를 검사하되, 앞에서부터 안되는건 배제하며 채워감
+2. 그러나 자릿수를 더 크게 해도 최소가 될 경우가 존재. 따라서 자릿수를 꽉채워서 모든 수를 검사하였으나 설계에 어려움을 느낌
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+
#include <iostream>
+#include <cstring>
+#include <cmath>
+
+using namespace std;
+
+int to;
+bool isable[10];
+
+int pushButton();
+int check(int cur);
+int min(int a, int b) {
+	return a < b ? a : b;
+}
+
+int main() {
+	cin >> to;
+
+	int n, t;
+	cin >> n;
+	memset(isable, 1, sizeof(isable));
+	for (int i = 0; i < n; i++) {
+		cin >> t;
+		isable[t] = false;
+	}
+
+	cout << min(abs(100 - to), pushButton());
+}
+
+
+int pushButton() {
+	int result = 2000000000, end = to * 10;
+
+	for (int i = 0; i <= end; i++) {
+		int length = check(i);
+		if(length != -1)
+			result = min(result, abs(to - i) + length);
+	}
+
+	return result;
+}
+
+int check(int cur) {
+	int t = 10, index = 0, end = cur * 10;
+
+	if (end == 0)
+		end = 10;
+
+	while (t <= end) {
+		int c = cur % t;
+		c = c / (t / 10);
+
+		if (!isable[c])
+			return -1;
+
+		t *= 10;
+		index++;
+	}
+
+	return index;
+}
+
This post is licensed under CC BY 4.0 by the author.

11066 파일합치기

11401 이항계수 3

Comments powered by Disqus.

diff --git a/posts/11401/index.html b/posts/11401/index.html new file mode 100644 index 000000000..376b28cf6 --- /dev/null +++ b/posts/11401/index.html @@ -0,0 +1,131 @@ + 11401 이항계수 3 | 디피의 개발일지
Posts 11401 이항계수 3
Post
Cancel

11401 이항계수 3

11401 이항계수 3

알고리즘(페르마의 소정리, 거듭제곱)

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+
1. 페르마의 소정리 적용
+	nCk = n! / k!(n-k)! % 1000000007 에서 A = n!, B = k!(n-k)!, p = 1000000007 이라고 할 때
+
+	위 식은 (A * B^(-1)) % p 를 성립
+	페르마의 소정리는 p가 소수일때, p와 서로소인 a가 있으면, a^p mod p = a mod p 가 성립. -> a^p-1 mod p = 1 mod p 이므로 a^p-1 mod p = 1 이 성립함을 알려주는 공식
+	여기서 양변에 a를 한번 더 나누면
+	a^p-2 mod p = a^(-1) 이 성립함을 알 수 있음
+	따라서 위 이항 계수 식은 다음처럼 변형된다.
+	(A * B^(-1)) % p = (A * B^(p-2)) % p
+2. A = n!과 B = k!(n-k)! 를 구한 후, 거듭제곱 공식으로 B^(p-2)를 구하면 됨.
+
+
+ A = n*...*(n-k+1), B = k! 로 두고 풀어도 됨
+-> 처음에 틀렸던 이유는 (k!)^(p-2)가 아니라 (k)^(p-2)로 풀었기 때문
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+
#include <iostream>
+#define DIVIDE 1000000007
+
+using namespace std;
+
+long long getCoefficient(int n, int k);
+long long getPower(long long n, long long k);
+
+int main() {
+	int n, k;
+	cin >> n >> k;
+	cout << getCoefficient(n, k) << endl;
+
+}
+
+long long getCoefficient(int n, int k) {
+	if (k == 0 || k == n)
+		return 1;
+
+	long long top = 1, bottom = 1, bottom2 = 1;
+
+	for (int i = n; i >= 1; i--)
+		top = (top * i) % DIVIDE;
+	top %= DIVIDE;
+	for (int i = k; i >= 1; i--)
+		bottom = (bottom * i) % DIVIDE;
+	bottom %= DIVIDE;
+	for (int i = n - k; i >= 1; i--)
+		bottom2 = (bottom2 * i) % DIVIDE;
+	bottom2 %= DIVIDE;
+
+
+	bottom = (bottom * bottom2) % DIVIDE;
+	top %= DIVIDE;
+
+	return (top * (getPower(bottom, DIVIDE - 2))) % DIVIDE;
+}
+
+long long getPower(long long n, long long k) {
+	long long result = 1, temp = n;
+
+	while (k > 0) {
+		if (k % 2 == 1) {
+			result = (result * temp) % DIVIDE;
+		}
+		temp = (temp * temp) % DIVIDE;
+		k /= 2;
+	}
+
+	return result % DIVIDE;
+}
+
This post is licensed under CC BY 4.0 by the author.

1107 리모컨

11437 LCA

Comments powered by Disqus.

diff --git a/posts/11437/index.html b/posts/11437/index.html new file mode 100644 index 000000000..71f49d884 --- /dev/null +++ b/posts/11437/index.html @@ -0,0 +1,201 @@ + 11437 LCA | 디피의 개발일지
Posts 11437 LCA
Post
Cancel

11437 LCA

11437 LCA

알고리즘 (BFS, LCA)

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
1. 트리만들기
+	1. check 배열을 만들고, 1번 노드만 true 표시함
+	2. 정점 쌍이 들어왔을 때, 둘중에 check 표시된 것이 A, 안된 것이 B라고 할때, B의 부모엔 A를, 깊이엔 A+1을 넣고, check[B] 에 true 표시함
+	3. true 표시가 끝나고, B에 인접한 정점들을 BFS로 끝까지 방문하면서 깊이를 부여하고 check 표시함.
+	4. 만약 둘 다 check에 표시가 안돼있으면, 두 정점의 인접한 정점 벡터에 둘다 추가한다.
+2. LCA 찾기
+
+	1. log2의 부모를 찾는 방법으로 풀면 아주 빠르게 가능. 하지만, 이 방법은 어렵다
+	2. 간단하게 두 정점 중 깊이가 더 깊은 것을 부모를 찾아가면서 덜 깊은 것과 깊이를 똑같이 맞춤
+	3. 깊이가 같아지면, 정점이 같아질때까지 둘다 부모를 찾아감
+		-> 1초대.
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+
#include <iostream>
+#include <vector>
+#include <algorithm>
+#define MAX_NODE 50001
+
+using namespace std;
+
+typedef struct Node {
+	int depth;
+	int parent;
+	vector<int> closeNode;
+} Node;
+Node node[MAX_NODE];
+int numOfNode, list[MAX_NODE];
+bool check[MAX_NODE];
+
+int findLCS(int first, int second);
+void makeTree(int root);
+
+int main() {
+	ios_base::sync_with_stdio(false);
+	cin.tie(NULL);
+	cout.tie(NULL);
+
+	cin >> numOfNode;
+	check[1] = true;
+	int temp1, temp2, numOfPair;
+
+	for (int i = 0; i < numOfNode-1; i++) {
+		cin >> temp1 >> temp2;
+		if (check[temp1]) {
+			node[temp2].depth = node[temp1].depth + 1;
+			node[temp2].parent = temp1;
+			check[temp2] = true;
+			if(node[temp2].closeNode.size() > 0)
+				makeTree(temp2);
+		}
+		else if (check[temp2]) {
+			node[temp1].depth = node[temp2].depth + 1;
+			node[temp1].parent = temp2;
+			check[temp1] = true;
+			if (node[temp1].closeNode.size() > 0)
+				makeTree(temp1);
+		}
+		else {
+			node[temp1].closeNode.push_back(temp2);
+			node[temp2].closeNode.push_back(temp1);
+		}
+	}
+
+	cin >> numOfPair;
+	for (int i = 0; i < numOfPair; i++) {
+		cin >> temp1 >> temp2;
+		int curResult = node[temp1].depth < node[temp2].depth ? findLCS(temp1, temp2) : findLCS(temp2, temp1);
+		cout << curResult << "\n";
+	}
+}
+
+void makeTree(int root) {
+	int index = 0, length = 0;
+	list[length++] = root;
+
+	while (index < length) {
+		int cur = list[index++];
+		int depth = node[cur].depth + 1;
+
+		for (int i = 0; i < node[cur].closeNode.size(); i++) {
+			int t = node[cur].closeNode[i];
+			if (!check[t]) {
+				node[t].depth = depth;
+				node[t].parent = cur;
+				check[t] = true;
+				list[length++] = t;
+			}
+		}
+		node[cur].closeNode.clear();
+	}
+	return;
+}
+
+int findLCS(int first, int second) {
+	while (node[first].depth != node[second].depth)
+		second = node[second].parent;
+	while (first != second) {
+		first = node[first].parent;
+		second = node[second].parent;
+	}
+	return first;
+}
+
This post is licensed under CC BY 4.0 by the author.

11401 이항계수 3

1149 RGB 거리

Comments powered by Disqus.

diff --git a/posts/1149/index.html b/posts/1149/index.html new file mode 100644 index 000000000..69adbae93 --- /dev/null +++ b/posts/1149/index.html @@ -0,0 +1,81 @@ + 1149 RGB 거리 | 디피의 개발일지
Posts 1149 RGB 거리
Post
Cancel

1149 RGB 거리

1149 RGB 거리

알고리즘(dp)

1
+2
+
1. 그냥 현재 노드에서 이전의 상태가 X였을때의 최솟값을 저장하는 dp 배열 DP[1000][3] 을 선언하고 DP를 해주면 된다.
+2. 첫번째와 마지막이 상관이없으므로 매번 값을 초기화 시켜줄 필요는 없다.
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+
#include <iostream>
+#define INF 2000000000
+using namespace std;
+
+int N, cost[1000][3], DP[1000][3];
+
+int getMin(int deep, int bf);
+int min(int a, int b) {
+	return a < b ? a : b;
+}
+int main() {
+	cin >> N;
+	for (int i = 0; i < N; i++)
+		cin >> cost[i][0] >> cost[i][1] >> cost[i][2];
+
+	int result = INF;
+	for (int i = 0; i < 3; i++) {
+		result = min(result, getMin(1, i) + cost[0][i]);
+	}
+
+	cout << result;
+}
+
+int getMin(int deep, int bf) {
+	if (deep == N) {
+		return 0;
+	}
+	if (DP[deep][bf] != 0)
+		return DP[deep][bf];
+
+	int result = INF;
+
+	for (int i = 0; i < 3; i++)
+		if (i != bf)
+			result = min(result, getMin(deep + 1, i)+ cost[deep][i]);
+
+	return DP[deep][bf] = result;
+}
+
This post is licensed under CC BY 4.0 by the author.

11437 LCA

11657 타임머신

Comments powered by Disqus.

diff --git a/posts/11657/index.html b/posts/11657/index.html new file mode 100644 index 000000000..b5418d021 --- /dev/null +++ b/posts/11657/index.html @@ -0,0 +1,149 @@ + 11657 타임머신 | 디피의 개발일지
Posts 11657 타임머신
Post
Cancel

11657 타임머신

11657 타임머신

알고리즘(밸만포드)

1
+2
+
1. 밸만포드로 푼다.
+2. 길이는 long long으로 저장한다.
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+
#include <bits/stdc++.h>
+
+using namespace std;
+
+struct Road {
+	int to;
+	int time;
+};
+struct City {
+	vector<Road> next;
+	long long minTime;
+	int before;
+};
+
+City city[501];
+list<Road> q;
+int N, M;
+bool generateMinTime();
+
+int main() {
+	ios::sync_with_stdio(0);
+	cin.tie(0);
+	cin >> N >> M;
+
+	int a, b, c;
+	for (int i = 0; i < M; i++) {
+		cin >> a >> b >> c;
+		city[a].next.push_back({ b, c });
+	}
+	for (int i = 2; i <= N; i++)
+		city[i].minTime = 2000000000000;
+
+	if (generateMinTime()) {
+		for (int i = 2; i <= N; i++) {
+			if (city[i].minTime != 2000000000000)
+				cout << city[i].minTime << "\n";
+			else
+				cout << -1 << "\n";
+		}
+	}
+	else
+		cout << -1 << "\n";
+}
+
+bool generateMinTime() {
+	for (int i = 0; i < N - 1; i++) {
+		for (int j = 1; j <= N; j++) {
+			if (city[j].minTime != 2000000000000) {
+				for (int k = 0; k < city[j].next.size(); k++) {
+					Road t = city[j].next[k];
+					long long nt = city[j].minTime + t.time;
+
+					if (nt < city[t.to].minTime)
+						city[t.to].minTime = nt;
+				}
+			}
+		}
+	}
+
+	for (int j = 1; j <= N; j++) {
+		if (city[j].minTime != 2000000000000) {
+			for (int k = 0; k < city[j].next.size(); k++) {
+				Road t = city[j].next[k];
+				long long  nt = city[j].minTime + t.time;
+
+				if (nt < city[t.to].minTime)
+					return false;
+			}
+		}
+	}
+	return true;
+}
+
This post is licensed under CC BY 4.0 by the author.

1149 RGB 거리

1167 트리의 지름

Comments powered by Disqus.

diff --git a/posts/1167/index.html b/posts/1167/index.html new file mode 100644 index 000000000..be1683a88 --- /dev/null +++ b/posts/1167/index.html @@ -0,0 +1,139 @@ + 1167 트리의 지름 | 디피의 개발일지
Posts 1167 트리의 지름
Post
Cancel

1167 트리의 지름

1167 트리의 지름

알고리즘 (dfs, dp)

1
+2
+3
+4
+5
+6
+
1. 1번 노드를 최상위 노드라고 가정하고 품
+2. 1번 노드부터 시작하여 트리를 밑으로 탐색함.
+3. 끝까지 닿으면, 거리를 올리면서(bottom-top) 다시 위로 올라감(재귀로 탐색하면 됨)
+4. 한 노드에서 재귀로 반환하는 건, 자신의 자식 노드에서 올라오는 것중 가장 큰 값(끝까지 갔을 때의 값들 중 가장 큰 값)
+5. 또 한 노드에선, 자식 노드에서 올라오는 것 중 가장 큰 값 2개를 저장하여, 탐색이 끝나면, 그 두개의 합이 전역으로 저장되어있는 length값보다 크면 저장.
+6. 탐색이 모두 끝나면, 전역 length에 트리의 지름이 저장되어있음
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+
#include <iostream>
+#include <vector>
+#include <algorithm>
+
+using namespace std;
+
+vector<pair<int, int>> point[100001];
+int N, maxLength;
+bool check[100001];
+
+long long getLength(int cur);
+long long max(long long a, long long b) {
+	return a > b ? a : b;
+}
+
+int main() {
+	ios::sync_with_stdio(0);
+	cin.tie(0);
+	cout.tie(0);
+
+	cin >> N;
+	for (int i = 1; i <= N; i++) {
+		int cur, a, b;
+		cin >> cur;
+		cin >> a;
+		while (a != -1) {
+			cin >> b;
+			point[cur].push_back({ a, b });
+			cin >> a;
+		}
+	}
+
+	getLength(1);
+	cout << maxLength;
+}
+
+long long getLength(int cur) {
+	check[cur] = true;
+
+	bool isLast = true;
+	long long result[2] = { 0, 0 };
+	for (int i = 0; i < point[cur].size(); i++) {
+		if (!check[point[cur][i].first]) {
+			long long tmp = getLength(point[cur][i].first) + point[cur][i].second;
+
+			result[1] = max(result[1], tmp);
+			if (result[1] > result[0]) {
+				tmp = result[0];
+				result[0] = result[1];
+				result[1] = tmp;
+			}
+
+			isLast = false;
+		}
+	}
+
+	if (isLast)
+		return 0;
+	else {
+		maxLength =max(maxLength, result[0] + result[1]);
+		return max(result[0], result[1]);
+	}
+}
+
This post is licensed under CC BY 4.0 by the author.

11657 타임머신

1197 MST

Comments powered by Disqus.

diff --git "a/posts/11779-\354\265\234\354\206\214\353\271\204\354\232\251\352\265\254\355\225\230\352\270\260-2/index.html" "b/posts/11779-\354\265\234\354\206\214\353\271\204\354\232\251\352\265\254\355\225\230\352\270\260-2/index.html" new file mode 100644 index 000000000..b0ba1eae3 --- /dev/null +++ "b/posts/11779-\354\265\234\354\206\214\353\271\204\354\232\251\352\265\254\355\225\230\352\270\260-2/index.html" @@ -0,0 +1,151 @@ + 11779 최소비용구하기 2 | 디피의 개발일지
Posts 11779 최소비용구하기 2
Post
Cancel

11779 최소비용구하기 2

알고리즘

  • 일반적인 다익스트라로 푸는데, 풀면서 이전에 어디서 왔는지를 기록한다.
  • 다익스트라가 끝나고, 끝에서부터 왔던길을 되돌아가며 경로를 찾아낸다.

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+
#include <iostream>
+#include <queue>
+#include <vector>
+#include <cstring>
+#define INF 2000000000000
+
+typedef long long ll;
+using namespace std;
+
+int n, m, s, e, path[1001];
+vector<int> p;
+vector<pair<int, int>> to[1001];
+priority_queue<pair<ll, int>> q;
+ll cost[1001];
+
+void dijkstra();
+void checkPath();
+
+int main() {
+	ios::sync_with_stdio(0);
+	cin.tie(0);
+	cout.tie(0);
+	cin >> n >> m;
+	while (m--) {
+		int a, b, c;
+		cin >> a >> b >> c;
+		to[a].push_back({ b, c });
+	}
+	cin >> s >> e;
+
+	dijkstra();
+	checkPath();
+	cout << cost[e] << "\n";
+	cout << p.size() << "\n";
+	for (int i = p.size()-1; i >= 0; i--)
+		cout << p[i] << " ";
+}
+
+void dijkstra() {
+	for (int i = 0; i <= n; i++)
+		cost[i] = INF;
+
+	cost[s] = 0;
+	path[s] = -1;
+	q.push({ 0, s });
+	while (!q.empty()) {
+		ll c = -q.top().first;
+		int cur = q.top().second;
+		q.pop();
+
+		if (c > cost[cur])
+			continue;
+
+		for (int i = 0; i < to[cur].size(); i++) {
+			ll nc = c + to[cur][i].second;
+			int ncur = to[cur][i].first;
+
+			if (nc < cost[ncur]) {
+				cost[ncur] = nc;
+				path[ncur] = cur;
+				q.push({ -nc, ncur });
+			}
+		}
+	}
+	return;
+}
+
+void checkPath() {
+	int cur = e;
+	while (path[cur] != -1) {
+		p.push_back(cur);
+		cur = path[cur];
+	}
+	p.push_back(cur);
+}
+
This post is licensed under CC BY 4.0 by the author.

1208 부분수열의 하1 2

17387 선분교차 2

Comments powered by Disqus.

diff --git a/posts/1197/index.html b/posts/1197/index.html new file mode 100644 index 000000000..90810fd9b --- /dev/null +++ b/posts/1197/index.html @@ -0,0 +1,207 @@ + 1197 MST | 디피의 개발일지
Posts 1197 MST
Post
Cancel

1197 MST

1197 MST

알고리즘(크루스칼 알고리즘 사용)

1
+2
+3
+4
+5
+6
+
1. 가장 가중차가 작은 간선부터 검사
+2. 사이클 검사
+	1. 간선의 양단이 전부 이미 안들어갔으면 양단을 같은 벡터에 집어넣고, set 벡터에 집어넣음
+	2. 한쪽만 들어갔다면, 들어가 있는 한쪽의 set벡터에 안들어간 쪽을 집어넣음
+	3. 두개 다 들어가 있고, 들어간 집합이 같다면, 사이클 형성 -> false 반환
+	4. 두개 다 들어가 있고, 들어간 집합이 다르다면, 두개를 합침.
+

자료구조

1
+2
+3
+
간선을 저장하는 Edge 배열
+노드를 저장하는 Node 배열. - 배열의 인덱스가 노드의 번호이고, 저장돼있는 정보는 들어가있는 셋의 번호. 아직 포함안됐으면 -1
+셋을 저장하는 set 벡터 - vector<vector<int>> set 으로 선언하여, set안에 각 vector<int>는 하나의 집합을 표현.
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+
#include <iostream>
+#include <algorithm>
+#include <vector>
+#include <cstring>
+#define MAX_EDGE 1000000
+#define MAX_NODE 10000
+
+using namespace std;
+
+typedef struct Edge {
+	int node1;
+	int node2;
+	int value;
+
+	bool operator < (Edge e) {
+		return value < e.value;
+	}
+} Edge;
+
+Edge edge[MAX_EDGE];
+int nodeSetNum[MAX_NODE];
+vector<vector<int>> set;
+int numOfNode, numOfEdge;
+
+long long getMST();
+bool unionFind(int index);
+
+int main() {
+	cin >> numOfNode >> numOfEdge;
+	for (int i = 0; i < numOfEdge; i++)
+		cin >> edge[i].node1 >> edge[i].node2 >> edge[i].value;
+	for (int i = 0; i < numOfEdge; i++) {
+		edge[i].node1;
+		edge[i].node2;
+	}
+
+	cout << getMST();
+}
+
+long long getMST() {
+	sort(edge, &edge[numOfEdge]);
+	memset(nodeSetNum, -1, sizeof(nodeSetNum));
+	long long sum = 0;
+	int edgeCount = 0;
+	for(int i = 0; i<numOfEdge; i++) {
+		if (unionFind(i)) {
+			sum += edge[i].value;
+			edgeCount++;
+		}
+		//노드 개수 -1 이면 트리완성
+		if (edgeCount == numOfNode - 1)
+			break;
+	}
+
+	return sum;
+}
+
+bool unionFind(int index) {
+	if (nodeSetNum[edge[index].node1] == -1 && nodeSetNum[edge[index].node2] == -1) {
+		vector<int> temp;
+		temp.push_back(edge[index].node1);
+		temp.push_back(edge[index].node2);
+		set.push_back(temp);
+		nodeSetNum[edge[index].node1] = set.size() - 1;
+		nodeSetNum[edge[index].node2] = set.size() - 1;
+	}
+	else if (nodeSetNum[edge[index].node1] == -1) {
+		nodeSetNum[edge[index].node1] = nodeSetNum[edge[index].node2];
+		set[nodeSetNum[edge[index].node1]].push_back(edge[index].node1);
+	}
+	else if (nodeSetNum[edge[index].node2] == -1) {
+		nodeSetNum[edge[index].node2] = nodeSetNum[edge[index].node1];
+		set[nodeSetNum[edge[index].node2]].push_back(edge[index].node2);
+	}
+	else if (nodeSetNum[edge[index].node1] == nodeSetNum[edge[index].node2])
+		return false;
+	else {
+		if (nodeSetNum[edge[index].node1] < nodeSetNum[edge[index].node2]) {
+			vector<int> move = set[nodeSetNum[edge[index].node2]];
+			for (int i = 0; i < move.size(); i++) {
+				nodeSetNum[move[i]] = nodeSetNum[edge[index].node1];
+				set[nodeSetNum[edge[index].node1]].push_back(move[i]);
+			}
+		}
+		else {
+			vector<int> move = set[nodeSetNum[edge[index].node1]];
+			for (int i = 0; i < move.size(); i++) {
+				nodeSetNum[move[i]] = nodeSetNum[edge[index].node2];
+				set[nodeSetNum[edge[index].node2]].push_back(move[i]);
+			}
+		}
+	}
+	return true;
+}
+
This post is licensed under CC BY 4.0 by the author.

1167 트리의 지름

1202 Jewelry thief

Comments powered by Disqus.

diff --git a/posts/1202/index.html b/posts/1202/index.html new file mode 100644 index 000000000..ee0cca9f8 --- /dev/null +++ b/posts/1202/index.html @@ -0,0 +1,169 @@ + 1202 Jewelry thief | 디피의 개발일지
Posts 1202 Jewelry thief
Post
Cancel

1202 Jewelry thief

1202 Jewelry thief

알고리즘

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+
1. 보석을 가치순으로 내림차순으로 정렬
+2. 가방을 multiset에 저장함
+3. 가치가 큰 보석부터 차례대로 꺼내면서 적절한 가방을 선택하고, 집어넣음
+	-적절한 가방 : 무게가 같으면 가장 적절. 같지 않으면 남는 무게가 가장 적은 가방
+	-찾는 법
+		1. 같은 게 있는지 확인
+		2. 없으면, 현재 검사하는 보석의 무게를 넣고 그대로 빼면 넣었던 자리에 있는 옆 가방의 반복자가 반환되는데
+			이 반복자가 multiset.end()와 같으면 보석 무게가 너무 무거워서 넣을 수 있는 가방이 없는 것이므로 다음보석 검사
+			아니면 그 반복자가 가리키는 가방에 집어넣는다.
+	-집어넣은 다음엔 multiset에서 넣은 가방을 삭제한다.
+

푼 과정

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+
1. 보석은 가치순으로 정렬하고, 무게가 가장 적절한 가방에 넣어야된다고 생각함
+2. 가치순으로 정렬하는 거야 쉬운데 적절한 가방을 택하는 과정이 어려웠음
+	-N*N 방법 : 순차탐색. 가방무게 - 보석무게 의 결과가 0또는 양수인 것중 첫번째를 적절한 가방으로 선택. 끝까지가면 없는 것
+		-> 당연히 시간초과남
+	-NlogN방법 : 이분탐색. 적절한 가방무게를 이분탐색함.
+		-> 가방을 하나씩 찾을 때마다, 빈 가방이 생기는데, 이 빈가방이 많아질수록 탐색의 시간이 더 걸리게 됨.(빈가방을 피하는 시간이 계속 증가함)
+		-> 그리고 보석과 같은 무게를 찾는게 아니다보니 제대로 찾는 것 같지도 않음
+3. 이분탐색식으로 하되, 가방을 선택할 때마다, 그 자료구조에서 가방을 지우되 가방을 지우는 연산이 복잡하지 않은 자료구조를 선택해야겠다는 생각이 듦
+	-> 이진탐색트리로 해야겠다고 생각이 듦
+4. 하지만 솔직히 이진탐색트리를 구현해본적이 한번도 없음
+5. 고민하던 중 set과 multiset에 대해 알게됨
+6. set인 중복이 없는 이진탐색트리처럼, multiset은 중복이 잇는 이진탐색트리처럼 사용할 수 있다는 것을 알게 됨
+

과제

1
+
1. 이진탐색트리 구현 방법을 공부해보자.
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+
#include <iostream>
+#include <algorithm>
+#include <set>
+#define MAX 3000000
+
+using namespace std;
+
+typedef struct Jewelry {
+	int weight;
+	int value;
+	bool operator < (Jewelry j) {
+		if (value != j.value)
+			return value > j.value;
+		else
+			return weight < j.weight;
+	}
+} Jewelry;
+
+Jewelry jewelry[MAX];
+multiset<int> bag;
+int numOfJewelry, numOfBag;
+
+long long maxValue();
+
+int main() {
+	cin >> numOfJewelry >> numOfBag;
+	for (int i = 0; i < numOfJewelry; i++)
+		cin >> jewelry[i].weight >> jewelry[i].value;
+	for (int i = 0; i < numOfBag; i++) {
+		int temp;
+		cin >> temp;
+		bag.insert(temp);
+	}
+
+	cout << maxValue();
+}
+
+//무게 넣어서 현재 위치 찾고, 현재 위치의 다음번 가방에 집어넣는 식
+long long maxValue() {
+	sort(jewelry, &jewelry[numOfJewelry]);
+
+	long long value = 0;
+	for (int i = 0; i < numOfJewelry; i++) {
+		if (bag.empty())
+			break;
+		multiset<int>::iterator find = bag.find(jewelry[i].weight);
+		if (find == bag.end()) {
+			multiset<int>::iterator index = bag.insert(jewelry[i].weight);
+			multiset<int>::iterator temp = bag.erase(index);
+			if (temp == bag.end())
+				continue;
+			else
+				bag.erase(temp);
+		}
+		else
+			bag.erase(find);
+		value += jewelry[i].value;
+	}
+
+	return value;
+}
+
This post is licensed under CC BY 4.0 by the author.

1197 MST

12100 Samsung sw test

Comments powered by Disqus.

diff --git "a/posts/1208-\353\266\200\353\266\204\354\210\230\354\227\264\354\235\230-\355\225\2512/index.html" "b/posts/1208-\353\266\200\353\266\204\354\210\230\354\227\264\354\235\230-\355\225\2512/index.html" new file mode 100644 index 000000000..e57b70083 --- /dev/null +++ "b/posts/1208-\353\266\200\353\266\204\354\210\230\354\227\264\354\235\230-\355\225\2512/index.html" @@ -0,0 +1,51 @@ + 1208 부분수열의 하1 2 | 디피의 개발일지
Posts 1208 부분수열의 하1 2
Post
Cancel

1208 부분수열의 하1 2

알고리즘

  • 수를 두개 집합으로 나눔.

  • 나눈 집합 각각에 대해 모든 경우를 살펴봐서 각 집합 내부에서 s에 도달한 횟수를 세고, 만들 수 있는 수와 그 수가 나온 횟수를 센다.

  • 위에서 센 횟수로, 양 집합에서 만들 수 있는 수가 a, b이고, 이 a, b가 각 집합에서 나온 횟수를 c, d라고 하면,

    a + b = s 일때, c * d만큼의 수를 전체 경우의 수에 더한다.

  • 이때 save 배열을 set으로 구현한다면 메모리 사용량이 대폭 줄어든다.(각 집합이 최대 20개이니, 나올 수 있는 수의 개수는 2^20개로, 대략 100만개이므로, 현재보다 1/4로 줄일 수 있음)

    • 근데 귀찮으니 패스

코드

#include <iostream>
+
+typedef long long ll;
+using namespace std;
+
+int n, s, num[40], last, save[2][4000001];
+ll ans;
+
+void dp(int cur, int acc, bool isPut);
+
+int main() {
+	cin >> n >> s;
+	for (int i = 0; i < n; i++)
+		cin >> num[i];
+
+	last = n;
+	if (n > 20) {
+		dp(20, 0, false);
+		last = 20;
+	}
+	dp(0, 0, false);
+
+	for (int i = 0; i <= 4000000; i++) {
+		int t = s - i + 4000000;
+		if (t <= 4000000)
+			if (save[0][i] && save[1][t])
+				ans += (ll)save[0][i] * (ll)save[1][t];
+	}
+
+	cout << ans;
+}
+
+void dp(int cur, int acc, bool isPut) {
+	if (cur == last) {
+		if (isPut) {
+			if (cur > 20)
+				save[1][acc + 2000000]++;
+			else
+				save[0][acc + 2000000]++;
+			if (acc == s)
+				ans++;
+		}
+		return;
+	}
+
+	dp(cur + 1, acc + num[cur], true);
+	dp(cur + 1, acc, isPut);
+
+	return;
+}
+
This post is licensed under CC BY 4.0 by the author.

1504 특정한 최단경로

11779 최소비용구하기 2

Comments powered by Disqus.

diff --git a/posts/12100/index.html b/posts/12100/index.html new file mode 100644 index 000000000..270d181a8 --- /dev/null +++ b/posts/12100/index.html @@ -0,0 +1,467 @@ + 12100 Samsung sw test | 디피의 개발일지
Posts 12100 Samsung sw test
Post
Cancel

12100 Samsung sw test

12100 Samsung sw test

알고리즘(DFS 재귀)

1
+2
+3
+4
+5
+6
+7
+8
+
1. 위, 오른쪽, 아래, 왼쪽으로 차례로 보냄. 보낼때, 보내는 쪽에 가까운 곳부터 하나씩 검사.
+2. 주의 사항 :
+	2 4 8
+	2 8 16
+	4 2 32
+	에서 위로 한번 보내면 맨 왼쪽 맨 위는 4가 되야하고 그 밑에 4가 와야함.
+	하지만 내가 처음 짠 코드에서는 한번 업데이트 된곳을 저장안하고 그때그때 업데이트된 맵에 하나하나씩 적용했기에 맨왼쪽맨위에 8이 왔음
+	이점에 주의
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+
#include <iostream>
+#include <cstring>
+#define MAX_WIDTH 21
+#define MAX_DEEP 5
+
+using namespace std;
+
+int map[MAX_WIDTH][MAX_WIDTH];
+int maxCell = 0;
+int width;
+
+void getProperties();
+void moveDP(int (*currentMap)[MAX_WIDTH], int deep = 0);
+void test() {
+	int m[3][3] = { 1,2,3,4,5,6,7,8,9 };
+	int(*map)[3] = m;
+	for (int i = 0; i < 3; i++) {
+		for (int j = 0; j < 3; j++)
+			cout << map[i][j];
+		cout << endl;
+	}
+	cout << endl;
+	int temp[3][3];
+	for (int i = 0; i < 3; i++)
+		memcpy(temp[i], map[i], sizeof(int) * 3);
+
+	temp[0][0] = 0;
+	temp[1][1] = 0;
+	temp[2][2] = 0;
+
+	for (int i = 0; i < 3; i++) {
+		for (int j = 0; j < 3; j++)
+			cout << map[i][j];
+		cout << endl;
+	}
+	cout << endl;
+	for (int i = 0; i < 3; i++) {
+		for (int j = 0; j < 3; j++)
+			cout << temp[i][j];
+		cout << endl;
+	}
+	cout << endl;
+
+	for (int i = 0; i < 3; i++)
+		memcpy(temp[i], map[i], sizeof(int) * 3);
+
+	for (int i = 0; i < 3; i++) {
+		for (int j = 0; j < 3; j++)
+			cout << temp[i][j];
+		cout << endl;
+	}
+	cout << endl;
+
+	temp[0][0] = 0;
+	temp[1][1] = 0;
+	temp[2][2] = 0;
+
+	for (int i = 0; i < 3; i++) {
+		for (int j = 0; j < 3; j++)
+			cout << temp[i][j];
+		cout << endl;
+	}
+	cout << endl;
+}
+int main() {
+	//test();
+	while (true) {
+		getProperties();
+		moveDP(map);
+		cout << maxCell;
+		maxCell = 0;
+	}
+}
+
+void getProperties() {
+	cin >> width;
+	for (int i = 0; i < width; i++)
+		for (int j = 0; j < width; j++)
+			cin >> map[i][j];
+}
+
+/*
+10
+8  8 4 16 32 0 0 8 8 8
+8  8 4 0  0  8 0 0 0 0
+16 0 0 16 0  0 0 0 0 0
+0  0 0 0  0  0 0 0 0 0
+0  0 0 0  0  0 0 0 0 0
+0  0 0 0  0  0 0 0 0 0
+0  0 0 0  0  0 0 0 0 0
+0  0 0 0  0  0 0 0 0 0
+0  0 0 0  0  0 0 0 0 16
+0  0 0 0  0  0 0 0 0 2
+위 오오위오 -> 128
+*/
+void moveDP(int(*currentMap)[MAX_WIDTH], int deep) {
+	if (deep == MAX_DEEP) {
+		for (int i = 0; i < width; i++) {
+			for (int j = 0; j < width; j++) {
+				if (currentMap[i][j] > maxCell)
+					maxCell = currentMap[i][j];
+				//cout << currentMap[i][j];
+			}
+			//cout << endl;
+		}
+		//cout << endl;
+		return;
+	}
+	int tempMap[MAX_WIDTH][MAX_WIDTH];
+	bool updated[MAX_WIDTH][MAX_WIDTH] = { 0, };
+	for (int i = 0; i < MAX_WIDTH; i++)
+		memcpy(tempMap[i], currentMap[i], sizeof(int) * MAX_WIDTH);
+
+	//위
+	for (int row = 1; row < width; row++)
+		for (int col = 0; col < width; col++)
+			if (tempMap[row][col] != 0)
+				for (int m = row - 1; m >= 0; m--) {
+					if (tempMap[m][col] != 0) {
+						if (tempMap[m][col] == tempMap[row][col] && !updated[m][col]) {
+							tempMap[m][col] *= 2;
+							tempMap[row][col] = 0;
+							updated[m][col] = true;
+						}
+						else {
+							tempMap[m + 1][col] = tempMap[row][col];
+							tempMap[row][col] = m == (row -1) ? tempMap[row][col] : 0;
+						}
+						break;
+					}
+					else if (m == 0) {
+						tempMap[m][col] = tempMap[row][col];
+						tempMap[row][col] = 0;
+					}
+				}
+	moveDP(tempMap, deep + 1);
+	for (int i = 0; i < MAX_WIDTH; i++) {
+		memcpy(tempMap[i], currentMap[i], sizeof(int) * MAX_WIDTH);
+		memset(updated[i], 0, sizeof(bool) * MAX_WIDTH);
+	}
+
+	//오른쪽
+	for (int col = width-2; col >= 0; col--)
+		for (int row = 0; row < width; row++)
+			if (tempMap[row][col] != 0)
+				for (int m = col + 1; m <= width-1; m++) {
+					if (tempMap[row][m] != 0) {
+						if (tempMap[row][m] == tempMap[row][col] && !updated[row][m]) {
+							tempMap[row][m] *= 2;
+							tempMap[row][col] = 0;
+							updated[row][m] = true;
+						}
+						else {
+							tempMap[row][m-1] = tempMap[row][col];
+							tempMap[row][col] = m == (col + 1) ? tempMap[row][col] : 0;
+						}
+						break;
+					}
+					else if (m == width-1) {
+						tempMap[row][m] = tempMap[row][col];
+						tempMap[row][col] = 0;
+					}
+				}
+	moveDP(tempMap, deep + 1);
+	for (int i = 0; i < MAX_WIDTH; i++) {
+		memcpy(tempMap[i], currentMap[i], sizeof(int) * MAX_WIDTH);
+		memset(updated[i], 0, sizeof(bool) * MAX_WIDTH);
+	}
+
+	//아래
+	for (int row = width-2; row >= 0; row--)
+		for (int col = 0; col < width; col++)
+			if (tempMap[row][col] != 0)
+				for (int m = row + 1; m <= width-1; m++) {
+					if (tempMap[m][col] != 0) {
+						if (tempMap[m][col] == tempMap[row][col] && !updated[m][col]) {
+							tempMap[m][col] *= 2;
+							tempMap[row][col] = 0;
+							updated[m][col] = true;
+						}
+						else {
+							tempMap[m - 1][col] = tempMap[row][col];
+							tempMap[row][col] = m == (row + 1) ? tempMap[row][col] : 0;
+						}
+						break;
+					}
+					else if (m == width-1) {
+						tempMap[m][col] = tempMap[row][col];
+						tempMap[row][col] = 0;
+					}
+				}
+	moveDP(tempMap, deep + 1);
+	for (int i = 0; i < MAX_WIDTH; i++) {
+		memcpy(tempMap[i], currentMap[i], sizeof(int) * MAX_WIDTH);
+		memset(updated[i], 0, sizeof(bool) * MAX_WIDTH);
+	}
+
+	//왼쪽
+	for (int col = 1; col <= width-1; col++)
+		for (int row = 0; row < width; row++)
+			if (tempMap[row][col] != 0)
+				for (int m = col - 1; m >= 0; m--) {
+					if (tempMap[row][m] != 0) {
+						if (tempMap[row][m] == tempMap[row][col] && !updated[row][m]) {
+							tempMap[row][m] *= 2;
+							tempMap[row][col] = 0;
+							updated[row][m] = true;
+						}
+						else {
+							tempMap[row][m + 1] = tempMap[row][col];
+							tempMap[row][col] = m == (col - 1) ? tempMap[row][col] : 0;
+						}
+						break;
+					}
+					else if (m == 0) {
+						tempMap[row][m] = tempMap[row][col];
+						tempMap[row][col] = 0;
+					}
+				}
+	moveDP(tempMap, deep + 1);
+	for (int i = 0; i < MAX_WIDTH; i++) {
+		memcpy(tempMap[i], currentMap[i], sizeof(int) * MAX_WIDTH);
+		memset(updated[i], 0, sizeof(bool) * MAX_WIDTH);
+	}
+}
+
This post is licensed under CC BY 4.0 by the author.

1202 Jewelry thief

1248 맞춰봐

Comments powered by Disqus.

diff --git a/posts/1248/index.html b/posts/1248/index.html new file mode 100644 index 000000000..108bc0995 --- /dev/null +++ b/posts/1248/index.html @@ -0,0 +1,139 @@ + 1248 맞춰봐 | 디피의 개발일지
Posts 1248 맞춰봐
Post
Cancel

1248 맞춰봐

1248 맞춰봐

알고리즘 (백트래킹)

1
+2
+
1. 그냥 앞에서부터 가능한 수를 채워나가는 백트래킹 방식을 사용하면 된다.
+2. 이때, 가능한 수는 ans를 채워간다는 얘기다. 즉, 가능한 경우만을 탐색하여 가서 정답만을 하나하나 채워가는 식으로 모든 경우를 탐색하면 됨.
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+
#include <bits/stdc++.h>
+
+using namespace std;
+
+int N, ans[10];
+char S[10][10];
+bool gotAns;
+
+void makeArr(int deep, int beforeSum);
+bool check(int deep, int beforeSum);
+void goNext(int start, int end, int deep, int bs);
+
+int main() {
+	cin >> N;
+	for (int i = 0; i < N; i++)
+		for (int j = i; j < N; j++)
+			cin >> S[i][j];
+
+	makeArr(0, 0);
+	for (int i = 0; i < N; i++)
+		cout << ans[i] << " ";
+}
+
+void makeArr(int deep, int beforeSum) {
+	if (deep == N) {
+		gotAns = true;
+		return;
+	}
+
+	switch (S[deep][deep]) {
+	case '0':
+		goNext(0, 0, deep, beforeSum);
+		break;
+	case '+':
+		goNext(1, 10, deep, beforeSum);
+		break;
+	case '-':
+		goNext(-10, -1, deep, beforeSum);
+		break;
+	}
+}
+
+void goNext(int start, int end, int deep, int bs) {
+	for (int i = start; i <= end; i++) {
+		if (gotAns)
+			return;
+		if (check(deep, bs + i)) {
+			ans[deep] = i;
+			makeArr(deep + 1, bs + i);
+		}
+	}
+}
+
+bool check(int deep, int sum) {
+	int temp = sum;
+
+	for (int i = 0; i < deep; i++) {
+		if (S[i][deep] == '0' && temp != 0)
+			return false;
+		if (S[i][deep] == '+' && temp <= 0)
+			return false;
+		if (S[i][deep] == '-' && temp >= 0)
+			return false;
+		temp -= ans[i];
+	}
+	return true;
+}
+
This post is licensed under CC BY 4.0 by the author.

12100 Samsung sw test

12849 본대산책

Comments powered by Disqus.

diff --git a/posts/12849/index.html b/posts/12849/index.html new file mode 100644 index 000000000..8d495e51c --- /dev/null +++ b/posts/12849/index.html @@ -0,0 +1,87 @@ + 12849 본대산책 | 디피의 개발일지
Posts 12849 본대산책
Post
Cancel

12849 본대산책

12849 본대산책

알고리즘(DP)

1
+2
+3
+4
+
1. a라는 건물에 x분을 남기고 들어가는 것은, a에 연결된 모든 건물 b에 x+1분을 남기고 들어온 것과 같음
+2. 즉, 정보과학관에 0분을 남기고 들어오는 것을 시작으로 모든 경우를 탐색하면 된다.
+3. 쭉쭉 내려가다가 D분에 도달했을때, 장소가 정보과학관이면 1, 아니면 0을 리턴하여 바텀업으로 함
+4. DP에는 현재 위치와 시간으로 배열을 만들고, 그 위치에서 그 시간이 걸렸을 때의 결과를 1000000007로 나눈 값을 저장함
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+
#include <iostream>
+#include <vector>
+#include <cstring>
+#define MAX_MINUTE 100000
+#define DIVIDE 1000000007
+
+using namespace std;
+
+//0 : 정보, 1 : 전산, 2 : 미래, 3 : 신양 , 4: 한경직,
+//5 : 진리, 6 : 형남, 7 : 학생
+vector<vector<int>> map = { {1,2}, {0,2,3}, {0,1,3,4}, {1,2,4,5}, {2,3,5,6}, {3,4,7}, {4,7}, {5,6} };
+int D;
+int DP[8][MAX_MINUTE];
+
+int getMin(int num, int minute);
+
+int main() {
+	memset(DP, -1, sizeof(DP));
+	cin >> D;
+	cout << getMin(0, 0) << endl;
+}
+/*
+a 에 x를 남기고 들어오는 횟수는
+	a와 연결된 모든 b에 x+1를 남기고 들어오는 횟수와 같음
+*/
+
+int getMin(int num, int minute) {
+	if (minute == D)
+		return num == 0 ? 1 : 0;
+	if (DP[num][minute] != -1)
+		return DP[num][minute];
+
+	long long result = 0;
+	int length = map[num].size();
+	for (int i = 0; i < length; i++)
+		result += getMin(map[num][i], minute + 1);
+
+	return DP[num][minute] = result % DIVIDE;
+}
+
This post is licensed under CC BY 4.0 by the author.

1248 맞춰봐

12969 ABC

Comments powered by Disqus.

diff --git a/posts/12865/index.html b/posts/12865/index.html new file mode 100644 index 000000000..0de0324f7 --- /dev/null +++ b/posts/12865/index.html @@ -0,0 +1,41 @@ + 12865 평범한 배낭 | 디피의 개발일지
Posts 12865 평범한 배낭
Post
Cancel

12865 평범한 배낭

12865 평범한 배낭

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
#include <iostream>
+
+using namespace std;
+
+int numOfObject, weight, DP[101][100001];
+
+int main() {
+	cin >> numOfObject >> weight;
+	for (int i = 1; i <= numOfObject; i++) {
+		int w, v;
+		cin >> w >> v;
+		for (int j = 1; j <= weight; j++) {
+			if (j >= w)
+				DP[i][j] = DP[i - 1][j - w] + v > DP[i - 1][j] ? DP[i - 1][j - w] + v : DP[i - 1][j];
+			else
+				DP[i][j] = DP[i - 1][j];
+		}
+	}
+	cout << DP[numOfObject][weight];
+}
+
This post is licensed under CC BY 4.0 by the author.

9527 1의 개수세기

13460 Gold 2

Comments powered by Disqus.

diff --git "a/posts/12872-\355\224\214\353\240\210\354\235\264\353\246\254\354\212\244\355\212\270/index.html" "b/posts/12872-\355\224\214\353\240\210\354\235\264\353\246\254\354\212\244\355\212\270/index.html" new file mode 100644 index 000000000..45d5082c0 --- /dev/null +++ "b/posts/12872-\355\224\214\353\240\210\354\235\264\353\246\254\354\212\244\355\212\270/index.html" @@ -0,0 +1,79 @@ + 12872 플레이리스트 | 디피의 개발일지
Posts 12872 플레이리스트
Post
Cancel

12872 플레이리스트

알고리즘

  • dp를 사용
  • save[101][101] 로 메모이제이션을 위한 배열을 선언
    • 한 곳에는 이제까지 들어간 새로운 곡의 수, 한 곳에는 현재 깊이
  • 원리
    • m == 0 일때, 현재 깊이 d에서는 이전 곡의 구성이 어찌됐든 간에, 현재~끝에 나와야할 곡의 구성의 수는 같다.
    • 하지만 m이 0이 아니고, 모든 곡이 나와야한다는 조건이 있으므로, 이전곡의 구성에 영향을 받는데, 이때 이전에 들어간 개별적인 곡의 수가 몇개였는지에 대해서만 생각해주면 된다.
      • ex) n == 3, m == 1, d == 5 에서 a b a b c, b a b c a 는 같은 상태이다.
    • 따라서 메모이제이션을 위한 배열을 위와같이 만들어주고, 적절한 조건에 따라서 dp를 진행하면 된다.

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+
#include <iostream>
+#include <cstring>
+#define DIV 1000000007
+using namespace std;
+
+int n, m, p, state[101];
+long long save[101][101];
+
+int dp(int d, int count);
+
+int main() {
+	memset(save, -1, sizeof(save));
+	memset(state, -1, sizeof(state));
+	cin >> n >> m >> p;
+	cout << dp(0, 0);
+}
+
+int dp(int d, int count) {
+	if (p - d < n - count)
+		return 0;
+	if (d == p)
+		return 1;
+	if (save[count][d] != -1)
+		return save[count][d];
+
+	long long ans = 0;
+	for (int i = 0; i < n; i++) {
+		if (state[i] == -1 || d - state[i] > m) {
+			int temp = state[i], nc = count;
+			if (temp == -1)
+				nc++;
+			state[i] = d;
+			ans = (ans + dp(d + 1, nc)) % DIV;
+			state[i] = temp;
+		}
+	}
+
+	return save[count][d] = ans % DIV;
+}
+
This post is licensed under CC BY 4.0 by the author.

15989 1,2,3 더하기 4

115653 구슬탈출4

Comments powered by Disqus.

diff --git "a/posts/12906-\354\203\210\353\241\234\354\232\264-\355\225\230\353\205\270\354\235\264\355\203\221/index.html" "b/posts/12906-\354\203\210\353\241\234\354\232\264-\355\225\230\353\205\270\354\235\264\355\203\221/index.html" new file mode 100644 index 000000000..6947b55d6 --- /dev/null +++ "b/posts/12906-\354\203\210\353\241\234\354\232\264-\355\225\230\353\205\270\354\235\264\355\203\221/index.html" @@ -0,0 +1,193 @@ + 12906 새로운 하노이탑 | 디피의 개발일지
Posts 12906 새로운 하노이탑
Post
Cancel

12906 새로운 하노이탑

알고리즘

  • 평범하게 BFS하면 되는데, 방문 표시를 어떻게 하느냐가 이번 문제의 관건이다.

  • 난 각 기둥의 문자열 사이에 “/” 라는 문자를 추가하여 상태를 구분짓고, 이것을 set에 저장하였다.

  • 이렇게 상태를 구분지어본 것은 처음이라, 코드를 짜면서도 계속 내 코드를 의심했다

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+
#include <iostream>
+#include <queue>
+#include <set>
+
+using namespace std;
+
+struct node {
+	string str[3];
+	int cnt;
+};
+
+int num[3];
+queue<node> q;
+set<string> s;
+
+string sumNode(node n) {
+	return n.str[0] + "/" + n.str[1] + "/" + n.str[2];
+}
+
+bool check(node n) {
+	char c[3] = { 'A', 'B', 'C' };
+	for (int i = 0; i < 3; i++) {
+		for (int j = 0; j < n.str[i].size(); j++)
+			if (n.str[i][j] != c[i])
+				return false;
+	}
+	return true;
+}
+
+int main() {
+	int a;
+	node temp;
+	for (int i = 0; i < 3; i++) {
+		cin >> a;
+		if (a != 0)
+			cin >> temp.str[i];
+		else
+			temp.str[i] = "";
+	}
+	temp.cnt = 0;
+	for (int i = 0; i < 3; i++) {
+		for(int j =0; j< temp.str[i].size(); j++)
+			switch (temp.str[i][j]) {
+			case 'A':
+				num[0]++;
+				break;
+			case 'B':
+				num[1]++;
+				break;
+			default:
+				num[2]++;
+				break;
+			}
+	}
+
+	q.push(temp);
+	s.insert(sumNode(temp));
+
+	while (!q.empty()) {
+		node t = q.front();
+		q.pop();
+
+		int sizes[3] = { t.str[0].size(), t.str[1].size() , t.str[2].size() };
+		if (sizes[0] == num[0] && sizes[1] == num[1] && sizes[2] == num[2]) {
+			if (check(t)) {
+				cout << t.cnt;
+				return 0;
+			}
+		}
+
+		for (int i = 0; i < 3; i++) {
+			int size = sizes[i];
+			if (size != 0) {
+				char last = t.str[i][size - 1];
+				t.str[i].pop_back();
+
+				for (int j = 0; j < 3; j++) {
+					if (i != j) {
+						t.str[j] += last;
+
+						string sum = sumNode(t);
+						auto iter = s.find(sum);
+						if (iter == s.end()) {
+							s.insert(sum);
+							q.push({ {t.str[0], t.str[1], t.str[2]}, t.cnt + 1 });
+						}
+
+						t.str[j].pop_back();
+					}
+				}
+				t.str[i] += last;
+			}
+		}
+	}
+}
+
+
This post is licensed under CC BY 4.0 by the author.

9576 책 나눠주기

10986 나머지합

Comments powered by Disqus.

diff --git a/posts/12969/index.html b/posts/12969/index.html new file mode 100644 index 000000000..ae051e7d2 --- /dev/null +++ b/posts/12969/index.html @@ -0,0 +1,119 @@ + 12969 ABC | 디피의 개발일지
Posts 12969 ABC
Post
Cancel

12969 ABC

12969 ABC

알고리즘(dp)

1
+2
+3
+
1. 현재 깊이 d, 이전에 나왔던 a의 개수, 이전에 나왔던 b의 개수, 이제까지의 k 수를 기준으로 dp를 만들어야함. k도 결과에 영향을 주기 때문.
+2. 즉, dp[d][a][b][k] 를 선언하고, 이 상태가 검사되면 true로 지정하여 다시 검사하지않음.
+3. 정답을 찾았을 때는 전역변수 got에 true값을 지정하여 바로 다른 모든걸 종료시키고 출력
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+
#include <iostream>
+#include <cstring>
+using namespace std;
+
+int N, K, num[30], ans[30];
+bool save[31][31][31][450], got;
+
+void DP(int d, int a, int b, int n);
+
+int main() {
+	cin >> N >> K;
+	memset(num, -1, sizeof(num));
+	memset(ans, -1, sizeof(ans));
+
+	DP(0, 0, 0, 0);
+	if (!got)
+		cout << -1 << endl;
+	else {
+		for (int i = 0; i < N; i++) {
+			if (ans[i] == 0)
+				cout << 'A';
+			else if (ans[i] == 1)
+				cout << 'B';
+			else if (ans[i] == 2)
+				cout << 'C';
+		}
+	}
+}
+
+void DP(int d, int a, int b, int n) {
+	if (d == N) {
+		if (n == K) {
+			got = true;
+			memcpy(ans, num, sizeof(num));
+		}
+		return;
+	}
+	if (save[d][a][b][n])
+		return;
+	save[d][a][b][n] = true;
+
+	if (got)
+		return;
+
+	num[d] = 0;
+	DP(d + 1, a + 1, b, n);
+	if (!got) {
+		num[d] = 1;
+		DP(d + 1, a, b + 1, n + a);
+	}
+	if (!got) {
+		num[d] = 2;
+		DP(d + 1, a, b, n + a + b);
+	}
+	num[d] = -1;
+}
+
This post is licensed under CC BY 4.0 by the author.

12849 본대산책

1339 단어 수학

Comments powered by Disqus.

diff --git a/posts/13144/index.html b/posts/13144/index.html new file mode 100644 index 000000000..85aab9bbb --- /dev/null +++ b/posts/13144/index.html @@ -0,0 +1,81 @@ + 13144 List of Unique Numbers | 디피의 개발일지
Posts 13144 List of Unique Numbers
Post
Cancel

13144 List of Unique Numbers

알고리즘(내방식)

  • 숫자를 왼쪽에서 오른쪽으로 살펴가면서, 그 수가 나온 인덱스를 기록(saveIdx)한다.
  • i를 기록할때 이미 기록된 인덱스가 있을 경우, 그 인덱스부터 이전에 체크가 안된 인덱스들은 i 까지만 연속해서 숫자를 뽑을 수 있다. 따라서 이에 해당하는 경우의 수를 정답에 추가시켜준다.

알고리즘(투포인터)

  • 내가 한 방식에서 관점만 바꾸면 투포인터 방법이 된다.

    1. 왼쪽에서 오른쪽으로 end를 하나씩 늘려가며 숫자를 살펴가면서, 현재 수를 체크한다.

    2. 만약 i에서의 숫자가 이미 체크 되었을 경우, start 에서부터 그 체크된 숫자(c)까지는 end 이전까지만 연속해서 숫자를 뽑을 수 있다.
    3. 따라서 start를 c까지 늘려가며 경우의 수를 추가하고, check를 풀어가면 된다.

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+
#include <iostream>
+
+using namespace std;
+
+int n, nums[100010], saveIdx[100010];
+long long ans;
+bool check[100010];
+
+int main() {
+	ios::sync_with_stdio(0);
+	cin.tie(0);
+	cout.tie(0);
+	cin >> n;
+	for (int i = 1; i <= n; i++)
+		cin >> nums[i];
+
+	for (int i = 1; i <= n; i++) {
+		if (saveIdx[nums[i]] != 0) {
+			for (int j = saveIdx[nums[i]]; j >= 1; j--) {
+				if (!check[j]) {
+					ans += (i - j);
+					check[j] = true;
+					saveIdx[nums[j]] = 0;
+				}
+				else
+					break;
+			}
+		}
+		saveIdx[nums[i]] = i;
+	}
+	for (int i = n; i >= 1; i--) {
+		if (!check[i]) {
+			ans += (n + 1 - i);
+			check[i] = true;
+		}
+		else
+			break;
+	}
+	cout << ans;
+}
+
This post is licensed under CC BY 4.0 by the author.

MVVM, MVP 패턴

flex, grid로 남은 곳 꽉채우기

Comments powered by Disqus.

diff --git a/posts/13303/index.html b/posts/13303/index.html new file mode 100644 index 000000000..de3055fe2 --- /dev/null +++ b/posts/13303/index.html @@ -0,0 +1,149 @@ + 13303 장애물 경기 | 디피의 개발일지
Posts 13303 장애물 경기
Post
Cancel

13303 장애물 경기

13303 장애물 경기

알고리즘

1
+2
+3
+4
+5
+
1. 장애물을 x 좌표 순으로 정렬. 도착지점의 Y좌표와 걸린 길이(y축 이동만 고려)를 저장하는 set을 선언 후, {startY, 0} 를 추가
+2. 장애물을 하나씩 꺼내며, 그 장애물에 걸리는 원소만 set에서 꺼내고 하나씩 위로갔을때 거리와 아래로 갔을때 거리를 계산
+    이때, 꺼낸 것이 10개여도 장애물의 위 아래 각각에서 최소거리가 걸리는 것만 set에 다시 저장 -> 즉, 10개 여도 2개만 저장됨.
+3. 2에서 검사한 원소는 set에서 제거한다.
+4. 모든 장애물을 상대로 하면, 이제 최소거리를 구하고, 그때의 y좌표를 저장하고, 출력하면 됨.
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+
#include <iostream>
+#include <algorithm>
+#include <vector>
+#include <set>
+
+using namespace std;
+
+struct Line {
+	long long x;
+	long long yl;
+	long long yh;
+
+	bool operator < (Line b) {
+		return x == b.x ? yl < b.yl : x < b.x;
+	}
+};
+
+int n, starty, endx;
+vector<Line> line;
+set<pair<long long, long long>> minY;
+
+long long min(long long a, long long b) {
+	return a < b ? a : b;
+}
+
+int main() {
+	ios::sync_with_stdio(0);
+	cin.tie(0);
+	cin >> n >> starty >> endx;
+	for (int i = 0; i < n; i++) {
+		long long a, b, c;
+		cin >> a >> b >> c;
+		line.push_back({ a,b,c });
+	}
+	sort(line.begin(), line.end());
+
+	minY.insert({ starty, 0 });
+
+	for (int i = 0; i < n; i++) {
+		vector<pair<long long, long long>> temp;
+		set<pair<long long, long long>>::iterator s = minY.lower_bound({ line[i].yl, -200000000000000 });
+		set<pair<long long, long long>>::iterator e = minY.upper_bound({ line[i].yh, 200000000000000 });
+		long long up = 20000000000000, down = 200000000000000;
+		for (set<pair<long long, long long>>::iterator j = s; j != e; ++j) {
+			up = min(up, (line[i].yh - j->first) + j->second);
+			down = min(down, (j->first - line[i].yl) + j->second);
+		}
+		minY.erase(s, e);
+		minY.insert({ line[i].yh, up });
+		minY.insert({ line[i].yl, down });
+	}
+
+	long long result = 20000000000000;
+	vector<int> resultY;
+	for (set<pair<long long, long long>>::iterator s = minY.begin(); s != minY.end(); s++) {
+		result = min(result, s->second);
+	}
+	for (set<pair<long long, long long>>::iterator s = minY.begin(); s != minY.end(); s++) {
+		if (result == s->second) {
+			resultY.push_back(s->first);
+		}
+	}
+
+	cout << result + endx<< "\n";
+	cout << resultY.size() << " ";
+	for (int i = 0; i < resultY.size(); i++) {
+		cout << resultY[i] << " ";
+	}
+}
+
This post is licensed under CC BY 4.0 by the author.

9095 1,2,3 더하기

노드 스터디 3장

Comments powered by Disqus.

diff --git a/posts/1339/index.html b/posts/1339/index.html new file mode 100644 index 000000000..ba74c4ebe --- /dev/null +++ b/posts/1339/index.html @@ -0,0 +1,83 @@ + 1339 단어 수학 | 디피의 개발일지
Posts 1339 단어 수학
Post
Cancel

1339 단어 수학

1339 단어 수학

알고리즘(브루트포스, 그리디)

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+
- 브루트포스
+	1. 어떤 알파벳이 나왔는지 기록하고, 거기에 맞춰 알파벳의 종류의 개수만큼 모든 가능한 수를 뽑아 검사한다.
+- 그리디
+	1. 들어오는 단어를 알파벳 단위로 분류하고 다음처럼 자릿수를 곱해준다.
+		ABC -> 100A + 10B + C
+		BCA -> 100B + 10C + A
+	2. 그리고 모든 단어를 더해준다.
+		101A + 110B + 11C
+	3. 이를 앞에 자릿수대로 내림차순 정렬하면, 가장 커야할 수가 나오고, 그에따라 수를 부여하고 계산하면 된다.
+		110B + 101A + 11C
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+
#include <iostream>
+#include <string>
+#include <algorithm>
+
+using namespace std;
+
+int N, acheck[26], index, result;
+string word;
+
+int main() {
+	cin >> N;
+
+	for (int i = 0; i < N; i++) {
+		cin >> word;
+		int digit = 1;
+		for (int j = word.size() - 1; j >= 0; j--) {
+			if (acheck[word[j] - 'A'] == 0)
+				index++;
+			acheck[word[j] - 'A'] += digit;
+			digit *= 10;
+		}
+	}
+	sort(acheck, acheck + 26);
+
+	int cur = 9 - index + 1;
+	for (int i = 26 - index; i < 26; i++) {
+		result += acheck[i] * cur;
+		cur++;
+	}
+	cout << result;
+}
+
This post is licensed under CC BY 4.0 by the author.

12969 ABC

13458 Samsung sw test

Comments powered by Disqus.

diff --git a/posts/13458/index.html b/posts/13458/index.html new file mode 100644 index 000000000..7da31b0d5 --- /dev/null +++ b/posts/13458/index.html @@ -0,0 +1,65 @@ + 13458 Samsung sw test | 디피의 개발일지
Posts 13458 Samsung sw test
Post
Cancel

13458 Samsung sw test

13458 Samsung sw test

알고리즘

1
+2
+3
+
1. 오직 1명은 무조건 1명 있어야한다는 말
+2. 총감독관이 감시할 수 있는 응시자 수만큼 빼고, 감독관 수 1명 늘린다음에, 넣어야하는 부감독관 수를 더함. -> 이를 모든 시험장에 적용
+3. 이때 총 필요한 감독관을 저장하는 변수는 long long으로 해야함(응시자 수 100만명, 시험장 수 100만개 이므로)
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+
#include <iostream>
+#define MAX_CLASS 1000000
+
+using namespace std;
+
+int testClass[MAX_CLASS];
+int numOfClass;
+int chief;
+int second;
+
+int main() {
+	cin >> numOfClass;
+	for (int i = 0; i < numOfClass; i++)
+		cin >> testClass[i];
+	cin >> chief >> second;
+
+	long long need = numOfClass;
+	for (int i = 0; i < numOfClass; i++) {
+		testClass[i] -= chief;
+		if (testClass[i] <= 0)
+			continue;
+		int temp = testClass[i] / second;
+		need += temp;
+		testClass[i] -= temp * second;
+		need += (testClass[i] > 0 ? 1 : 0);
+	}
+
+	cout << need;
+}
+
This post is licensed under CC BY 4.0 by the author.

1339 단어 수학

13549 숨바꼭질 3

Comments powered by Disqus.

diff --git a/posts/13460/index.html b/posts/13460/index.html new file mode 100644 index 000000000..c3b80a1ca --- /dev/null +++ b/posts/13460/index.html @@ -0,0 +1,281 @@ + 13460 Gold 2 | 디피의 개발일지
Posts 13460 Gold 2
Post
Cancel

13460 Gold 2

13460 Gold 2

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+
#include <iostream>
+#include <algorithm>
+#include <cstring>
+#define MAX_WIDTH 10
+#define MAX_DEEP 10
+
+using namespace std;
+
+char map[MAX_WIDTH][MAX_WIDTH];
+int width;
+int height;
+int R[2];
+int B[2];
+int Hole[2];
+int minDeep;
+
+void getProperties();
+void findMinWay(int tempR[], int tempB[], int deep = 0);
+void print(int tempR[], int tempB[], int deep);
+
+int main() {
+	getProperties();
+	minDeep = 100;
+	findMinWay(R, B);
+	if (minDeep == 100)
+		cout << -1;
+	else
+		cout << minDeep;
+}
+
+void getProperties() {
+	cin >> height >> width;
+
+	for (int h = 0; h < height; h++) {
+		for (int w = 0; w < width; w++) {
+			cin >> map[h][w];
+			if (map[h][w] == 'R') {
+				R[0] = h;
+				R[1] = w;
+			}
+			else if (map[h][w] == 'B') {
+				B[0] = h;
+				B[1] = w;
+			}
+			else if (map[h][w] == 'O') {
+				Hole[0] = h;
+				Hole[1] = w;
+			}
+		}
+	}
+}
+
+
+void findMinWay(int tempR[], int tempB[], int deep) {
+	if (tempB[0] == Hole[0] && tempB[1] == Hole[1])
+		return;
+	if (tempR[0] == Hole[0] && tempR[1] == Hole[1]) {
+		if (deep < minDeep) {
+			minDeep = deep;
+		}
+		return;
+	}
+	if (deep >= MAX_DEEP)
+		return;
+
+	int currentR[2], currentB[2];
+	memcpy(currentR, tempR, sizeof(tempR)*2);
+	memcpy(currentB, tempB, sizeof(tempB)*2);
+
+	//print(tempR, tempB, deep);
+
+	//위
+	while (map[currentR[0]-1][currentR[1]] != '#' && map[currentR[0]][currentR[1]] != 'O')
+		currentR[0]--;
+	while (map[currentB[0]-1][currentB[1]] != '#' && map[currentB[0]][currentB[1]] != 'O')
+		currentB[0]--;
+	if (currentR[0] == currentB[0] && currentR[1] == currentB[1])
+		if (map[currentB[0]][currentB[1]] != 'O') {
+			if (tempR[0] < tempB[0])
+				currentB[0]++;
+			else
+				currentR[0]++;
+		}
+	findMinWay(currentR, currentB, deep + 1);
+	memcpy(currentR, tempR, sizeof(int) * 2);
+	memcpy(currentB, tempB, sizeof(int) * 2);
+
+
+	//오른쪽
+	while (map[currentR[0]][currentR[1]+1] != '#' && map[currentR[0]][currentR[1]] != 'O')
+		currentR[1]++;
+	while (map[currentB[0]][currentB[1]+1] != '#' && map[currentB[0]][currentB[1]] != 'O')
+		currentB[1]++;
+	if (currentR[0] == currentB[0] && currentR[1] == currentB[1])
+		if (map[currentB[0]][currentB[1]] != 'O') {
+			if (tempR[1] < tempB[1])
+				currentR[1]--;
+			else
+				currentB[1]--;
+		}
+	findMinWay(currentR, currentB, deep + 1);
+	memcpy(currentR, tempR, sizeof(int) * 2);
+	memcpy(currentB, tempB, sizeof(int) * 2);
+
+	//아래
+	while (map[currentR[0]+1][currentR[1]] != '#' && map[currentR[0]][currentR[1]] != 'O')
+		currentR[0]++;
+	while (map[currentB[0]+1][currentB[1]] != '#' && map[currentB[0]][currentB[1]] != 'O')
+		currentB[0]++;
+	if (currentR[0] == currentB[0] && currentR[1] == currentB[1])
+		if (map[currentB[0]][currentB[1]] != 'O') {
+			if (tempR[0] < tempB[0])
+				currentR[0]--;
+			else
+				currentB[0]--;
+		}
+	findMinWay(currentR, currentB, deep + 1);
+	memcpy(currentR, tempR, sizeof(int) * 2);
+	memcpy(currentB, tempB, sizeof(int) * 2);
+
+	//왼쪽
+	while (map[currentR[0]][currentR[1]-1] != '#' && map[currentR[0]][currentR[1]] != 'O')
+		currentR[1]--;
+	while (map[currentB[0]][currentB[1]-1] != '#' && map[currentB[0]][currentB[1]] != 'O')
+		currentB[1]--;
+	if (currentR[0] == currentB[0] && currentR[1] == currentB[1])
+		if (map[currentB[0]][currentB[1]] != 'O') {
+			if (tempR[1] < tempB[1])
+				currentB[1]++;
+			else
+				currentR[1]++;
+		}
+	findMinWay(currentR, currentB, deep + 1);
+	memcpy(currentR, tempR, sizeof(int) * 2);
+	memcpy(currentB, tempB, sizeof(int) * 2);
+}
+
+void print(int tempR[], int tempB[], int deep) {
+	cout << "deep : " << deep << "/ R : " << tempR[0] << ' ' << tempR[1] << "/ B : " << tempB[0] << ' ' << tempB[1] << endl;
+}
+
This post is licensed under CC BY 4.0 by the author.

12865 평범한 배낭

14501 Samsung sw test

Comments powered by Disqus.

diff --git a/posts/13549/index.html b/posts/13549/index.html new file mode 100644 index 000000000..887316ca7 --- /dev/null +++ b/posts/13549/index.html @@ -0,0 +1,113 @@ + 13549 숨바꼭질 3 | 디피의 개발일지
Posts 13549 숨바꼭질 3
Post
Cancel

13549 숨바꼭질 3

13549 숨바꼭질 3

알고리즘(BFS)

1
+
1. BFS로 가능한 곳은 가되, check엔 이전에 온 것보다 적은 시간을 걸리며 온 것을 받아주기 위해 시간을 저장하자.
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+
#include <iostream>
+#include <cstring>
+#include <list>
+
+using namespace std;
+
+int N, K, result = 2000000000;
+int check[200020];
+list<pair<int,int>> q;
+
+int bfs();
+int min(int a, int b) {
+	return a < b ? a : b;
+}
+
+int main() {
+	for (int i = 0; i < 200020; i++)
+		check[i] = 2000000000;
+
+	cin >> N >> K;
+	if (N >= K)
+		cout << N - K;
+	else
+		cout << bfs() << endl;
+
+}
+
+int bfs() {
+	q.push_back({ N, 0 });
+	check[N] = 0;
+
+	while (!q.empty()) {
+		int X = q.front().first, t = q.front().second;
+		q.pop_front();
+
+		if (X >= K) {
+			result = min(result, X - K + t);
+		}
+		else {
+			if (X != 0 && t < check[X * 2]) {
+				q.push_back({ X * 2, t });
+				check[X * 2] = t;
+			}
+			if (t + 1 < check[X + 1]) {
+				q.push_back({ X + 1, t + 1 });
+				check[X + 1] = t + 1;
+			}
+			if (X-1 >= 0 && t + 1 < check[X - 1]) {
+				q.push_back({ X - 1, t + 1 });
+				check[X - 1] = t + 1;
+			}
+		}
+	}
+	return result;
+}
+
This post is licensed under CC BY 4.0 by the author.

13458 Samsung sw test

14003 LIS5

Comments powered by Disqus.

diff --git a/posts/14003/index.html b/posts/14003/index.html new file mode 100644 index 000000000..7e34fe587 --- /dev/null +++ b/posts/14003/index.html @@ -0,0 +1,117 @@ + 14003 LIS5 | 디피의 개발일지
Posts 14003 LIS5
Post
Cancel

14003 LIS5

14003 LIS5

알고리즘 (LIS)

1
+2
+3
+4
+5
+6
+7
+
1. LIS 로 구함
+2. 수열 출력
+	1. LIS구하면서, 현재 LIS의 인덱스를 항상 저장함
+	2. LIS를 구하면, 가장 뒤에있는 LIS의 가장 뒤 원소의 인덱스가 저장됨
+	3. 그 인덱스로부터 길이를 하나씩줄여가며 앞으로 가면서 길이가 맞는걸 뒤에서부터 저장
+	4. 길이가 0이되면 종료
+	5. 이렇게하면 가장 뒤에있는 LIS가 저장된다.
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+
#include <iostream>
+#include <algorithm>
+#define MAX_LENGTH 1000001
+
+using namespace std;
+
+int lengthOfSequence, sequence[MAX_LENGTH], length[MAX_LENGTH], subSequence[MAX_LENGTH], longest, lis[MAX_LENGTH];
+
+void LIS();
+
+int main() {
+	ios::sync_with_stdio(0);
+	cin.tie(0);
+	cin >> lengthOfSequence;
+	for (int i = 1; i <= lengthOfSequence; i++)
+		cin >> sequence[i];
+	LIS();
+	cout << longest << "\n";
+	for (int i = 1; i <= longest; i++)
+		cout << lis[i] << " ";
+}
+
+void LIS() {
+	subSequence[0] = -2000000000;
+	int lengthOfSub = 1;
+
+	int longestIndex = 0;
+	for (int i = 1; i <= lengthOfSequence; i++) {
+		int* lb = lower_bound(subSequence, subSequence + lengthOfSub, sequence[i]);
+		*lb = sequence[i];
+		if (lb - subSequence == lengthOfSub)
+			length[i] = lengthOfSub++;
+		else
+			length[i] = lb - subSequence;
+
+		if (length[i] > longest) {
+			longest = length[i];
+			longestIndex = i;
+		}
+	}
+
+	int temp = longest;
+	for (int i = longestIndex; i >= 1; i--) {
+		if (length[i] == temp) {
+			lis[temp] = sequence[i];
+			temp--;
+		}
+	}
+}
+
+
+
This post is licensed under CC BY 4.0 by the author.

13549 숨바꼭질 3

14499 Samsung sw test

Comments powered by Disqus.

diff --git a/posts/14238/index.html b/posts/14238/index.html new file mode 100644 index 000000000..f182067b4 --- /dev/null +++ b/posts/14238/index.html @@ -0,0 +1,137 @@ + 14238 출근기록 | 디피의 개발일지
Posts 14238 출근기록
Post
Cancel

14238 출근기록

알고리즘

  1. DP 사용
  2. 앞에서부터 백트래킹처럼 조건이 맞을때 전진
  3. 만약 끝까지 갔으면 gotAns = true 설정하여, true이면 바로 종료.
  4. 만약 끝까지 안갔으면, save에 저장
  5. 이때 save[현재-2 문자][현재-1문자][남은 A][남은 B][남은 C] 로 함
    1. 현재 상태를 결정하는 건, 전전문자, 전 문자, 남은 A의 개수, 남은 B의 개수, 남은 C의 개수로 결정되므로.
    2. 사용한 A,B,C 의 개수가 같다면, 현재-2 이전의 문자 배열은 상관없기 때문이다.
  6. DP 함수 시작부분에 현재 상태의 save가 true이면 현재 dp 종료

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+
#include <bits/stdc++.h>
+
+using namespace std;
+
+string input;
+int ABC[3], save[3][3][51][51][51];
+char result[100];
+bool gotAns = false;
+
+void dp(int n);
+void goNext(char cur, int n);
+
+int main() {
+	cin >> input;
+
+	for (int i = 0; i < input.size(); i++) {
+		if (input[i] == 'A') {
+			ABC[0]++;
+		}
+		else if (input[i] == 'B')
+			ABC[1]++;
+		else
+			ABC[2]++;
+	}
+
+	dp(0);
+	if (gotAns) {
+		for (int i = 0; i < input.size(); i++)
+			cout << result[i];
+	}
+	else
+		cout << -1;
+}
+
+void goNext(char cur, int n) {
+	ABC[cur - 'A']--;
+	result[n] = cur;
+	dp(n + 1);
+	ABC[cur - 'A']++;
+}
+
+void dp(int n) {
+	if (n == input.size()) {
+		gotAns = true;
+		return;
+	}
+	else if (gotAns || (n > 1 && save[result[n - 2]-'A'][result[n - 1]-'A'][ABC[0]][ABC[1]][ABC[2]]))
+		return;
+
+	if (ABC[0] > 0)
+		goNext('A', n);
+	if (gotAns)
+		return;
+
+	if (ABC[1] > 0 && (n == 0 || result[n-1] != 'B'))
+		goNext('B', n);
+	if (gotAns)
+		return;
+
+	if(ABC[2] > 0 && (n == 0 || result[n - 1] != 'C') && (n <= 1 || result[n - 2] != 'C'))
+		goNext('C', n);
+	if (gotAns)
+		return;
+
+	if (n > 1) {
+		save[result[n - 2]-'A'][result[n - 1]-'A'][ABC[0]][ABC[1]][ABC[2]] = 1;
+	}
+}
+
This post is licensed under CC BY 4.0 by the author.

2437 저울

16928 뱀과 사다리게임

Comments powered by Disqus.

diff --git "a/posts/14466-\354\206\214\352\260\200-\352\270\270\354\235\204-\352\261\264\353\204\210\352\260\204-\354\235\264\354\234\240-6/index.html" "b/posts/14466-\354\206\214\352\260\200-\352\270\270\354\235\204-\352\261\264\353\204\210\352\260\204-\354\235\264\354\234\240-6/index.html" new file mode 100644 index 000000000..f9aa6b9c5 --- /dev/null +++ "b/posts/14466-\354\206\214\352\260\200-\352\270\270\354\235\204-\352\261\264\353\204\210\352\260\204-\354\235\264\354\234\240-6/index.html" @@ -0,0 +1,141 @@ + 14466 소가 길을 건너간 이유 6 | 디피의 개발일지
Posts 14466 소가 길을 건너간 이유 6
Post
Cancel

14466 소가 길을 건너간 이유 6

알고리즘

  • 모든 소를 대상으로 ‘길’이 있는 길로 다니지 말고 BFS를 함
  • 다른 소를 만날때마다 count를 1씩 증가시키고, 한 소의 bfs가 다 끝나면 n - count를 ans에 저장
  • ans / 2를 출력

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+
#include <iostream>
+#include <list>
+#include <cstring>
+#include <vector>
+
+using namespace std;
+
+int n, k, r, map[101][101];
+list<pair<int, int>> l;
+bool visit[101][101], path[101][101][4];
+pair<int, int> cow[101];
+
+int bfs();
+
+int main() {
+	cin >> n >> k >> r;
+	int a, b, c, d, e, f, g;
+	for (int i = 0; i < r; i++) {
+		cin >> a >> b >> c >> d;
+		e = a - c, f = b - d;
+
+		if (e == 1)
+			g = 0;
+		else if (e == -1)
+			g = 2;
+		else if (f == 1)
+			g = 3;
+		else
+			g = 1;
+		path[c][d][g >= 2 ? g - 2 : g + 2] = true;
+		path[a][b][g] = true;
+	}
+	for (int i = 1; i <= k; i++) {
+		cin >> cow[i].first >> cow[i].second;
+		map[cow[i].first][cow[i].second] = i;
+	}
+	cout << bfs();
+}
+
+int bfs() {
+	int ans = 0, offset[4][2] = { {-1,0}, {0, 1}, {1, 0}, {0, -1} };
+
+	for (int i = 1; i <= k; i++) {
+		int count = 0;
+		visit[cow[i].first][cow[i].second] = true;
+		l.push_back({ cow[i].first, cow[i].second });
+
+		while (!l.empty()) {
+			int r = l.front().first, c = l.front().second;
+			l.pop_front();
+
+			if (map[r][c] != 0)
+				count++;
+
+			for (int i = 0; i < 4; i++) {
+				int nr = r + offset[i][0], nc = c + offset[i][1];
+
+				if (nr <= n && nr >= 1 && nc <= n && nc >= 1 && !visit[nr][nc] && !path[r][c][i]) {
+					l.push_back({ nr, nc });
+					visit[nr][nc] = true;
+				}
+			}
+
+		}
+
+		ans += k - count;
+		memset(visit, 0, sizeof(visit));
+	}
+	return ans / 2;
+}
+
This post is licensed under CC BY 4.0 by the author.

16639 괄호 추가하기 3

React native 기초

Comments powered by Disqus.

diff --git a/posts/14499/index.html b/posts/14499/index.html new file mode 100644 index 000000000..6fb87e125 --- /dev/null +++ b/posts/14499/index.html @@ -0,0 +1,267 @@ + 14499 Samsung sw test | 디피의 개발일지
Posts 14499 Samsung sw test
Post
Cancel

14499 Samsung sw test

14499 Samsung sw test

알고리즘

1
+2
+3
+
1. 각 명령별로 검사. 초기 주사위 상태 (top : 1, north : 2, east : 3, south : 5, western : 4, bottom : 6)
+2. 명령대로 주사위를 굴리고 -> 그 위치가 맵 밖이면 다음 명령 실행
+					맵 안이면 이동시키고, 주사위 상태 변경 -> 이후 이동한 칸에 숫자 적용하고 -> 상단 프린트 -> 다음 명령 실행
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+
#include <iostream>
+#include <cstring>
+#define MAX_WIDTH 20
+#define MAX_COMMAND 1000
+
+using namespace std;
+
+typedef struct Location {
+	int row;
+	int col;
+} Location;
+typedef struct Dice {
+	int top;
+	int north;
+	int east;
+	int south;
+	int western;
+	int bottom;
+}Dice;
+
+int width;
+int height;
+int map[MAX_WIDTH][MAX_WIDTH];
+Location locationDice;
+int numOfCommand;
+int command[MAX_COMMAND];
+int dice[7];
+
+
+void getProperties();
+Dice diceChange(Dice d, int direction);
+bool diceMove(int command);
+void game();
+
+int main() {
+	getProperties();
+	game();
+}
+
+void getProperties() {
+	cin >> height >> width >> locationDice.row >> locationDice.col >> numOfCommand;
+
+	for (int i = 0; i < MAX_WIDTH; i++)
+		memset(map, 0, sizeof(int) * MAX_WIDTH);
+	memset(dice, 0, sizeof(int) * 7);
+
+	for (int i = 0; i < height; i++) {
+		for (int j = 0; j < width; j++)
+			cin >> map[i][j];
+	}
+
+	for (int i = 0; i < numOfCommand; i++)
+		cin >> command[i];
+}
+
+void game() {
+	Dice d = { 1, 2, 3, 5, 4, 6 };
+
+	for (int i = 0; i < numOfCommand; i++) {
+		if (diceMove(command[i])) {
+			d = diceChange(d, command[i]);
+			if (map[locationDice.row][locationDice.col] == 0)
+				map[locationDice.row][locationDice.col] = dice[d.bottom];
+			else {
+				dice[d.bottom] = map[locationDice.row][locationDice.col];
+				map[locationDice.row][locationDice.col] = 0;
+			}
+
+			cout << dice[d.top] << endl;
+		}
+	}
+}
+
+bool diceMove(int command) {
+	//temp를 먼저 옮기고, 범위 밖이면 false리턴, 아니면 수정 후 true리턴
+	Location temp = { locationDice.row, locationDice.col };
+	if (command == 1)
+		temp.col++;
+	else if (command == 2)
+		temp.col--;
+	else if (command == 3)
+		temp.row--;
+	else if (command == 4)
+		temp.row++;
+
+	if (temp.col < 0 || temp.col >= width || temp.row < 0 || temp.row >= height)
+		return false;
+
+	locationDice.col = temp.col;
+	locationDice.row = temp.row;
+	return true;
+}
+
+Dice diceChange(Dice d, int direction) {
+	//1 : 동, 2: 서, 3: 북, 4 : 남
+	Dice temp;
+	if (direction == 1) {
+		temp.top = d.western;
+		temp.north = d.north;
+		temp.east = d.top;
+		temp.south = d.south;
+		temp.western = d.bottom;
+		temp.bottom = d.east;
+	}
+	else if (direction == 2) {
+		temp.top = d.east;
+		temp.north = d.north;
+		temp.east = d.bottom;
+		temp.south = d.south;
+		temp.western = d.top;
+		temp.bottom = d.western;
+	}
+	else if (direction == 3) {
+		temp.top = d.south;
+		temp.north = d.top;
+		temp.east = d.east;
+		temp.south = d.bottom;
+		temp.western = d.western;
+		temp.bottom = d.north;
+	}
+	else if (direction == 4) {
+		temp.top = d.north;
+		temp.north = d.bottom;
+		temp.east = d.east;
+		temp.south = d.top;
+		temp.western = d.western;
+		temp.bottom = d.south;
+	}
+	return temp;
+}
+
This post is licensed under CC BY 4.0 by the author.

14003 LIS5

14500 Samsung sw test

Comments powered by Disqus.

diff --git a/posts/14500/index.html b/posts/14500/index.html new file mode 100644 index 000000000..1296251d9 --- /dev/null +++ b/posts/14500/index.html @@ -0,0 +1,439 @@ + 14500 Samsung sw test | 디피의 개발일지
Posts 14500 Samsung sw test
Post
Cancel

14500 Samsung sw test

14500 Samsung sw test

알고리즘

1
+
1. 가능한 모든 모양으로 검사.
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+
#include <iostream>
+#define MAX_WIDTH 501
+
+using namespace std;
+
+int width, height;
+int map[MAX_WIDTH][MAX_WIDTH];
+
+void getProperties();
+int putTetromino();
+
+int main() {
+	while (true) {
+		getProperties();
+		cout << putTetromino();
+	}
+}
+
+void getProperties() {
+	cin >> height >> width;
+	for (int i = 0; i < height; i++)
+		for (int j = 0; j < width; j++)
+			cin >> map[i][j];
+}
+
+
+int putTetromino() {
+	int max = 0;
+
+	//일자 확인
+	for (int row = 0; row < height; row++) {
+		int temp = map[row][0] + map[row][1] + map[row][2];
+		for (int i = 3; i < width; i++) {
+			temp += map[row][i];
+			if (temp > max)
+				max = temp;
+			temp -= map[row][i - 3];
+		}
+	}
+
+	//세로 일자 확인
+	for (int col = 0; col < width; col++) {
+		int temp = map[0][col] + map[1][col] + map[2][col];
+		for (int i = 3; i < height; i++) {
+			temp += map[i][col];
+			if (temp > max)
+				max = temp;
+			temp -= map[i - 3][col];
+		}
+	}
+
+	//네모 확인
+	int lastHeight = height - 1;
+	int lastWidth = width - 1;
+	for (int row = 0; row < lastHeight; row++) {
+		for (int col = 0; col < lastWidth; col++) {
+			int temp = map[row][col] + map[row][col+1] + map[row+1][col] + map[row+1][col+1];
+			max = temp > max ? temp : max;
+		}
+	}
+
+	//L1 확인
+	lastHeight = height - 2;
+	lastWidth = width - 1;
+	for (int row = 0; row < lastHeight; row++) {
+		for (int col = 0; col < lastWidth; col++) {
+			int temp = map[row][col] + map[row + 1][col] + map[row + 2][col] + map[row + 2][col + 1];
+			max = temp > max ? temp : max;
+		}
+	}
+
+	//L2 확인
+	lastHeight = height - 1;
+	lastWidth = width - 2;
+	for (int row = 0; row < lastHeight; row++) {
+		for (int col = 0; col < lastWidth; col++) {
+			int temp = map[row][col] + map[row + 1][col] + map[row][col + 1] + map[row][col + 2];
+			max = temp > max ? temp : max;
+		}
+	}
+
+	//L3 확인
+	lastHeight = height - 2;
+	lastWidth = width - 1;
+	for (int row = 0; row < lastHeight; row++) {
+		for (int col = 0; col < lastWidth; col++) {
+			int temp = map[row][col] + map[row][col + 1] + map[row + 1][col + 1] + map[row + 2][col + 1];
+			max = temp > max ? temp : max;
+		}
+	}
+
+	//L4 확인
+	lastHeight = height - 1;
+	lastWidth = width - 2;
+	for (int row = 0; row < lastHeight; row++) {
+		for (int col = 0; col < lastWidth; col++) {
+			int temp = map[row + 1][col] + map[row + 1][col + 1] + map[row + 1][col + 2] + map[row][col + 2];
+			max = temp > max ? temp : max;
+		}
+	}
+
+	//L5 확인
+	lastHeight = height - 2;
+	lastWidth = width - 1;
+	for (int row = 0; row < lastHeight; row++) {
+		for (int col = 0; col < lastWidth; col++) {
+			int temp = map[row + 2][col] + map[row + 2][col + 1] + map[row + 1][col + 1] + map[row][col + 1];
+			max = temp > max ? temp : max;
+		}
+	}
+
+	//L6 확인
+	lastHeight = height - 1;
+	lastWidth = width - 2;
+	for (int row = 0; row < lastHeight; row++) {
+		for (int col = 0; col < lastWidth; col++) {
+			int temp = map[row][col] + map[row+1][col] + map[row + 1][col + 1] + map[row+1][col + 2];
+			max = temp > max ? temp : max;
+		}
+	}
+
+	//L7 확인
+	lastHeight = height - 2;
+	lastWidth = width - 1;
+	for (int row = 0; row < lastHeight; row++) {
+		for (int col = 0; col < lastWidth; col++) {
+			int temp = map[row][col] + map[row][col + 1] + map[row + 1][col] + map[row+2][col];
+			max = temp > max ? temp : max;
+		}
+	}
+
+	//L8 확인
+	lastHeight = height - 1;
+	lastWidth = width - 2;
+	for (int row = 0; row < lastHeight; row++) {
+		for (int col = 0; col < lastWidth; col++) {
+			int temp = map[row][col] + map[row][col + 1] + map[row][col + 2] + map[row + 1][col + 2];
+			max = temp > max ? temp : max;
+		}
+	}
+
+	//Z1 확인
+	lastHeight = height - 2;
+	lastWidth = width - 1;
+	for (int row = 0; row < lastHeight; row++) {
+		for (int col = 0; col < lastWidth; col++) {
+			int temp = map[row][col+1] + map[row+1][col + 1] + map[row+1][col] + map[row + 2][col];
+			max = temp > max ? temp : max;
+		}
+	}
+	//Z2 확인
+	lastHeight = height - 1;
+	lastWidth = width - 2;
+	for (int row = 0; row < lastHeight; row++) {
+		for (int col = 0; col < lastWidth; col++) {
+			int temp = map[row][col] + map[row][col + 1] + map[row+1][col + 1] + map[row + 1][col + 2];
+			max = temp > max ? temp : max;
+		}
+	}
+	//Z3 확인
+	lastHeight = height - 2;
+	lastWidth = width - 1;
+	for (int row = 0; row < lastHeight; row++) {
+		for (int col = 0; col < lastWidth; col++) {
+			int temp = map[row][col] + map[row+1][col] + map[row+1][col + 1] + map[row + 2][col + 1];
+			max = temp > max ? temp : max;
+		}
+	}
+	//Z4 확인
+	lastHeight = height - 1;
+	lastWidth = width - 2;
+	for (int row = 0; row < lastHeight; row++) {
+		for (int col = 0; col < lastWidth; col++) {
+			int temp = map[row][col+1] + map[row][col + 2] + map[row+1][col] + map[row + 1][col + 1];
+			max = temp > max ? temp : max;
+		}
+	}
+
+	//T1 확인
+	lastHeight = height - 1;
+	lastWidth = width - 2;
+	for (int row = 0; row < lastHeight; row++) {
+		for (int col = 0; col < lastWidth; col++) {
+			int temp = map[row+1][col] + map[row][col + 1] + map[row + 1][col+2] + map[row + 1][col + 1];
+			max = temp > max ? temp : max;
+		}
+	}
+	//T2 확인
+	lastHeight = height - 2;
+	lastWidth = width - 1;
+	for (int row = 0; row < lastHeight; row++) {
+		for (int col = 0; col < lastWidth; col++) {
+			int temp = map[row][col] + map[row+1][col] + map[row + 2][col] + map[row + 1][col + 1];
+			max = temp > max ? temp : max;
+		}
+	}
+	//T3 확인
+	lastHeight = height - 1;
+	lastWidth = width - 2;
+	for (int row = 0; row < lastHeight; row++) {
+		for (int col = 0; col < lastWidth; col++) {
+			int temp = map[row][col] + map[row][col + 1] + map[row][col+2] + map[row + 1][col + 1];
+			max = temp > max ? temp : max;
+		}
+	}
+	//T4 확인
+	lastHeight = height - 2;
+	lastWidth = width - 1;
+	for (int row = 0; row < lastHeight; row++) {
+		for (int col = 0; col < lastWidth; col++) {
+			int temp = map[row][col + 1] + map[row+1][col] + map[row + 1][col+1] + map[row + 2][col + 1];
+			max = temp > max ? temp : max;
+		}
+	}
+
+
+	return max;
+}
+
This post is licensed under CC BY 4.0 by the author.

14499 Samsung sw test

14938 서강그라운드

Comments powered by Disqus.

diff --git a/posts/14501/index.html b/posts/14501/index.html new file mode 100644 index 000000000..6baeadb49 --- /dev/null +++ b/posts/14501/index.html @@ -0,0 +1,67 @@ + 14501 Samsung sw test | 디피의 개발일지
Posts 14501 Samsung sw test
Post
Cancel

14501 Samsung sw test

14501 Samsung sw test

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+
#include <iostream>
+#define MAX_DAY 15
+
+using namespace std;
+
+int day;
+int time[MAX_DAY];
+int proceeds[MAX_DAY];
+
+int DP(int d, int proceed);
+int max(int a, int b);
+
+int main() {
+	cin >> day;
+	for (int i = 0; i < day; i++)
+		cin >> time[i] >> proceeds[i];
+
+	cout << DP(0, 0);
+ }
+
+int DP(int d, int proceed) {
+	if (d >= day)
+		return proceed;
+
+	int a = 0;
+	if (day - d >= time[d])
+		a = DP(d + time[d], proceed + proceeds[d]);
+	return max(a, DP(d + 1, proceed));
+}
+
+int max(int a, int b) {
+	return a > b ? a : b;
+}
+
This post is licensed under CC BY 4.0 by the author.

13460 Gold 2

14502 Samsung sw test

Comments powered by Disqus.

diff --git a/posts/14502/index.html b/posts/14502/index.html new file mode 100644 index 000000000..60e5327b8 --- /dev/null +++ b/posts/14502/index.html @@ -0,0 +1,191 @@ + 14502 Samsung sw test | 디피의 개발일지
Posts 14502 Samsung sw test
Post
Cancel

14502 Samsung sw test

14502 Samsung sw test

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+
#include <iostream>
+#include <list>
+#include <cstring>
+#define MAX_WIDTH 8
+
+using namespace std;
+
+typedef struct Cell {
+	int row;
+	int col;
+} Cell;
+
+int map[MAX_WIDTH][MAX_WIDTH];
+int tempMap[MAX_WIDTH][MAX_WIDTH];
+int width, height;
+
+int getMaxSafeArea();
+
+int main() {
+	cin >> height >> width;
+	for (int i = 0; i < height; i++)
+		for (int j = 0; j < width; j++)
+			cin >> map[i][j];
+
+	cout << getMaxSafeArea();
+}
+
+int getMaxSafeArea() {
+	int maxSafeArea = 0;
+	int last = height * width - 2;
+	int last2 = height * width - 1;
+	int last3 = height * width;
+
+	for (int start = 0; start < last; start++) {
+		int startRow = start / width;
+		int startCol = start % width;
+		if (map[startRow][startCol] != 0)
+			continue;
+		for (int start2 = start + 1; start2 < last2; start2++) {
+			int start2Row = start2 / width;
+			int start2Col = start2 % width;
+			if (map[start2Row][start2Col] != 0)
+				continue;
+			for (int start3 = start2 + 1; start3 < last3; start3++) {
+				int start3Row = start3 / width;
+				int start3Col = start3 % width;
+				if (map[start3Row][start3Col] != 0)
+					continue;
+
+				for (int i = 0; i < MAX_WIDTH; i++)
+					memcpy(tempMap[i], map[i], sizeof(int) * MAX_WIDTH);
+
+				tempMap[startRow][startCol] = 1;
+				tempMap[start2Row][start2Col] = 1;
+				tempMap[start3Row][start3Col] = 1;
+
+				list<Cell> queue;
+				for (int i = 0; i < height; i++)
+					for (int j = 0; j < width; j++)
+						if (tempMap[i][j] == 2)
+							queue.push_back({ i, j });
+
+				while (!queue.empty()) {
+					Cell current = queue.front();
+					queue.pop_front();
+
+					if (current.row - 1 >= 0 && tempMap[current.row - 1][current.col] == 0) {
+						tempMap[current.row - 1][current.col] = 2;
+						queue.push_back({ current.row - 1, current.col });
+					}
+					if (current.row + 1 < height && tempMap[current.row + 1][current.col] == 0) {
+						tempMap[current.row + 1][current.col] = 2;
+						queue.push_back({ current.row + 1, current.col });
+					}
+					if (current.col - 1 >= 0 && tempMap[current.row][current.col - 1] == 0) {
+						tempMap[current.row][current.col - 1] = 2;
+						queue.push_back({ current.row, current.col - 1 });
+					}
+					if (current.col + 1 < width && tempMap[current.row][current.col + 1] == 0) {
+						tempMap[current.row][current.col + 1] = 2;
+						queue.push_back({ current.row, current.col + 1 });
+					}
+				}
+				int temp = 0;
+				for (int i = 0; i < height; i++)
+					for (int j = 0; j < width; j++)
+						if (tempMap[i][j] == 0)
+							temp++;
+				maxSafeArea = temp > maxSafeArea ? temp : maxSafeArea;
+			}
+		}
+	}
+
+	return maxSafeArea;
+}
+
This post is licensed under CC BY 4.0 by the author.

14501 Samsung sw test

14503 Samsung sw test

Comments powered by Disqus.

diff --git a/posts/14503/index.html b/posts/14503/index.html new file mode 100644 index 000000000..76de8192b --- /dev/null +++ b/posts/14503/index.html @@ -0,0 +1,177 @@ + 14503 Samsung sw test | 디피의 개발일지
Posts 14503 Samsung sw test
Post
Cancel

14503 Samsung sw test

14503 Samsung sw test

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+
#include <iostream>
+#define MAX_WIDTH 50
+
+using namespace std;
+
+int direction;
+int location[2];
+int map[MAX_WIDTH][MAX_WIDTH];
+int height, width;
+
+int robot();
+bool changeDirection();
+
+int main() {
+	cin >> height >> width;
+	cin >> location[0] >> location[1] >> direction;
+	for (int i = 0; i < height; i++)
+		for (int j = 0; j < width; j++)
+			cin >> map[i][j];
+
+	cout << robot();
+}
+
+int robot() {
+	bool doesStop = false;
+
+	while (!doesStop) {
+		map[location[0]][location[1]] = 2;
+
+		while (!changeDirection()) {
+			if (direction == 0)
+				location[0] += 1;
+			else if (direction == 1)
+				location[1] -= 1;
+			else if (direction == 2)
+				location[0] -= 1;
+			else if (direction == 3)
+				location[1] += 1;
+
+			if (map[location[0]][location[1]] == 1) {
+				doesStop = true;
+				break;
+			}
+		}
+	}
+
+	int count = 0;
+	for (int i = 0; i < height; i++)
+		for (int j = 0; j < width; j++)
+			if (map[i][j] == 2)
+				count++;
+	return count;
+}
+
+bool changeDirection() {
+	for (int i = 0; i < 4; i++) {
+		if (direction == 0) {
+			direction = 3;
+			if (map[location[0]][location[1] - 1] == 0) {
+				location[1] -= 1;
+				return true;
+			}
+		}
+		else if (direction == 1) {
+			direction = 0;
+			if (map[location[0] - 1][location[1]] == 0) {
+				location[0] -= 1;
+				return true;
+			}
+		}
+		else if (direction == 2) {
+			direction = 1;
+			if (map[location[0]][location[1] + 1] == 0) {
+				location[1] += 1;
+				return true;
+			}
+		}
+		else if (direction == 3) {
+			direction = 2;
+			if (map[location[0] + 1][location[1]] == 0) {
+				location[0] += 1;
+				return true;
+			}
+		}
+	}
+
+	return false;
+}
+
This post is licensed under CC BY 4.0 by the author.

14502 Samsung sw test

14863 서울에서 경산까지

Comments powered by Disqus.

diff --git a/posts/14529-Where's-Bessie/index.html b/posts/14529-Where's-Bessie/index.html new file mode 100644 index 000000000..0f325749b --- /dev/null +++ b/posts/14529-Where's-Bessie/index.html @@ -0,0 +1,207 @@ + 14529 Where's Bessie | 디피의 개발일지
Posts 14529 Where's Bessie
Post
Cancel

14529 Where's Bessie

알고리즘

  • 그냥 조건에 맞춰서 풀면 되는 문제이다.
  • 가능한 모든 사각형에 대해 검사하고, pcl을 뽑아낸 다음에, 마지막으로 ‘다른 pcl에 포함되는 pcl’을 제거하고 출력하면 된다.
  • ‘한 색은 연속된 지역이 하나이고, 다른 색은 두개 이상이어야한다. ‘ 라는 조건은 다음과 같이 하면된다.
    • 인접한 같은 색에 대해서만 BFS를 진행하고, BFS 진행된 총 수 t를 센다.
    • t가 현재 지역에서 그 색의 수와 같으면 첫번째 조건이 만족되었다고 표시, 그것이 아니라면 두번째 조건이 만족했다고 표시하는데, 두번째 조건은 만족할때마다 1을 더해주는 카운터로 표현하자.
    • 위와같이 하면
      • ‘연속된 지역이 하나인 색’이 없을 경우 첫번째 조건이 만족하지 않음
      • 두 색 모두 하나로 연결되었을때는 두번째 조건이 만족되지 않음.
      • 따라서 정답일때만 pcl에 추가해줄 수 있다.

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+
#include <iostream>
+#include <vector>
+#include <cstring>
+
+using namespace std;
+
+char map[22][22];
+int n, colorCheck[26], os[4][2] = { {-1,0}, {0,1}, {1,0},{0,-1} };
+vector<pair<pair<int, int>, pair<int, int>>> pcl;
+bool visit[22][22];
+pair<int, int> q[420];
+
+void getPCL();
+void checkSingleRectagle(int, int,int,int);
+
+int main() {
+	cin >> n;
+	for (int i = 0; i < n; i++)
+		for (int j = 0; j < n; j++)
+			cin >> map[i][j];
+	getPCL();
+
+	int last = pcl.size() - 1;
+	for (int i = last; i >=0; i--) {
+		int si = pcl[i].first.first, sj = pcl[i].first.second, w = pcl[i].second.first, h = pcl[i].second.second;
+		for (int j = 0; j < i; j++)
+			if (si >= pcl[j].first.first && sj >= pcl[j].first.second && w <= pcl[j].second.first && h <= pcl[j].second.second) {
+				pcl.pop_back();
+				break;
+			}
+	}
+
+	cout << pcl.size() << endl;
+}
+
+void getPCL() {
+	for (int h = n - 1; h >= 0; h--) {
+		for (int w = n - 1; w >= 0; w--) {
+			for (int i = 0; i < n - w; i++) {
+				for (int j = 0; j < n - h; j++) {
+					checkSingleRectagle(i, j, i + w, j + h);
+					memset(colorCheck, 0, sizeof(colorCheck));
+				}
+			}
+		}
+	}
+}
+
+void checkSingleRectagle(int si, int sj, int w, int h) {
+	for (int i = 0; i < pcl.size(); i++)
+		if (si >= pcl[i].first.first && sj >= pcl[i].first.second && w <= pcl[i].second.first && h <= pcl[i].second.second)
+			return;
+
+	int count = 0;
+	for (int i = sj; i <= h; i++) {
+		for (int j = si; j <= w; j++) {
+			if (!colorCheck[map[i][j] - 'A'])
+				count++;
+			colorCheck[map[i][j] - 'A']++;
+			if (count > 2)
+				return;
+		}
+	}
+	if (count != 2)
+		return;
+
+
+	bool check1 = false;
+	int check2 = 0;
+	for (int i = sj; i <= h; i++) {
+		for (int j = si; j <= w; j++) {
+			if (!visit[i][j]) {
+				char c = map[i][j];
+				int temp = colorCheck[c - 'A'], curIdx = 0, lastIdx = 0;
+				visit[i][j] = true;
+				q[lastIdx++] = { i,j };
+				while (curIdx != lastIdx) {
+					int ch = q[curIdx].first, cc = q[curIdx].second;
+					curIdx++;
+					temp--;
+
+					for (int k = 0; k < 4; k++) {
+						int nh = ch + os[k][0], nc = cc + os[k][1];
+						if (nh >= sj && nh <= h && nc >= si && nc <= w && !visit[nh][nc] && map[nh][nc] == c) {
+							q[lastIdx++] = { nh, nc };
+							visit[nh][nc] = true;
+						}
+					}
+				}
+				if (temp == 0)
+					check1 = true;
+				else
+					check2++;
+			}
+			if (check1 && check2 >= 2)
+				break;
+		}
+	}
+	memset(visit, 0, sizeof(visit));
+
+	if (check1 && check2 >= 2)
+		pcl.push_back({ {si, sj}, { w, h} });
+}
+
This post is licensed under CC BY 4.0 by the author.

브라우저 동작원리

11048 이동하기

Comments powered by Disqus.

diff --git a/posts/14658/index.html b/posts/14658/index.html new file mode 100644 index 000000000..f090bf715 --- /dev/null +++ b/posts/14658/index.html @@ -0,0 +1,71 @@ + 14658 하늘에서 별똥별 | 디피의 개발일지
Posts 14658 하늘에서 별똥별
Post
Cancel

14658 하늘에서 별똥별

알고리즘

  • 한 점에서 x축으로의 범위를 정하고, 다른 점에서 y축으로의 범위를 정하는 방식으로 모든 두개의 점 조합으로 트래펄린을 설치한다.

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+
#include <iostream>
+
+using namespace std;
+
+int n, m, l, k;
+pair<int, int> s[110];
+
+int max(int a, int b) {
+	return a < b ? b : a;
+}
+
+
+int main() {
+	cin >> n >> m >> l >> k;
+
+	int a, b;
+	for (int i = 0; i < k; i++) {
+		cin >> a >> b;
+		s[i] = { a, b };
+	}
+
+	int cnt = 0;
+	for (int i = 0; i < k; i++) {
+		for (int j = 0; j < k; j++) {
+			int temp = 0;
+			for (int e = 0; e < k; e++) {
+				if (s[i].first <= s[e].first && s[i].first + l >= s[e].first
+					&& s[j].second <= s[e].second && s[j].second + l >= s[e].second)
+					temp++;
+			}
+			cnt = max(cnt, temp);
+		}
+	}
+	cout << k - cnt;
+}
+
This post is licensed under CC BY 4.0 by the author.

4485 젤다

자바스크립트 데코데이터 패턴

Comments powered by Disqus.

diff --git a/posts/14863/index.html b/posts/14863/index.html new file mode 100644 index 000000000..dc65b98c3 --- /dev/null +++ b/posts/14863/index.html @@ -0,0 +1,157 @@ + 14863 서울에서 경산까지 | 디피의 개발일지
Posts 14863 서울에서 경산까지
Post
Cancel

14863 서울에서 경산까지

14863 서울에서 경산까지

알고리즘 (dp)

1
+2
+3
+
1. dp를 평범하게 진행. dp배열은 dp[101][100001] 로 앞에는 현재 위치, 뒤에는 시간
+2. 시간이 맞으면 진행하되, 안맞으면 result 를 초기화해둔 -20000000이 그대로 있음.
+3. 0보다 낮으면, 현재 시간포함 0초까지 -20000000로 초기화. 같은 상태에 더 낮은 시간을 가지고 접근하는 걸 막음.
+

다른 방법 (dp)

1
+2
+3
+4
+5
+6
+
1. dp[100005] 로 시간만으로 dp를 만듬.
+2. 첫번째 노드의 시간과 값으로 dp 초기화
+3. dp 배열을 t=k 부터 검사하여 값이 있으면, dp[j + a] = max(dp[j+a], dp[j] + b) 로 시간을 올려가며 dp를 수행
+4. 이때 노드는 앞에서부터 검사한다.
+5. 값이 있으면 작업을 수행하고, 끝나면 dp[j] = 0 으로 다시 초기화.
+6. 다 끝나면, 저장된 값중 최고값 뽑아서 리턴
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+
#include <iostream>
+
+using namespace std;
+
+int road[101][4], k, n, dp[101][100001], dp2[100001];
+
+
+//방법 1
+int getMaxDP(int cur, int t);
+//방법 2
+int getMaxDP2();
+
+int max(int a, int b) {
+	return a > b ? a : b;
+}
+int main() {
+	cin >> n >> k;
+
+	for (int i = 0; i < n; i++) {
+		cin >> road[i][0] >> road[i][1] >> road[i][2] >> road[i][3];
+	}
+	cout << getMaxDP2();
+}
+
+int getMaxDP(int cur, int t) {
+	if (cur == n)
+		return 0;
+	if (dp[cur][t] != 0)
+		return dp[cur][t];
+
+	int result = -2000000000;
+	if (t - road[cur][0] >= 0)
+		result = getMaxDP(cur + 1, t - road[cur][0]) + road[cur][1];
+	if (t - road[cur][2] >= 0)
+		result = max(result, getMaxDP(cur + 1, t - road[cur][2]) + road[cur][3]);
+
+	if (result <= 0) {
+		for (int i = t; i >= 0; i--) {
+			if (dp[cur][i] < 0)
+				break;
+			else
+				dp[cur][i] = result;
+		}
+	}
+
+	return dp[cur][t] = result;
+}
+
+int getMaxDP2() {
+	dp2[road[0][0]] = road[0][1];
+	dp2[road[0][2]] = road[0][3];
+
+	for (int i = 1; i < n; i++) {
+		for (int j = k; j >= 0; j--) {
+			if (dp2[j] > 0) {
+				if (j + road[i][0] <= k)
+					dp2[j + road[i][0]] = max(dp2[j + road[i][0]], dp2[j] + road[i][1]);
+				if (j + road[i][2] <= k)
+					dp2[j + road[i][2]] = max(dp2[j + road[i][2]], dp2[j] + road[i][3]);
+				dp2[j] = 0;
+			}
+		}
+	}
+	int result = 0;
+	for (int i = 0; i <= k; i++) {
+		result = max(result, dp2[i]);
+	}
+	return result;
+}
+
This post is licensed under CC BY 4.0 by the author.

14503 Samsung sw test

14888 Samsung sw test

Comments powered by Disqus.

diff --git a/posts/14888/index.html b/posts/14888/index.html new file mode 100644 index 000000000..2c9bfa49d --- /dev/null +++ b/posts/14888/index.html @@ -0,0 +1,145 @@ + 14888 Samsung sw test | 디피의 개발일지
Posts 14888 Samsung sw test
Post
Cancel

14888 Samsung sw test

14888 Samsung sw test

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+
#include <iostream>
+#define MAX_NUM 11
+
+using namespace std;
+
+int num[MAX_NUM];
+int numOfNum;
+int operators[4];	// + - * /
+int operatorPerm[MAX_NUM - 1];	// 1 : +, 2 : -, 3 : *, 4 : /
+int min = 1000000001;
+int max = -1000000001;
+void getMinAndMax(int operatorPerm[MAX_NUM-1], int len);
+void swap_arr(int *arr, int i, int j);
+
+int main() {
+	cin >> numOfNum;
+	for (int i = 0; i < numOfNum; i++)
+		cin >> num[i];
+	cin >> operators[0] >> operators[1] >> operators[2] >> operators[3];
+
+	int index = 0;
+	for (int j = 0; j < operators[0]; j++)
+		operatorPerm[index++] = 0;
+	for (int j = 0; j < operators[1]; j++)
+		operatorPerm[index++] = 1;
+	for (int j = 0; j < operators[2]; j++)
+		operatorPerm[index++] = 2;
+	for (int j = 0; j < operators[3]; j++)
+		operatorPerm[index++] = 3;
+
+	getMinAndMax(operatorPerm, 0);
+	cout << max << endl << min << endl;
+}
+
+//각 연산자 수에 맞게 0~numOfNum-1에서 수를 뽑아 각 연산자에 배치
+void getMinAndMax(int operatorPerm[MAX_NUM - 1], int len) {
+	if (len == numOfNum - 1) {
+		int temp = num[0];
+		for (int i = 1; i < numOfNum; i++) {
+			if (operatorPerm[i - 1] == 0)
+				temp += num[i];
+			else if (operatorPerm[i - 1] == 1)
+				temp -= num[i];
+			else if (operatorPerm[i - 1] == 2)
+				temp *= num[i];
+			else
+				temp /= num[i];
+		}
+
+		if (temp > max)
+			max = temp;
+		if (temp < min)
+			min = temp;
+		return;
+	}
+
+	getMinAndMax(operatorPerm, len+1);
+	for (int i = len+1; i < numOfNum - 1; i++) {
+		if (operatorPerm[len] == operatorPerm[i])
+			continue;
+		swap_arr(operatorPerm, i, len);
+		getMinAndMax(operatorPerm, len + 1);
+		swap_arr(operatorPerm, i, len);
+	}
+}
+
+void swap_arr(int *arr, int i, int j) {
+	int temp = arr[j];
+	arr[j] = arr[i];
+	arr[i] = temp;
+}
+
+
This post is licensed under CC BY 4.0 by the author.

14863 서울에서 경산까지

14889 Samsung sw test

Comments powered by Disqus.

diff --git a/posts/14889/index.html b/posts/14889/index.html new file mode 100644 index 000000000..72c9f60aa --- /dev/null +++ b/posts/14889/index.html @@ -0,0 +1,121 @@ + 14889 Samsung sw test | 디피의 개발일지
Posts 14889 Samsung sw test
Post
Cancel

14889 Samsung sw test

14889 Samsung sw test

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+
#include <iostream>
+#include <cstring>
+#define MAX_WIDTH 20
+
+using namespace std;
+
+int numOfPeople;
+int map[MAX_WIDTH][MAX_WIDTH];
+
+int minDifferenceBetweenTwoTeam(int arr[10], int len, int deep);
+int getSum(int arr[10]);
+int min(int a, int b);
+
+int main() {
+	cin >> numOfPeople;
+	for (int i = 0; i < numOfPeople; i++)
+		for (int j = 0; j < numOfPeople; j++)
+			cin >> map[i][j];
+
+	int arr[10];
+	memset(arr, -1, sizeof(arr));
+	cout << minDifferenceBetweenTwoTeam(arr, 0, 0);
+}
+
+int minDifferenceBetweenTwoTeam(int arr[10], int len, int deep) {
+	if (len == numOfPeople / 2) {
+		//계산
+		int temp[10];
+		int index = 0;
+		int indext = 0;
+		for (int i = 0; i < numOfPeople; i++) {
+			if (arr[index] == i)
+				index++;
+			else
+				temp[indext++] = i;
+		}
+		int d = getSum(temp) - getSum(arr);
+		return d < 0 ? (-1) * d : d;
+	}
+	else if (numOfPeople - deep < numOfPeople / 2 - len)
+		return 10000000;
+
+	arr[len] = deep;
+	int a = minDifferenceBetweenTwoTeam(arr, len + 1, deep + 1);
+	arr[len] = 0;
+	return min(a, minDifferenceBetweenTwoTeam(arr, len, deep + 1));
+}
+
+int min(int a, int b) {
+	return a < b ? a : b;
+}
+
+int getSum(int arr[10]) {
+	int sum = 0;
+	for (int i = 0; i < numOfPeople/2; i++)
+		for (int j = 0; j < numOfPeople/2; j++)
+			sum += map[arr[i]][arr[j]];
+
+	return sum;
+}
+
This post is licensed under CC BY 4.0 by the author.

14888 Samsung sw test

14890 Samsung sw test

Comments powered by Disqus.

diff --git a/posts/14890/index.html b/posts/14890/index.html new file mode 100644 index 000000000..1b58d9857 --- /dev/null +++ b/posts/14890/index.html @@ -0,0 +1,159 @@ + 14890 Samsung sw test | 디피의 개발일지
Posts 14890 Samsung sw test
Post
Cancel

14890 Samsung sw test

14890 Samsung sw test

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+
#include <iostream>
+#include <cstring>
+#define MAX_N 100
+
+using namespace std;
+
+int map[MAX_N][MAX_N];
+int width;
+int need;
+
+int checkEveryArr();
+bool checkSingleArr(int arr[MAX_N]);
+
+int main() {
+	cin >> width >> need;
+	for (int i = 0; i < width; i++)
+		for (int j = 0; j < width; j++)
+			cin >> map[i][j];
+	cout << checkEveryArr();
+}
+
+int checkEveryArr() {
+	int count = 0;
+
+	//행
+	int temp[MAX_N];
+	for (int row = 0; row < width; row++) {
+		if (checkSingleArr(map[row])) {
+			count++;
+		}
+	}
+
+	//열
+	for (int col = 0; col < width; col++) {
+		for (int j = 0; j < width; j++)
+			temp[j] = map[j][col];
+		if (checkSingleArr(temp)) {
+			count++;
+		}
+	}
+	return count;
+}
+
+bool checkSingleArr(int arr[MAX_N]) {
+	bool checked[MAX_N];
+	memset(checked, 0, sizeof(bool) * width);
+
+	int ex = arr[0];
+	for (int i = 1; i < width; i++) {
+		if (ex != arr[i]) {
+			int difference = ex - arr[i];
+			if (difference == -2 || difference == 2)
+				return false;
+
+			if (difference == 1) {
+				if (width - i >= need) {
+					for (int k = 0; k < need; k++)
+						if (arr[i] != arr[i + k])
+							return false;
+					for (int k = 0; k < need; k++)
+						checked[i + k] = true;
+				}
+				else
+					return false;
+			}
+			else if (difference == -1) {
+				if (i >= need) {
+					for (int k = 1; k <= need; k++)
+						if (ex != arr[i - k] || checked[i-k])
+							return false;
+				}
+				else
+					return false;
+			}
+		}
+		ex = arr[i];
+	}
+	return true;
+}
+
This post is licensed under CC BY 4.0 by the author.

14889 Samsung sw test

14891 Samsung sw test

Comments powered by Disqus.

diff --git a/posts/14891/index.html b/posts/14891/index.html new file mode 100644 index 000000000..18ffbc9e1 --- /dev/null +++ b/posts/14891/index.html @@ -0,0 +1,229 @@ + 14891 Samsung sw test | 디피의 개발일지
Posts 14891 Samsung sw test
Post
Cancel

14891 Samsung sw test

14891 Samsung sw test

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+
#include <iostream>
+#include <cstdio>
+#define MAX_ROTATE 100
+#define LEFT 6
+#define RIGHT 2
+
+using namespace std;
+
+int command[MAX_ROTATE][2];
+int gear[4][8];
+int numOfRotate;
+
+int implementCommand();
+void implementSingleCommand(int gearN, int way);
+void rotateSingleGear(int gearN, int way);
+
+int main() {
+	for (int i = 0; i < 4; i++)
+		for (int j = 0; j < 8; j++)
+			scanf_s("%1d", &gear[i][j]);
+	cin >> numOfRotate;
+	for (int i = 0; i < numOfRotate; i++) {
+		cin >> command[i][0] >> command[i][1];
+		command[i][0] -= 1;
+	}
+
+	cout << implementCommand();
+}
+
+int implementCommand() {
+	for (int i = 0; i < numOfRotate; i++)
+		implementSingleCommand(command[i][0], command[i][1]);
+
+	int score = 0;
+	score += gear[0][0] == 0 ? 0 : 1;
+	score += gear[1][0] == 0 ? 0 : 2;
+	score += gear[2][0] == 0 ? 0 : 4;
+	score += gear[3][0] == 0 ? 0 : 8;
+	return score;
+}
+
+void implementSingleCommand(int gearN, int way) {
+	//1 : 시계방향, -1 : 반시계방향
+
+	rotateSingleGear(gearN, way);
+
+	//왼쪽
+	int exWay = way;
+	for (int current = gearN - 1; current >= 0; current--) {
+		if (exWay == 1) {
+			if (gear[current + 1][LEFT + 1] != gear[current][RIGHT]) {
+				rotateSingleGear(current, -1);
+				exWay = -1;
+			}
+			else
+				exWay = 0;
+		}
+		else if (exWay == -1) {
+			if (gear[current + 1][LEFT - 1] != gear[current][RIGHT]) {
+				rotateSingleGear(current, 1);
+				exWay = 1;
+			}
+			else
+				exWay = 0;
+		}
+		else
+			break;
+	}
+
+	//오른쪽
+	exWay = way;
+	for (int current = gearN + 1; current <= 3; current++) {
+		if (exWay == 1) {
+			if (gear[current - 1][RIGHT + 1] != gear[current][LEFT]) {
+				rotateSingleGear(current, -1);
+				exWay = -1;
+			}
+			else
+				exWay = 0;
+		}
+		else if (exWay == -1) {
+			if (gear[current - 1][RIGHT - 1] != gear[current][LEFT]) {
+				rotateSingleGear(current, 1);
+				exWay = 1;
+			}
+			else
+				exWay = 0;
+		}
+		else
+			break;
+	}
+
+}
+
+void rotateSingleGear(int gearN, int way) {
+	if (way == 1) {
+		int temp = gear[gearN][0];
+		for (int i = 1; i < 8; i++) {
+			int temp2 = gear[gearN][i];
+			gear[gearN][i] = temp;
+			temp = temp2;
+		}
+		gear[gearN][0] = temp;
+	}
+	else if (way == -1) {
+		int temp = gear[gearN][7];
+		for (int i = 6; i >= 0; i--) {
+			int temp2 = gear[gearN][i];
+			gear[gearN][i] = temp;
+			temp = temp2;
+		}
+		gear[gearN][7] = temp;
+	}
+}
+
This post is licensed under CC BY 4.0 by the author.

14890 Samsung sw test

15486 퇴사2

Comments powered by Disqus.

diff --git a/posts/14938/index.html b/posts/14938/index.html new file mode 100644 index 000000000..b2b183039 --- /dev/null +++ b/posts/14938/index.html @@ -0,0 +1,133 @@ + 14938 서강그라운드 | 디피의 개발일지
Posts 14938 서강그라운드
Post
Cancel

14938 서강그라운드

14938 서강그라운드

알고리즘(플로이드워셜)

1
+2
+3
+4
+
1. 접근 방법
+	- 처음엔 DFS 또는 BFS로 접근
+	- 근데 해보니 이전에 왔던 것보다 적은 거리를 써서온건 보내고, 보낸 다음부터 이어지는 건 겹치면 빼야함.
+	- 결국 플로이드워셜로 최소거리를 모두 구한 후, 탐색범위 안에 들어오는 지역의 아이템을 모두 더하는 방식과 동일함.
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+
#include <iostream>
+#include <list>
+#include <cstring>
+using namespace std;
+
+int n, m, r, t, item[31], area[31][31];
+int check[31];
+list<pair<int, pair<int, int>>> q;
+
+int floyd();
+int max(int a, int b) {
+	return a > b ? a : b;
+}
+int min(int a, int b) {
+	return a < b ? a : b;
+}
+
+int main() {
+	cin >> n >> m >> r;
+	for (int i = 1; i <= n; i++)
+		cin >> item[i];
+
+	for (int i = 0; i <= n; i++)
+		for (int j = 0; j <= n; j++)
+			if (i != j)
+				area[i][j] = 200000000;
+
+	for (int i = 0; i < r; i++) {
+		int a, b, c;
+		cin >> a >> b >> c;
+
+		if (c < area[a][b]) {
+			area[a][b] = c;
+			area[b][a] = c;
+		}
+	}
+	cout << floyd() << endl;
+}
+
+int floyd() {
+	int result = 0;
+
+	for (int i = 1; i <= n; i++) {
+		for (int j = 1; j <= n; j++) {
+			for (int k = 1; k <= n; k++) {
+				if (area[j][k] > area[j][i] + area[i][k])
+					area[j][k] = area[j][i] + area[i][k];
+			}
+		}
+	}
+
+	for (int i = 1; i <= n; i++) {
+		int temp = 0;
+		for (int j = 1; j <= n; j++) {
+			if (area[i][j] <= m)
+				temp += item[j];
+		}
+		result = max(result, temp);
+	}
+	return result;
+}
+
+
This post is licensed under CC BY 4.0 by the author.

14500 Samsung sw test

14939 불끄기

Comments powered by Disqus.

diff --git a/posts/14939/index.html b/posts/14939/index.html new file mode 100644 index 000000000..18a367d88 --- /dev/null +++ b/posts/14939/index.html @@ -0,0 +1,173 @@ + 14939 불끄기 | 디피의 개발일지
Posts 14939 불끄기
Post
Cancel

14939 불끄기

14939 불끄기

알고리즘(브루트, 그리디, 비트마스킹)

1
+2
+3
+4
+5
+
1. 최소한으로 눌러야하는 경우 : 한 칸은 한번만 눌러야함. 두번 이상누르면 최소가 아니고, 무한 루프가 될 수 잇음
+2. 모든 방법을 다 검사해봐야 정답을 얻을 수 있음 -> 브루트 포스
+3. 한번씩만 누르면 누르는 순서는 중요하지않음 -> 첫줄부터 차례대로 누름
+4. 이때 첫번째줄은 모든 경우로 누른다고 치고, 두번째 줄부터는 위에 전구가 켜진 경우만 눌러도 해결이 가능하다. -> 그리디
+5. 모든 경우는 0~1023까지 비트마스킹 방식으로 누르자 -> 비트마스킹
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+
#include <bits/stdc++.h>
+
+using namespace std;
+
+bool smap[10][10], tempMap[10][10];
+
+int brute();
+int rest();
+void swit(int r, int c);
+int min(int a, int b) {
+	return a < b ? a : b;
+}
+
+int main() {
+	for (int i = 0; i < 10; i++) {
+		char t;
+		for (int j = 0; j < 10; j++) {
+			cin >> t;
+			if (t == '#')
+				smap[i][j] = 0;
+			else if (t == 'O')
+				smap[i][j] = 1;
+		}
+	}
+
+	int result = brute();
+	if (result >= 2000000000)
+		cout << -1;
+	else
+		cout << result;
+
+}
+
+int brute() {
+	int result = 2000000000;
+	for (int i = 0; i <= 1023; i++) {
+		memcpy(tempMap, smap, sizeof(smap));
+
+		int k = 0, count = 0;
+		for (int j = 512; j >= 1; j /= 2) {
+			if (i & j) {
+				swit(0, k);
+				count++;
+			}
+			k++;
+		}
+		int r = rest();
+		result = min(result, count + r);
+	}
+
+	return result;
+}
+
+int rest() {
+	int count = 0;
+	for (int i = 1; i < 10; i++)
+		for (int j = 0; j < 10; j++)
+			if (tempMap[i - 1][j]) {
+				swit(i, j);
+				count++;
+			}
+
+	for (int j = 0; j < 10; j++) {
+		if (tempMap[9][j])
+			count = 2000000000;
+	}
+
+	return count;
+}
+
+void swit(int r, int c) {
+	tempMap[r][c] = !(tempMap[r][c]);
+	if (r > 0)
+		tempMap[r - 1][c] = !(tempMap[r - 1][c]);
+	if (c < 9 )
+		tempMap[r][c + 1] = !(tempMap[r][c + 1]);
+	if (r < 9)
+		tempMap[r + 1][c] = !(tempMap[r + 1][c]);
+	if (c > 0)
+		tempMap[r][c - 1] = !(tempMap[r][c - 1]);
+}
+
This post is licensed under CC BY 4.0 by the author.

14938 서강그라운드

1509 팰린드롬 분할

Comments powered by Disqus.

diff --git "a/posts/1504-\355\212\271\354\240\225\355\225\234-\354\265\234\353\213\250\352\262\275\353\241\234/index.html" "b/posts/1504-\355\212\271\354\240\225\355\225\234-\354\265\234\353\213\250\352\262\275\353\241\234/index.html" new file mode 100644 index 000000000..74052a68d --- /dev/null +++ "b/posts/1504-\355\212\271\354\240\225\355\225\234-\354\265\234\353\213\250\352\262\275\353\241\234/index.html" @@ -0,0 +1,139 @@ + 1504 특정한 최단경로 | 디피의 개발일지
Posts 1504 특정한 최단경로
Post
Cancel

1504 특정한 최단경로

알고리즘

  • 1 -> v1 -> v2 -> n 과 1 -> v2 -> v1 -> n 중 어느것이 더 짧은지 확인하면 된다.
  • 즉, 원점이 1일때, v1 일때, v2일때 다익스트라를 진행하고, 거기서 나온 거리로 위 거리를 계산하여 출력하면 된다.

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+
#include <iostream>
+#include <queue>
+#include <vector>
+#define INF 2100000000;
+
+using namespace std;
+
+int n, m, v1,v2, fromOne[801], fromV1[801], fromV2[801];
+vector<pair<int, int>> e[801];
+priority_queue<pair<int, int>> q;
+
+int getAns();
+void dijkstra(int* from, int c);
+int min(int a, int b) {
+	return a < b ? a : b;
+}
+
+int main() {
+	ios::sync_with_stdio(0);
+	cin.tie(0);
+
+	cin >> n >> m;
+	for (int i = 0; i < m; i++) {
+		int a, b, c;
+		cin >> a >> b >> c;
+		e[a].push_back({ b, c });
+		e[b].push_back({ a, c });
+	}
+	cin >> v1 >> v2;
+
+	cout << getAns();
+}
+
+int getAns() {
+	dijkstra(fromOne, 1);
+	dijkstra(fromV1, v1);
+	dijkstra(fromV2, v2);
+
+	int ans = INF;
+	if (fromOne[v1] == ans || fromV1[v2] == ans || fromV1[n] == ans || fromV2[n] == ans)
+		return -1;
+	return min(fromOne[v1] + fromV1[v2] + fromV2[n], fromOne[v2] + fromV2[v1] + fromV1[n]);
+}
+
+
+void dijkstra(int* from, int c) {
+	for (int i = 1; i <= n; i++)
+		from[i] = INF;
+	from[c] = 0;
+
+	q.push({ 0, c });
+	while (!q.empty()) {
+		int cost = -q.top().first, cur = q.top().second;
+		q.pop();
+
+		if (cost > from[cur])
+			continue;
+
+		for (int i = 0; i < e[cur].size(); i++) {
+			int nc = cost + e[cur][i].second, next = e[cur][i].first;
+
+			if (nc < from[next]) {
+				from[next] = nc;
+				q.push({ -nc, next });
+			}
+		}
+	}
+}
+
+
This post is licensed under CC BY 4.0 by the author.

115653 구슬탈출4

1208 부분수열의 하1 2

Comments powered by Disqus.

diff --git a/posts/1509/index.html b/posts/1509/index.html new file mode 100644 index 000000000..414460915 --- /dev/null +++ b/posts/1509/index.html @@ -0,0 +1,225 @@ + 1509 팰린드롬 분할 | 디피의 개발일지
Posts 1509 팰린드롬 분할
Post
Cancel

1509 팰린드롬 분할

1509 팰린드롬 분할

알고리즘(Manacher’s Algorithm, DP)

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+
1. 2차원 bool 형 배열 palindrome에 모든 팰린드롬을 저장.
+	-> palindrome[i][j] 엔 시작이 i이고 끝이 j인 문자열이 팰린드롬인지 아닌지 저장돼있음
+	-> 구하는 법은 처음부터 i = 0~length, j = i~length; 로 가면서 팰린드롬인지 확인하고,
+		팰린드롬이면 Manacher’s Algorithm 으로 그 사이 팰린드롬을 전부 표시
+		아니면 그냥 넘어간다
+	-> 팰린드롬일경우 그 사이는 전부 표시했으니, 이중for문 진행하면서 palindrome[i][j]이 true면 그냥 넘어가면 된다.
+
+2. 저장한 palindrome을 바탕으로 바텀업 DP 시행
+	-> DP[length] : 각 시작점이 x일때 이후 최소의 분할수를 기록한 것.
+	-> DP함수 처음 호출할 때 시작을 0으로 하고, 끝점을 0에서 length까지 검사하며 각 끝점에 대해 팰린드롬이면 start + 1를 주며 아래로 내려간다.
+	-> 그리고 start가 length와 같아지면 0을 반환하며 1씩 증가하며 올라간다.
+	-> 이때 각 시작점에선 모든 끝점에 대해 반환값이 최소인 것을 result에 기록하고, 모든 끝점에 대한 검사가 끝나면 DP[start]에 result를 기록하고 반환한다.
+
+바텀업으로 재귀 DP 사용하니 느림
+

다른방법

1
+2
+3
+4
+5
+
->  해보니 시간은 비슷하다. 그냥 다른 DP 방식도 있는걸로 알고 넘어가자
+1. 반복문으로, i : 1~length / j : i+1~length 를 검사
+2. 이전에 저장된 값이 있으면, DP[i-1] + 1하고 비교했을 때 더 크면 그걸로 업데이트 아니면 그냥 넘어가기
+3. 저장된값이 없으면 DP[i-1] + 1 저장
+4. 각 시작부분은 시작 시 위 2~3을 한다.
+
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+
    for (int i = 1; i <= length; i++){
+         	if ((DP[i] != 0 && DP[i] >DP[i - 1] + 1) || DP[i] == 0)
+
+DP[i] = DP[i - 1] + 1;
+
+for (int j = i + 1; j <= length; j++){
+if (Dp[i][j] != 0)
+{
+if ((DP[j] != 0 && DP[j] >DP[i-1] + 1) || DP[j] == 0)
+DP[j] = DP[i - 1] + 1;
+}
+}
+}
+
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+
#include <iostream>
+#include <string>
+#define MAX_LENGTH 2500
+
+using namespace std;
+//2차원 배열로 모든 팰린드롬 저장
+//첫번째 줄에 팰린드롬인것을 하나씩 넣음
+//그리고 오른쪽 아래로 내려가며 가능한 것을 최대한 넣자.
+//ex) map[0][0] 은 무조건 팰린드롬이니 고름 -> map[1][1]에서 가장가까운 팰린드롬 고름 map[1][1] -> map[2][2] -> ..... 총길이 2500, -> map[1][2] ...map[1][3].. 이렇게 진행?
+//위처럼 진행할 경우 2500*2500 + 2499*2499 + ... 총 26억 계산 -> 시간초과
+//가장가까운이 아닌, 가장 먼곳부터 골라보자. map[0][n] ... 그리고 길이가 이미고른 길이보다 길면 중간종료 하는 식으로?
+//하지만 최악의 경우 또 26억이 됨
+//DP  = 위 계산을 바텀업방식으로 하여 DP[x] 에 길이를 저장하는 방식으로 해보자
+
+int length, DP[MAX_LENGTH];
+char input[MAX_LENGTH];
+bool palindrome[MAX_LENGTH][MAX_LENGTH];
+
+void makePalindrome();
+bool isPalindrome(int start, int end);
+void recordPalindrome(int start, int end);
+int getMinSplit(int start);
+int min(int a, int b) {
+	return a < b ? a : b;
+}
+
+int main() {
+	string tempInput;
+	cin >> tempInput;
+	length = tempInput.size();
+	for (int i = 0; i < tempInput.size(); i++)
+		input[i] = tempInput[i];
+
+	makePalindrome();
+	cout << getMinSplit(0);
+
+}
+
+void makePalindrome() {
+	for (int i = 0; i < length; i++) {
+		for (int j = length - 1; j >= i; j--) {
+			if (palindrome[i][j] != 0)
+				continue;
+
+			if (isPalindrome(i, j))
+				recordPalindrome(i, j);
+			else
+				palindrome[i][j] = 0;
+		}
+	}
+}
+
+bool isPalindrome(int start, int end) {
+	for (; start < end; start++, end--)
+		if (input[start] != input[end])
+			return false;
+
+	return true;
+}
+
+void recordPalindrome(int start, int end) {
+	for (; start <= end; start++, end--)
+		palindrome[start][end] = 1;
+}
+
+int getMinSplit(int start) {
+	if (start == length)
+		return 0;
+	if (DP[start])
+		return DP[start];
+
+	int result = 200000000;
+
+	for (int i = length-1; i >= start; i--)
+		if (palindrome[start][i])
+			result = min(result, getMinSplit(i + 1) + 1);
+
+	return DP[start] = result;
+}
+
This post is licensed under CC BY 4.0 by the author.

14939 불끄기

1516 Developing game

Comments powered by Disqus.

diff --git a/posts/1516/index.html b/posts/1516/index.html new file mode 100644 index 000000000..bff9ee92a --- /dev/null +++ b/posts/1516/index.html @@ -0,0 +1,143 @@ + 1516 Developing game | 디피의 개발일지
Posts 1516 Developing game
Post
Cancel

1516 Developing game

1516 Developing game

알고리즘(위상정렬, 그래프. DP)

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
1. 어떤 건물을 짓기 위해 필요한 건물을 짓는 시간의 최대 + 이 건물을 짓는데 걸리는 시간을 출력하면 됨.
+2. 예를 들어
+	A ->  	-> D
+	      C
+	B -> 	-> E
+	로 그래프가 이루어져 있을 때,
+
+	A : A를 짓는 시간
+	B : B를 짓는 시간
+	C : max(A, B) + C를 짓는데 걸리는 시간
+	D : C + D를 짓는데 걸리는 시간
+	E : C + E를 짓는데 걸리는 시간
+	이기 때문이다.
+	물론 모든 건물은 지어질 수 있는 상태일 때 바로 짓는 것을 가정한다.
+3. 나는 이걸 무한루프로 모든 건물을 검사할 때까지, 모든 건물을 처음부터 끝까지 확인하는 식으로 구현하였다.
+   최대 건물의 수가 500개이니 선형일경우 최악 25000이기에 충분히 시간이 많다고 생각했기 때문이다.
+   하지만, 더 빠른 방법은 위상정렬을 하여 하는 방법이다.
+   그래프가 그려져 있는 대로 정렬을 하여야한다.
+   예를 들어, 위의 예에선 A B C D E 순으로 정렬이 되어야한다.
+   이렇게하면, 무한루프로 할 필요없이 처음부터 그냥 쑥훑으면서 검사하면 끝나기 때문이다.
+

기타

1
+2
+
1. 위상정렬 하는 법 알아보기 -> 알아봤는데 굳이 위상정렬 배열을 만들 필요는 없는 거 같다.
+2. DP : 왜 알고리즘 분류에 DP가 들어갔나 했더만, 이미 검사한 거는 check표시하여 검사하지않고, 최댓값을 다음 노드로 계속 넘겨주는 형태가 DP인거 같다.
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+
#include <bits/stdc++.h>
+#define MAX_LENGTH 100
+#define DIV 1000000000
+#define ALL 1023
+
+using namespace std;
+
+int N, num[101];
+long long DP[101][10][1 << 10];
+
+long long getAnswer(int, int);
+
+int main() {
+	cin >> N;
+	memset(DP, -1, sizeof(DP));
+
+	long long result = 0;
+	for (int i = 1; i <= 9; i++) {
+		num[0] = i;
+		result += getAnswer(1, 1 << i);
+	}
+
+	cout << result % DIV << endl;
+}
+
+long long getAnswer(int deep, int bit) {
+	if (deep == N) {
+		if (bit == ALL)
+			return 1;
+		return 0;
+	}
+	if (DP[deep][num[deep - 1]][bit] != -1)
+		return DP[deep][num[deep - 1]][bit];
+
+	long long result = 0;
+	if (num[deep - 1] != 0) {
+		num[deep] = num[deep - 1] - 1;
+		int b = bit & (1 << num[deep]) ? bit : bit + (1 << num[deep]);
+		result += getAnswer(deep + 1, b);
+	}
+	if (num[deep - 1] != 9) {
+		num[deep] = num[deep - 1] + 1;
+		int b = bit & (1 << num[deep]) ? bit : bit + (1 << num[deep]);
+		result += getAnswer(deep + 1, b);
+	}
+
+	return DP[deep][num[deep-1]][bit] = result % DIV;
+}
+
+
This post is licensed under CC BY 4.0 by the author.

1509 팰린드롬 분할

1562 계단수

Comments powered by Disqus.

diff --git a/posts/1522/index.html b/posts/1522/index.html new file mode 100644 index 000000000..69b9dc221 --- /dev/null +++ b/posts/1522/index.html @@ -0,0 +1,69 @@ + 1522 문자열 교환 | 디피의 개발일지
Posts 1522 문자열 교환
Post
Cancel

1522 문자열 교환

알고리즘

  • 문자열에서 a의 개수를 ac 라고 할때, 시작점을 문자열의 맨처음부터 맨 끝까지 이동하면서, 길이가 ac인 문자열을 안에 b가 몇개있는지 검사한다.
  • 이때 검사한 b의 개수 중 최소가 필요한 최소의 교환횟수
  • 쉽게 말해서, 길이가 ac인 문자열 안에 b를 최소로 포함한 문자열을 a로 채우는 문제로 치환한 것이다.

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+
#include <iostream>
+
+using namespace std;
+
+string str;
+int ans = 2000000000;
+
+int min(int a,int b) {
+	return a < b ? a : b;
+}
+
+void solve(int s, int length) {
+	int bc = 0;
+	for (int i = s; i < s + length; i++) {
+		if (str[i % str.size()] == 'b')
+			bc++;
+	}
+	ans = min(ans, bc);
+}
+
+int main() {
+	cin >> str;
+
+	int ac = 0;
+	for (int i = 0; i < str.size(); i++) {
+		if (str[i] == 'a')
+			ac++;
+	}
+	for (int i = 0; i < str.size(); i ++) {
+		solve(i, ac);
+	}
+
+	cout << ans;
+}
+
This post is licensed under CC BY 4.0 by the author.

network 개요

운영체제 개요

Comments powered by Disqus.

diff --git a/posts/15486/index.html b/posts/15486/index.html new file mode 100644 index 000000000..5df7d7421 --- /dev/null +++ b/posts/15486/index.html @@ -0,0 +1,71 @@ + 15486 퇴사2 | 디피의 개발일지
Posts 15486 퇴사2
Post
Cancel

15486 퇴사2

15486 퇴사2

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+
#include <iostream>
+
+using namespace std;
+
+int N, T[1500000], P[1500000], DP[1500000];
+
+int dp(int deep);
+int max(int a, int b) {
+	return a > b ? a : b;
+}
+int main() {
+	ios::sync_with_stdio(0);
+	cin.tie(0);
+	cin >> N;
+
+	for (int i = 0; i < N; i++) {
+		cin >> T[i] >> P[i];
+	}
+
+	cout << dp(0);
+}
+
+int dp(int deep) {
+	if (deep >= N)
+		return 0;
+	if (DP[deep] != 0)
+		return DP[deep];
+
+	int result = 0;
+	if (deep + T[deep] <= N)
+		result = dp(deep + T[deep]) + P[deep];
+	result = max(result, dp(deep + 1));
+
+	return DP[deep] = result;
+}
+
This post is licensed under CC BY 4.0 by the author.

14891 Samsung sw test

15683 Samsung sw test

Comments powered by Disqus.

diff --git a/posts/1562/index.html b/posts/1562/index.html new file mode 100644 index 000000000..ceb51bedb --- /dev/null +++ b/posts/1562/index.html @@ -0,0 +1,109 @@ + 1562 계단수 | 디피의 개발일지
Posts 1562 계단수
Post
Cancel

1562 계단수

1562 계단수

알고리즘(DP, 비트마스킹)

1
+2
+3
+4
+5
+6
+
1. 숫자를 기록하는 num[101] 배열과, DP[101][10][1 << 10] 배열을 이용하여 DP로 품
+2. 앞에서부터 길이에 맞게 숫자를 하나하나 기록해감. 기록하면서 비트마스킹으로 0~9가 있는지 표시
+	-> 이때 이미 표시된 비트에 또 표시하면 기록이 망가짐. 따라서 & 연산자로 검사하고 넣음
+		ex) int b = bit & (1 << num[deep]) ? bit : bit + (1 << num[deep]);
+3. 결과값은 DP[deep][num[deep-1]][bit] 에 기록하여 DP를 구현
+	(num[deep-1] 인 이유는 내 구현에선 현재 deep에 기록을 하고 다음 deep으로 넘기므로 num[deep]으로 하면 기록이 망가진다.)
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+
#include <bits/stdc++.h>
+#define MAX_LENGTH 100
+#define DIV 1000000000
+#define ALL 1023
+
+using namespace std;
+
+int N, num[101];
+long long DP[101][10][1 << 10];
+
+long long getAnswer(int, int);
+
+int main() {
+	cin >> N;
+	memset(DP, -1, sizeof(DP));
+
+	long long result = 0;
+	for (int i = 1; i <= 9; i++) {
+		num[0] = i;
+		result += getAnswer(1, 1 << i);
+	}
+
+	cout << result % DIV << endl;
+}
+
+long long getAnswer(int deep, int bit) {
+	if (deep == N) {
+		if (bit == ALL)
+			return 1;
+		return 0;
+	}
+	if (DP[deep][num[deep - 1]][bit] != -1)
+		return DP[deep][num[deep - 1]][bit];
+
+	long long result = 0;
+	if (num[deep - 1] != 0) {
+		num[deep] = num[deep - 1] - 1;
+		int b = bit & (1 << num[deep]) ? bit : bit + (1 << num[deep]);
+		result += getAnswer(deep + 1, b);
+	}
+	if (num[deep - 1] != 9) {
+		num[deep] = num[deep - 1] + 1;
+		int b = bit & (1 << num[deep]) ? bit : bit + (1 << num[deep]);
+		result += getAnswer(deep + 1, b);
+	}
+
+	return DP[deep][num[deep-1]][bit] = result % DIV;
+}
+
This post is licensed under CC BY 4.0 by the author.

1516 Developing game

1644 소수의 연속합

Comments powered by Disqus.

diff --git "a/posts/15653-\352\265\254\354\212\254\355\203\210\354\266\2344/index.html" "b/posts/15653-\352\265\254\354\212\254\355\203\210\354\266\2344/index.html" new file mode 100644 index 000000000..f6755979d --- /dev/null +++ "b/posts/15653-\352\265\254\354\212\254\355\203\210\354\266\2344/index.html" @@ -0,0 +1,241 @@ + 115653 구슬탈출4 | 디피의 개발일지
Posts 115653 구슬탈출4
Post
Cancel

115653 구슬탈출4

알고리즘

  • 푸는 방법은 bfs지만, 구현이 더 까다로운 문제이다.
  • visit은 빨간구슬, 파란구슬의 위치를 동시에 기록하여 두 구슬 모두 같은 자리로 다시 가지 않도록 한다.
  • 각 방향으로 기우릴때는 일단 한쪽을 먼저 보낸다고 가정하고, 실제로 먼저 보내는지 검사한 후 아니라면 다른쪽을 먼저 보낸다.
  • 한쪽이 구멍에 먼저 빠지면, (-1,-1) 위치로 보내어 충돌이 발생하지 않도록 한다.

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+
#include <iostream>
+#include <list>
+#define INF 2000000000;
+
+using namespace std;
+
+struct node {
+	pair<int, int> r;
+	pair<int, int> b;
+	int d;
+};
+
+int map[10][10], n, m, os[4][2] = { {-1,0}, {0,1}, {1, 0}, {0,-1} };
+pair<int, int> h, r, b;
+bool visit[10][10][10][10];
+list<node> l;
+
+int bfs();
+int min(int a, int b) {
+	return a < b ? a : b;
+}
+
+int main() {
+	cin >> n >> m;
+	for (int i = 0; i < n; i++) {
+		string a;
+		cin >> a;
+		for (int j = 0; j < m; j++) {
+			map[i][j] = a[j];
+			switch (map[i][j]) {
+			case 'R':
+				r = { i,j };
+				break;
+			case 'B':
+				b = { i,j };
+				break;
+			case 'O':
+				h = { i,j };
+			}
+		}
+	}
+
+	cout << bfs();
+}
+
+int bfs() {
+	visit[r.first][r.second][b.first][b.second] = true;
+	l.push_back({ {r.first, r.second}, {b.first, b.second}, 0 });
+
+	int ans = INF;
+	while (!l.empty()) {
+		node cur = l.front();
+		l.pop_front();
+
+		for (int i = 0; i < 4; i++) {
+			int rr = cur.r.first, rc = cur.r.second, br = cur.b.first, bc = cur.b.second, nd = cur.d + 1;
+			bool rf = true;
+			if (i % 2 == 0 && rc == bc) {
+				if (i == 0 && rr > br)
+					rf = false;
+				else if (i == 2 && rr < br)
+					rf = false;
+			}
+			else if (i % 2 == 1 && rr == br) {
+				if (i == 1 && bc > rc)
+					rf = false;
+				else if (i == 3 && bc < rc)
+					rf = false;
+			}
+			bool rh = false, bh = false;
+			if (rf) {
+				while (map[rr + os[i][0]][rc + os[i][1]] != '#') {
+					rr += os[i][0];
+					rc += os[i][1];
+					if (map[rr][rc] == 'O') {
+						rh = true;
+						rr = rc = -1;
+						break;
+					}
+				}
+				while (map[br + os[i][0]][bc + os[i][1]] != '#' && (br + os[i][0] != rr|| bc + os[i][1] != rc)) {
+					br += os[i][0];
+					bc += os[i][1];
+					if (map[br][bc] == 'O') {
+						bh = true;
+						break;
+					}
+				}
+			}
+			else {
+				while (map[br + os[i][0]][bc + os[i][1]] != '#') {
+					br += os[i][0];
+					bc += os[i][1];
+					if (map[br][bc] == 'O') {
+						bh = true;
+						br = bc = -1;
+						break;
+					}
+				}
+				while (map[rr + os[i][0]][rc + os[i][1]] != '#' && (rr + os[i][0] != br || rc + os[i][1] != bc)) {
+					rr += os[i][0];
+					rc += os[i][1];
+					if (map[rr][rc] == 'O') {
+						rh = true;
+						break;
+					}
+				}
+			}
+
+			if (rh && !bh)
+				ans = min(ans, nd);
+			else if (!rh && !bh && !visit[rr][rc][br][bc]) {
+				visit[rr][rc][br][bc] = true;
+				l.push_back({ {rr, rc}, {br, bc}, nd });
+			}
+		}
+	}
+
+	return ans == 2000000000 ? -1 : ans;
+}
+
This post is licensed under CC BY 4.0 by the author.

12872 플레이리스트

1504 특정한 최단경로

Comments powered by Disqus.

diff --git a/posts/15683/index.html b/posts/15683/index.html new file mode 100644 index 000000000..0fe2d01c4 --- /dev/null +++ b/posts/15683/index.html @@ -0,0 +1,363 @@ + 15683 Samsung sw test | 디피의 개발일지
Posts 15683 Samsung sw test
Post
Cancel

15683 Samsung sw test

15683 Samsung sw test

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+
#include <iostream>
+#include <cstring>
+#define MAX_WIDTH 8
+#define WALL 6
+#define BLINDSPOT 0
+
+using namespace std;
+
+typedef struct CCTV {
+	int row;
+	int col;
+	int direction;	// 1 : 북, 2 : 동, 3 : 남, 4 : 서
+	int num;
+} CCTV;
+
+int height, width;
+int map[MAX_WIDTH][MAX_WIDTH];
+int tempMap[MAX_WIDTH][MAX_WIDTH];
+CCTV cctv[MAX_WIDTH];
+int numOfCctv;
+
+int minBlindSpot(CCTV c[MAX_WIDTH], int len = 0);
+int calcBlindSpot(CCTV c[MAX_WIDTH]);
+void  up(int i, int j);
+void  right(int i, int j);
+void  down(int i, int j);
+void left(int i, int j);
+
+int main() {
+	cin >> height >> width;
+	int index = 0;
+	for (int i = 0; i < height; i++)
+		for (int j = 0; j < width; j++) {
+			int temp;
+			cin >> temp;
+			map[i][j] = temp;
+			if (temp != 0 && temp != 6) {
+				cctv[index].row = i;
+				cctv[index].col = j;
+				cctv[index].direction = 0;
+				cctv[index++].num = temp;
+			}
+		}
+	numOfCctv = index;
+	cout << minBlindSpot(cctv);
+}
+
+int minBlindSpot(CCTV c[MAX_WIDTH], int len) {
+	if (len == numOfCctv)
+		return calcBlindSpot(c);
+
+	int temp[4];
+	int last = c[len].num != 1 ? c[len].num != 2 ? c[len].num != 3 ? c[len].num != 4 ? 1 : 4 : 4 : 2 : 4;
+	for (int i = 1; i <= last; i++) {
+		c[len].direction = i;
+		temp[i-1] = minBlindSpot(c, len + 1);
+	}
+
+	int min = temp[0];
+	for (int i = 1; i < last; i++)
+		min = temp[i] < min ? temp[i] : min;
+
+	return min;
+}
+
+int calcBlindSpot(CCTV c[MAX_WIDTH]) {
+	for (int i = 0; i < height; i++)
+		memcpy(tempMap[i], map[i], sizeof(int) * width);
+
+	for (int i = 0; i < numOfCctv; i++) {
+		if (c[i].num == 5) {
+			up(c[i].row, c[i].col);
+			right(c[i].row, c[i].col);
+			down(c[i].row, c[i].col);
+			left(c[i].row, c[i].col);
+		}
+		else if (c[i].num == 2) {
+			if (c[i].direction == 1) {
+				right(c[i].row, c[i].col);
+				left(c[i].row, c[i].col);
+			}
+			else if (c[i].direction == 2) {
+				up(c[i].row, c[i].col);
+				down(c[i].row, c[i].col);
+			}
+		}
+		else {
+			if (c[i].num == 1) {
+				if (c[i].direction == 1)
+					up(c[i].row, c[i].col);
+				else if (c[i].direction == 2)
+					right(c[i].row, c[i].col);
+				else if (c[i].direction == 3)
+					down(c[i].row, c[i].col);
+				else if (c[i].direction == 4)
+					left(c[i].row, c[i].col);
+			}
+			else if (c[i].num == 3) {
+				if (c[i].direction == 1) {
+					up(c[i].row, c[i].col);
+					right(c[i].row, c[i].col);
+				}
+				else if (c[i].direction == 2) {
+					right(c[i].row, c[i].col);
+					down(c[i].row, c[i].col);
+				}
+				else if (c[i].direction == 3) {
+					down(c[i].row, c[i].col);
+					left(c[i].row, c[i].col);
+				}
+				else if (c[i].direction == 4) {
+					left(c[i].row, c[i].col);
+					up(c[i].row, c[i].col);
+				}
+			}
+			else if (c[i].num == 4) {
+				if (c[i].direction == 1) {
+					up(c[i].row, c[i].col);
+					right(c[i].row, c[i].col);
+					left(c[i].row, c[i].col);
+				}
+				else if (c[i].direction == 2) {
+					up(c[i].row, c[i].col);
+					right(c[i].row, c[i].col);
+					down(c[i].row, c[i].col);
+				}
+				else if (c[i].direction == 3) {
+					left(c[i].row, c[i].col);
+					right(c[i].row, c[i].col);
+					down(c[i].row, c[i].col);
+				}
+				else if (c[i].direction == 4) {
+					left(c[i].row, c[i].col);
+					down(c[i].row, c[i].col);
+					up(c[i].row, c[i].col);
+				}
+			}
+		}
+	}
+
+	int count = 0;
+	for (int i = 0; i < height; i++)
+		for (int j = 0; j < width; j++)
+			if (tempMap[i][j] == 0)
+				count++;
+
+	return count;
+}
+
+void up(int i, int j) {
+	for (int k = i - 1; k >= 0; k--) {
+		if (tempMap[k][j] != 6)
+			tempMap[k][j] = -1;
+		else
+			break;
+	}
+}
+void right(int i, int j) {
+	for (int k = j + 1; k < width; k++) {
+		if (tempMap[i][k] != 6)
+			tempMap[i][k] = -1;
+		else
+			break;
+	}
+}
+void down(int i, int j) {
+	for (int k = i+ 1; k < height; k++) {
+		if (tempMap[k][j] != 6)
+			tempMap[k][j] = -1;
+		else
+			break;
+	}
+}
+void left(int i, int j) {
+	for (int k = j - 1; k >= 0; k--) {
+		if (tempMap[i][k] != 6)
+			tempMap[i][k] = -1;
+		else
+			break;
+	}
+}
+
This post is licensed under CC BY 4.0 by the author.

15486 퇴사2

15684 Samsung sw test

Comments powered by Disqus.

diff --git a/posts/15684/index.html b/posts/15684/index.html new file mode 100644 index 000000000..400096fa5 --- /dev/null +++ b/posts/15684/index.html @@ -0,0 +1,251 @@ + 15684 Samsung sw test | 디피의 개발일지
Posts 15684 Samsung sw test
Post
Cancel

15684 Samsung sw test

15684 Samsung sw test

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+
#include <iostream>
+#define MAX_VERTICAL 10
+#define MAX_HORIZONTAL 30
+#define MAX_LINE 270
+
+using namespace std;
+
+typedef struct Ladder {
+	bool left;
+	bool right;
+} Ladder;
+typedef struct Location {
+	int row;
+	int col;
+} Location;
+
+Ladder ladder[MAX_HORIZONTAL][MAX_VERTICAL];
+int numOfVertical, numOfHorizontal, numOfLine;
+
+int getMinLine();
+bool downLadder();
+
+int main() {
+	cin >> numOfVertical >> numOfLine >> numOfHorizontal;
+	for (int i = 0; i < numOfLine; i++) {
+		int temp1, temp2;
+		cin >> temp1 >> temp2;
+
+		ladder[temp1 - 1][temp2 - 1].right = true;
+		ladder[temp1 - 1][temp2].left = true;
+	}
+
+	cout << getMinLine();
+}
+
+int getMinLine() {
+	//0개 추가
+	if (downLadder())
+		return 0;
+
+	Location location[MAX_HORIZONTAL * MAX_VERTICAL];
+	int index = 0;
+	for (int height = 0; height < numOfHorizontal; height++) {
+		for (int width = 0; width < numOfVertical - 1; width++) {
+			if (!ladder[height][width].right && !ladder[height][width].left) {
+				location[index].row = height;
+				location[index++].col = width;
+			}
+		}
+	}
+
+	//1개 추가
+	for (int i = 0; i < index; i++) {
+		ladder[location[i].row][location[i].col].right = true;
+		ladder[location[i].row][location[i].col + 1].left = true;
+		if (downLadder())
+			return 1;
+		ladder[location[i].row][location[i].col].right = false;
+		ladder[location[i].row][location[i].col+1].left = false;
+	}
+
+	//2개 추가
+	for (int i = 0; i < index-1; i++) {
+		ladder[location[i].row][location[i].col].right = true;
+		ladder[location[i].row][location[i].col + 1].left = true;
+		for (int j = i+1; j < index; j++) {
+			if (location[i].row == location[j].row && location[i].col + 1 == location[j].col)
+				continue;
+			ladder[location[j].row][location[j].col].right = true;
+			ladder[location[j].row][location[j].col + 1].left = true;
+			if (downLadder())
+				return 2;
+			ladder[location[j].row][location[j].col].right = false;
+			ladder[location[j].row][location[j].col + 1].left = false;
+		}
+		ladder[location[i].row][location[i].col].right = false;
+		ladder[location[i].row][location[i].col+1].left = false;
+	}
+
+
+	//3개 추가
+	for (int i = 0; i < index - 2; i++) {
+		ladder[location[i].row][location[i].col].right = true;
+		ladder[location[i].row][location[i].col + 1].left = true;
+		for (int j = i + 1; j < index-1; j++) {
+			if (location[i].row == location[j].row && location[i].col + 1 == location[j].col)
+				continue;
+			ladder[location[j].row][location[j].col].right = true;
+			ladder[location[j].row][location[j].col + 1].left = true;
+			for (int k = j + 1; k < index; k++) {
+				if(location[j].row == location[k].row && location[j].col + 1 == location[k].col)
+					continue;
+				ladder[location[k].row][location[k].col].right = true;
+				ladder[location[k].row][location[k].col + 1].left = true;
+				if (downLadder())
+					return 3;
+				ladder[location[k].row][location[k].col].right = false;
+				ladder[location[k].row][location[k].col + 1].left = false;
+			}
+			ladder[location[j].row][location[j].col].right = false;
+			ladder[location[j].row][location[j].col + 1].left = false;
+		}
+		ladder[location[i].row][location[i].col].right = false;
+		ladder[location[i].row][location[i].col + 1].left = false;
+	}
+
+
+	//그외
+	return -1;
+}
+
+bool downLadder() {
+	for (int start = 0; start < numOfVertical; start++) {
+		int current = start;
+		for (int height = 0; height < numOfHorizontal; height++) {
+			if (ladder[height][current].left)
+				current--;
+			else if (ladder[height][current].right)
+				current++;
+		}
+		if (start != current)
+			return false;
+	}
+	return true;
+}
+
This post is licensed under CC BY 4.0 by the author.

15683 Samsung sw test

15685 Samsung sw test

Comments powered by Disqus.

diff --git a/posts/15685/index.html b/posts/15685/index.html new file mode 100644 index 000000000..704912ded --- /dev/null +++ b/posts/15685/index.html @@ -0,0 +1,145 @@ + 15685 Samsung sw test | 디피의 개발일지
Posts 15685 Samsung sw test
Post
Cancel

15685 Samsung sw test

15685 Samsung sw test

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+
#include <iostream>
+#include <vector>
+#define MAP 101
+#define MAX_DRAGON 20
+#define MAX_GENERATION 10
+
+using namespace std;
+
+typedef struct Location {
+	int row;
+	int col;
+}Location;
+
+bool map[MAP][MAP];
+int dragon[MAX_DRAGON][4];
+int numOfdragon;
+
+void dragonCurb();
+void singleDragonCurb(int x, int y, int direction, int generation);
+int checkCell();
+
+int main() {
+	cin >> numOfdragon;
+	for (int i = 0; i < numOfdragon; i++)
+		cin >> dragon[i][0] >> dragon[i][1] >> dragon[i][2] >> dragon[i][3];
+
+	dragonCurb();
+	cout << checkCell();
+}
+
+void dragonCurb() {
+	for (int i = 0; i < numOfdragon; i++)
+		singleDragonCurb(dragon[i][0], dragon[i][1], dragon[i][2], dragon[i][3]);
+}
+
+void singleDragonCurb(int x, int y, int direction, int generation) {
+	vector<Location> v;
+	v.push_back({ y, x });
+	if (direction == 0)	v.push_back({ y, x + 1 });
+	else if (direction == 1) v.push_back({ y - 1, x });
+	else if (direction == 2) v.push_back({ y, x - 1 });
+	else if (direction == 3) v.push_back({ y + 1, x });
+
+	for (int i = 0; i < generation; i++) {
+		for (int j = v.size() - 2; j >= 0; j--) {
+			int rowD = v[j].row - v[j + 1].row;
+			int colD = v[j].col - v[j + 1].col;
+			if (rowD == 1)		//이전보다 아래
+				v.push_back({ v.back().row, v.back().col - 1 });
+			else if (rowD == -1)	//이번보다 위
+				v.push_back({ v.back().row, v.back().col + 1 });
+			else if (colD == 1)
+				v.push_back({ v.back().row + 1, v.back().col });
+			else if (colD == -1)
+				v.push_back({ v.back().row -1, v.back().col });
+		}
+	}
+
+	for (int i = 0; i < v.size(); i++)
+		map[v[i].row][v[i].col] = true;
+}
+
+int checkCell() {
+	int count = 0;
+	for (int i = 0; i < MAP-1; i++) {
+		for (int j = 0; j < MAP-1; j++) {
+			if (map[i][j] && map[i + 1][j] && map[i][j + 1] && map[i + 1][j + 1])
+				count++;
+		}
+	}
+	return count;
+}
+
This post is licensed under CC BY 4.0 by the author.

15684 Samsung sw test

15686 Samsung sw test

Comments powered by Disqus.

diff --git a/posts/15686/index.html b/posts/15686/index.html new file mode 100644 index 000000000..2fa0c1b9b --- /dev/null +++ b/posts/15686/index.html @@ -0,0 +1,125 @@ + 15686 Samsung sw test | 디피의 개발일지
Posts 15686 Samsung sw test
Post
Cancel

15686 Samsung sw test

15686 Samsung sw test

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+
#include <iostream>
+#define MAX_WIDTH 50
+#define MAX_CHIKEN 13
+
+using namespace std;
+
+typedef struct Location {
+	int row;
+	int col;
+} Location;
+
+int width, maxChiken, numOfHouse, numOfChiken;
+Location house[2 * MAX_WIDTH];
+Location chiken[MAX_CHIKEN];
+int minDistance = 10000000;
+int index[MAX_CHIKEN];
+
+void findMinChikenDistance(int deep = 0, int len = 0);
+
+int main() {
+	cin >> width >> maxChiken;
+	for (int i = 0; i < width; i++) {
+		for (int j = 0; j < width; j++) {
+			int temp;
+			cin >> temp;
+			if (temp == 1)
+				house[numOfHouse++] = { i, j };
+			else if (temp == 2)
+				chiken[numOfChiken++] = { i, j };
+		}
+	}
+
+
+	findMinChikenDistance();
+	cout << minDistance;
+}
+
+void findMinChikenDistance(int deep, int len) {
+	if (len == maxChiken) {
+		//검사
+		int distance = 0;
+		for (int h = 0; h < numOfHouse; h++) {
+			int min = 100000000;
+			for (int c = 0; c < maxChiken; c++) {
+				int rowD = house[h].row - chiken[index[c]].row;
+				int colD = house[h].col - chiken[index[c]].col;
+				rowD = rowD < 0 ? (-1) * rowD : rowD;
+				colD = colD < 0 ? (-1) * colD : colD;
+				min = (rowD + colD) < min ? (rowD + colD) : min;
+			}
+			distance += min;
+		}
+		minDistance = distance < minDistance ? distance : minDistance;
+		return;
+	}
+	if (numOfChiken - deep < maxChiken - len)
+		return;
+
+	index[len] = deep;
+	findMinChikenDistance(deep + 1, len + 1);
+	findMinChikenDistance(deep + 1, len);
+}
+
This post is licensed under CC BY 4.0 by the author.

15685 Samsung sw test

16234 Samsung sw test

Comments powered by Disqus.

diff --git a/posts/15971/index.html b/posts/15971/index.html new file mode 100644 index 000000000..64b7c7ca3 --- /dev/null +++ b/posts/15971/index.html @@ -0,0 +1,119 @@ + 15971 두 로봇 | 디피의 개발일지
Posts 15971 두 로봇
Post
Cancel

15971 두 로봇

알고리즘

  1. BFS로 시작 정점부터, 이제까지 경로 중에 있었던 가장 큰 weight(bigest)와 이제까지의 weight를 모두 합한 값(length)을 저장해나가며 진행.
  2. 이때 visit을 표시할 때, true/false 값이 아닌, length을 저장.
  3. BFS를 진행할 땐, node에 저장된 length와 현재 로봇의 length와 그 통로의 weight를 합한 값이 작을때만 진행.
  4. 3을 위해서는 visit 배열을 큰 값으로 초기화해야함.

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+
#include <bits/stdc++.h>
+
+using namespace std;
+
+struct robot {
+	int bigest;
+	int cur;
+	int length;
+};
+
+vector<pair<int,int>> edges[100010];
+int n, a, b;
+int nodes[100010];
+list<robot> q;
+
+int BFS();
+
+int main() {
+	ios::sync_with_stdio(0);
+	cin.tie(0);
+	cin >> n >> a >> b;
+	for (int i = 0; i < n-1; i++) {
+		int t1, t2, t3;
+		cin >> t1 >> t2 >> t3;
+		edges[t1].push_back({ t2, t3 });
+		edges[t2].push_back({ t1, t3 });
+	}
+	for (int i = 1; i <=n; i++) {
+		nodes[i] = 2000000000;
+	}
+	cout << BFS() << "\n";
+}
+
+int BFS() {
+	q.push_back({ 0, a, 0});
+	nodes[a] = 0;
+	int result = 2000000000;
+
+	while (!q.empty()) {
+		int cur = q.front().cur, bigest = q.front().bigest, length = q.front().length;
+		q.pop_front();
+
+		if (cur == b) {
+			result = min(result, length - bigest);
+			continue;
+		}
+
+		for (int i = 0; i < edges[cur].size(); i++) {
+			int to = edges[cur][i].first, w = edges[cur][i].second;
+			if (length + w < nodes[to]) {
+				int bigger = max(bigest, w);
+				nodes[to] = length + w;
+				q.push_back({ bigger, to, nodes[to] });
+			}
+		}
+	}
+
+	return result;
+}
+
This post is licensed under CC BY 4.0 by the author.

Firebase/Function firestore, storage 쓰기, 읽기

2437 저울

Comments powered by Disqus.

diff --git "a/posts/15989-1,2,3-\353\215\224\355\225\230\352\270\260-4/index.html" "b/posts/15989-1,2,3-\353\215\224\355\225\230\352\270\260-4/index.html" new file mode 100644 index 000000000..cbd66bdee --- /dev/null +++ "b/posts/15989-1,2,3-\353\215\224\355\225\230\352\270\260-4/index.html" @@ -0,0 +1,57 @@ + 15989 1,2,3 더하기 4 | 디피의 개발일지
Posts 15989 1,2,3 더하기 4
Post
Cancel

15989 1,2,3 더하기 4

알고리즘

  1. save[0] = 1로 초기화
  2. i : 1~3 에서 j : i~m 까지 save[j] += save[j-i] 를 반복
  • 원리

    • i = 1에서는 1로만 쭉 더한 것(3 = 1 + 1 + 1) 경우만 따짐.

    • i = 2에서는 1로만 더한 것에 2를 추가한 경우를 따진다.

    • i = 3에서도 마찬가지.

    • ex) m = 4일때

      • i = 1

        1 = 1

        2 = 1 + 1

        3 = 1 + 1 + 1

        4 = 1 + 1 + 1 + 1

      • i = 2

        1 = 1

        2 = 1 + 1 / 2

        3 = 1 + 1 + 1 / 1 + 2

        4 = 1 + 1 + 1 + 1 / 1 + 1 + 2 / 2 + 2

      • i = 3

        1 = 1

        2 = 1 + 1 / 2

        3 = 1 + 1 + 1 / 1 + 2 / 3

        4 = 1 + 1 + 1 + 1 / 1 + 1 + 2 / 2 + 2 / 1 + 3

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+
#include <iostream>
+#include <cstring>
+
+using namespace std;
+
+int n, m;
+long long save[10010];
+
+long long dp();
+
+int main() {
+	cin >> n;
+	while (n--) {
+		cin >> m;
+		cout << dp() << endl;
+		memset(save, 0, sizeof(save));
+	}
+}
+
+long long dp() {
+	save[0] = 1;
+
+	for (int i = 1; i <= 3; i++)
+		for (int j = i; j <= m; j++)
+			save[j] += save[j - i];
+
+	return save[m];
+}
+
This post is licensed under CC BY 4.0 by the author.

1890 점프

12872 플레이리스트

Comments powered by Disqus.

diff --git "a/posts/1613-\354\227\255\354\202\254/index.html" "b/posts/1613-\354\227\255\354\202\254/index.html" new file mode 100644 index 000000000..2303f376e --- /dev/null +++ "b/posts/1613-\354\227\255\354\202\254/index.html" @@ -0,0 +1,101 @@ + 1613 역사 | 디피의 개발일지
Posts 1613 역사
Post
Cancel

1613 역사

알고리즘

BFS

  • 입력으로 들어온, 알려진 전후관계로 그래프를 만듦.

  • 체크를 해야하는 원소 두개가 들어오면,
    • 먼저 첫번째 거에서 bfs를 시작하여 두번째거가 나오면 첫번째가 앞에 있는 것이므로 -1를 출력
    • 찾지못하면, 두번째에서 첫번째를 BFS로 찾음. 찾으면 1을 출력.
    • 찾지못하면, 0 을 출력
  • 시간 복잡도 : O(NM)
    • 사건의 개수 - N, 찾아야하는 선후관계 - M
    • N은 최대 400, M은 최대 50000임. 대략 2천만번의 연산 필요.
      • 하지만 첫번째에서 두번째/ 두번째에서 첫번째로 총 2번 검사하여야하고, 매 bfs 마다 400개 사건에 대한 visit 배열을 초기화하여야함.
      • 따라서 대략 8천만번이라고 보는 것이 합당함.

플로이드 워셜

  • 플로이드 워셜 알고리즘을 사용하여, 한 사건에서 다른 사건으로 가는 최소 경로를 찾음. -> map[][]에 저장
  • 체크를 해야하는 원소 a, b가 들어오면,
    • map[a][b]가 있으면,-1 출력
    • map[b][a] 가 있으면, 1 출력
    • 둘다 없으면 0 출력
  • 시간 복잡도 : N^3 + M*O(1) = O(N^3 + M)
    • N 은 최대 400개, M은 최대 50000이므로, 대략 6천4백만번의 연산 필요

어느게 빠를까?

  • 플로이드 워셜 방법이 압도적으로 빨랐다.
    • BFS - 1000ms, 플로이드 워셜 - 80ms
  • 연산 횟수는 두 방법에서 크게 차이나지 않았으나, bfs 방법에선 그래프를 만드는데 vector를 사용하였고, bfs를 구현하는데 queue 를 사용하였다.
  • 하지만 플로이드 워셜 방법은 다른 라이브러리의 도움 없이 구현할 수 있었다.
  • 이러한 차이로 플로이드 워셜 방법이 압도적으로 빨랐다고 생각된다.

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+
#include <iostream>
+#define INF 1000000000
+using namespace std;
+
+int n, k, s, map[401][401];
+
+int min(int a, int b) {
+	return a < b ? a : b;
+}
+
+int main() {
+	ios::sync_with_stdio(0);
+	cin.tie(0);
+	cout.tie(0);
+
+	for (int i = 0; i <= 400; i++) {
+		for (int j = 0; j <= 400; j++) {
+			if(i != j)	
+				map[i][j] = INF;
+		}
+	}
+
+	int a, b;
+	cin >> n >> k;
+	for (int i = 0; i < k; i++) {
+		cin >> a >> b;
+		map[a][b] = 1;
+	}
+
+	for (int i = 1; i <= n; i++) {
+		for (int j = 1; j <= n; j++) {
+			for (int k = 1; k <= n; k++) {
+				map[j][k] = min(map[j][k], map[j][i] + map[i][k]);
+			}
+		}
+	}
+
+
+	cin >> s;
+	for (int i = 0; i < s; i++) {
+		cin >> a >> b;
+		if (map[a][b] != INF)
+			cout << -1 << "\n";
+		else if (map[b][a] != INF)
+			cout << 1 << "\n";
+		else
+			cout << 0 << "\n";
+	}
+}
+
+
This post is licensed under CC BY 4.0 by the author.

react 작동원리부터 tailwindcss 사용까지

4485 젤다

Comments powered by Disqus.

diff --git a/posts/16234/index.html b/posts/16234/index.html new file mode 100644 index 000000000..ef53a147b --- /dev/null +++ b/posts/16234/index.html @@ -0,0 +1,319 @@ + 16234 Samsung sw test | 디피의 개발일지
Posts 16234 Samsung sw test
Post
Cancel

16234 Samsung sw test

16234 Samsung sw test

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+
#include <iostream>
+#include <list>
+#include <cstring>
+#define MAX_WIDTH 50
+
+using namespace std;
+
+typedef struct Location {
+	int row;
+	int col;
+} Location;
+
+int map[MAX_WIDTH][MAX_WIDTH];
+int tempMap[MAX_WIDTH][MAX_WIDTH];
+int width, lowerBound, upperBound;
+bool visit[MAX_WIDTH][MAX_WIDTH];
+
+int getCountOfImmigration();
+bool checkNeibor(Location l, int way);
+void movePeople(Location l);
+
+int main() {
+	cin >> width >> lowerBound >> upperBound;
+	for (int i = 0; i < width; i++)
+		for (int j = 0; j < width; j++)
+			cin>>map[i][j];
+
+	cout << getCountOfImmigration();
+}
+
+int getCountOfImmigration() {
+	int count = 0;
+	bool check = true;
+	for (int i = 0; i < width; i++)
+		memcpy(tempMap[i], map[i], sizeof(int) * width);
+
+	while (check) {
+		check = false;
+		bool check2 = false;
+		for (int i = 0; i < width; i++)
+			memset(visit[i], 0, sizeof(bool) * width);
+
+		for (int i = 0; i < width; i++)
+			for (int j = 0; j < width; j++) {
+				if (visit[i][j])
+					continue;
+				if (i > 0 && checkNeibor({ i, j }, 0)) {
+					movePeople({ i,j });
+					count += check2 ? 0 : 1;
+					check = true;
+					check2 = true;
+				}
+				else if (j < width -1 && checkNeibor({ i, j }, 1)) {
+					movePeople({ i,j });
+					count += check2 ? 0 : 1;
+					check = true;
+					check2 = true;
+				}
+				else if (i < width - 1 && checkNeibor({ i, j }, 2)) {
+					movePeople({ i,j });
+					count += check2 ? 0 : 1;
+					check = true;
+					check2 = true;
+				}
+				else if (j > 0 && checkNeibor({ i, j }, 3)) {
+					movePeople({ i,j });
+					count += check2 ? 0 : 1;
+					check = true;
+					check2 = true;
+				}
+			}
+
+		for (int i = 0; i < width; i++)
+			memcpy(map[i], tempMap[i], sizeof(int) * width);
+
+	}
+
+	return count;
+}
+
+
+bool checkNeibor(Location l, int way) {
+	if (l.row > 0 && way == 0) {
+		int d = map[l.row][l.col] - map[l.row - 1][l.col];
+		d = d < 0 ? (-1) * d : d;
+		if (d >= lowerBound && d <= upperBound)
+			return true;
+	}
+	else if (l.row < width - 1 && way == 2) {
+		int d = map[l.row][l.col] - map[l.row + 1][l.col];
+		d = d < 0 ? (-1) * d : d;
+		if (d >= lowerBound && d <= upperBound)
+			return true;
+	}
+	else if (l.col > 0 && way == 3) {
+		int d = map[l.row][l.col] - map[l.row][l.col - 1];
+		d = d < 0 ? (-1) * d : d;
+		if (d >= lowerBound && d <= upperBound)
+			return true;
+	}
+	else if (l.col < width - 1 && way == 1) {
+		int d = map[l.row][l.col] - map[l.row][l.col + 1];
+		d = d < 0 ? (-1) * d : d;
+		if (d >= lowerBound && d <= upperBound)
+			return true;
+	}
+
+	return false;
+}
+
+void movePeople(Location l) {
+	bool tempVisit[MAX_WIDTH][MAX_WIDTH] = { 0, };
+	list<Location> q;
+	q.push_back({ l.row, l.col });
+
+	list<Location> temp;
+	temp.push_back({ l.row, l.col });
+	tempVisit[l.row][l.col] = true;
+	visit[l.row][l.col] = true;
+	while (!temp.empty()) {
+		Location t = temp.front();
+		temp.pop_front();
+		if (!tempVisit[t.row - 1][t.col] && checkNeibor(t, 0)) {
+			temp.push_back({ t.row - 1, t.col });
+			q.push_back({ t.row - 1, t.col });
+			tempVisit[t.row - 1][t.col] = true;
+			visit[t.row - 1][t.col] = true;
+		}
+		if (!tempVisit[t.row][t.col + 1] && checkNeibor(t, 1)) {
+			temp.push_back({ t.row, t.col +1});
+			q.push_back({ t.row, t.col +1});
+			tempVisit[t.row][t.col + 1] = true;
+			visit[t.row][t.col + 1] = true;
+		}
+		if (!tempVisit[t.row + 1][t.col] && checkNeibor(t, 2)) {
+			temp.push_back({ t.row+1, t.col });
+			q.push_back({ t.row+1, t.col });
+			tempVisit[t.row+1][t.col] = true;
+			visit[t.row + 1][t.col] = true;
+		}
+		if (!tempVisit[t.row][t.col - 1] && checkNeibor(t, 3)) {
+			temp.push_back({ t.row, t.col - 1 });
+			q.push_back({ t.row, t.col - 1 });
+			tempVisit[t.row][t.col - 1] = true;
+			visit[t.row][t.col - 1] = true;
+		}
+	}
+
+	int count = 0;
+	list<Location>::iterator iter;
+	for (iter = q.begin(); iter != q.end(); iter++)
+		count += map[(*iter).row][(*iter).col];
+
+	int people = count / q.size();
+
+	for (iter = q.begin(); iter != q.end(); iter++) {
+		tempMap[(*iter).row][(*iter).col] = people;
+	}
+}
+
This post is licensed under CC BY 4.0 by the author.

15686 Samsung sw test

16235 Samsung sw test

Comments powered by Disqus.

diff --git a/posts/16235/index.html b/posts/16235/index.html new file mode 100644 index 000000000..2dad4e2b6 --- /dev/null +++ b/posts/16235/index.html @@ -0,0 +1,285 @@ + 16235 Samsung sw test | 디피의 개발일지
Posts 16235 Samsung sw test
Post
Cancel

16235 Samsung sw test

16235 Samsung sw test

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+
#include <iostream>
+#include <algorithm>
+#include <vector>
+#define MAX_WIDTH 10
+
+using namespace std;
+
+typedef struct Tree {
+	int age;
+	bool isAlive;
+	bool operator < (Tree t) {
+		return age < t.age;
+	}
+} Tree;
+typedef struct Cell {
+	int ntr;
+	vector<Tree> tree;
+} Cell;
+
+Cell map[MAX_WIDTH][MAX_WIDTH];
+int nutrition[MAX_WIDTH][MAX_WIDTH];
+int width, maxYear;
+
+void growTrees();
+int checkAliveTree();
+void spring();
+void summer();
+void fall();
+void winter();
+
+int main() {
+	int tempM;
+	cin >> width >> tempM >> maxYear;
+	for (int i = 0; i < width; i++)
+		for (int j = 0; j < width; j++) {
+			cin >> nutrition[i][j];
+			map[i][j].ntr = 5;
+		}
+	for (int i = 0; i < tempM; i++) {
+		int row, col, age;
+		cin >> row >> col >> age;
+		map[row-1][col-1].tree.push_back({ age, true });
+	}
+
+	growTrees();
+	cout << checkAliveTree();
+}
+
+void growTrees() {
+	for (int i = 0; i < maxYear; i++) {
+		spring();
+		summer();
+		fall();
+		winter();
+	}
+}
+void spring() {
+	//나이순으로 정렬
+	for (int i = 0; i < width; i++)
+		for (int j = 0; j < width; j++) {
+			sort(map[i][j].tree.begin(), map[i][j].tree.end());
+		}
+
+	//양분을 주고, 죽은 건 false 표시하기
+	for (int i = 0; i < width; i++)
+		for (int j = 0; j < width; j++)
+			for (int k = 0; k < map[i][j].tree.size(); k++) {
+				if (map[i][j].ntr >= map[i][j].tree[k].age) {
+					map[i][j].ntr -= map[i][j].tree[k].age;
+					map[i][j].tree[k].age++;
+				}
+				else
+					map[i][j].tree[k].isAlive = false;
+			}
+}
+void summer() {
+	for (int i = 0; i < width; i++)
+		for (int j = 0; j < width; j++) {
+			int count = 0;
+			for (int k = 0; k < map[i][j].tree.size(); k++) {
+				if (!map[i][j].tree[k].isAlive) {
+					map[i][j].ntr += (map[i][j].tree[k].age / 2);
+					count++;
+				}
+			}
+			for (int k = 0; k < count; k++) {
+				map[i][j].tree.pop_back();
+			}
+		}
+}
+void fall() {
+	for (int i = 0; i < width; i++) {
+		for (int j = 0; j < width; j++) {
+			for (int k = 0; k < map[i][j].tree.size(); k++) {
+				if (map[i][j].tree[k].age % 5 == 0) {
+					if (i > 0 && j > 0)
+						map[i - 1][j - 1].tree.push_back({ 1, true });
+
+					if (i > 0)
+						map[i - 1][j].tree.push_back({ 1, true });
+
+					if (i > 0 && j < width - 1)
+						map[i - 1][j + 1].tree.push_back({ 1,true });
+
+					if (j > 0)
+						map[i][j - 1].tree.push_back({ 1, true });
+
+					if (j < width -1)
+						map[i][j+1].tree.push_back({ 1, true });
+
+					if (i < width -1 && j >0)
+						map[i + 1][j - 1].tree.push_back({ 1,true });
+
+					if (i < width -1)
+						map[i+1][j].tree.push_back({ 1, true });
+
+					if (i < width - 1 && j < width -1)
+						map[i +1][j+1].tree.push_back({ 1, true });
+				}
+			}
+		}
+	}
+
+}
+void winter() {
+	for(int i = 0; i<width; i++)
+		for (int j = 0; j < width; j++) {
+			map[i][j].ntr += nutrition[i][j];
+		}
+}
+int checkAliveTree() {
+	int count = 0;
+	for (int i = 0; i < width; i++)
+		for (int j = 0; j < width; j++)
+			for (int k = 0; k < map[i][j].tree.size(); k++) {
+				if (map[i][j].tree[k].isAlive)
+					count++;
+				else
+					break;
+			}
+	return count;
+}
+
This post is licensed under CC BY 4.0 by the author.

16234 Samsung sw test

16236 Samsung sw test

Comments powered by Disqus.

diff --git a/posts/16236/index.html b/posts/16236/index.html new file mode 100644 index 000000000..a6894038e --- /dev/null +++ b/posts/16236/index.html @@ -0,0 +1,239 @@ + 16236 Samsung sw test | 디피의 개발일지
Posts 16236 Samsung sw test
Post
Cancel

16236 Samsung sw test

16236 Samsung sw test

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+
#include <iostream>
+#include <list>
+#define MAX_WIDTH 20
+
+using namespace std;
+
+typedef struct Location {
+	int row;
+	int col;
+	int distance;
+	bool operator < (Location t) {
+		if (distance < t.distance)
+			return true;
+		else if (t.distance < distance)
+			return false;
+		else {
+			if (row < t.row)
+				return true;
+			else if (t.row < row)
+				return false;
+			else if (col < t.col)
+				return true;
+			else
+				return false;
+		}
+	}
+} Location;
+
+int map[MAX_WIDTH][MAX_WIDTH];
+int width;
+Location shark;
+
+int babyShark();
+bool isThereSmallThanMe(int sharkSize);
+
+int main() {
+	cin >> width;
+	for (int i = 0; i < width; i++)
+		for (int j = 0; j < width; j++) {
+			cin >> map[i][j];
+			if (map[i][j] == 9) {
+				shark.row = i;
+				shark.col = j;
+				shark.distance = 0;
+				map[i][j] = 0;
+			}
+		}
+	cout << babyShark();
+}
+
+int babyShark() {
+	int distance = 0;
+	int sharkSize = 2;
+	int countEat = 0;
+
+	while (true) {
+		if (isThereSmallThanMe(sharkSize)) {
+			list<Location> q;
+			list<Location> canEat;
+			q.push_back(shark);
+			bool visit[MAX_WIDTH][MAX_WIDTH] = { 0, };
+			visit[shark.row][shark.col] = true;
+			while (!q.empty()) {
+				Location temp = q.front();
+				q.pop_front();
+
+				if (map[temp.row][temp.col] != 0 && map[temp.row][temp.col] < sharkSize)
+					canEat.push_back(temp);
+
+				if (temp.row > 0 && !visit[temp.row - 1][temp.col] && map[temp.row - 1][temp.col] <= sharkSize) {
+					q.push_back({ temp.row - 1, temp.col, temp.distance + 1 });
+					visit[temp.row - 1][temp.col] = true;
+				}
+				if (temp.col < width - 1 && !visit[temp.row][temp.col + 1] && map[temp.row][temp.col + 1] <= sharkSize) {
+					q.push_back({ temp.row, temp.col + 1, temp.distance + 1 });
+					visit[temp.row][temp.col + 1] = true;
+				}
+				if (temp.row < width - 1 && !visit[temp.row + 1][temp.col] && map[temp.row + 1][temp.col] <= sharkSize) {
+					q.push_back({ temp.row + 1, temp.col, temp.distance + 1 });
+					visit[temp.row + 1][temp.col] = true;
+				}
+				if (temp.col > 0 && !visit[temp.row][temp.col - 1] && map[temp.row][temp.col - 1] <= sharkSize) {
+					q.push_back({ temp.row, temp.col - 1, temp.distance + 1 });
+					visit[temp.row][temp.col - 1] = true;
+				}
+			}
+
+			//가장 가깝고 왼쪽 위 먹고, visit 초기화, shark 위치, 사이즈 확인, count 1 증가,
+			if (!canEat.empty()) {
+				canEat.sort();
+				Location eat = canEat.front();
+				map[eat.row][eat.col] = 0;
+				shark.row = eat.row;
+				shark.col = eat.col;
+				shark.distance = 0;
+				distance += eat.distance;
+				countEat++;
+				if (countEat == sharkSize) {
+					sharkSize++;
+					countEat = 0;
+				}
+			}
+			else
+				break;
+		}
+		else
+			break;
+	}
+
+	return distance;
+}
+
+bool isThereSmallThanMe(int sharkSize) {
+	for (int i = 0; i < width; i++)
+		for (int j = 0; j < width; j++)
+			if (map[i][j] != 0 && sharkSize > map[i][j])
+				return true;
+	return false;
+}
+
This post is licensed under CC BY 4.0 by the author.

16235 Samsung sw test

1629 곱셈

Comments powered by Disqus.

diff --git a/posts/1629/index.html b/posts/1629/index.html new file mode 100644 index 000000000..821f16187 --- /dev/null +++ b/posts/1629/index.html @@ -0,0 +1,41 @@ + 1629 곱셈 | 디피의 개발일지
Posts 1629 곱셈
Post
Cancel

1629 곱셈

1629 곱셈

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
#include <iostream>
+
+using namespace std;
+
+long long A, B, C;
+long long result = 1;
+
+
+int main() {
+	cin >> A >> B >> C;
+
+	while (B != 0) {
+		if (B % 2 == 1) {
+			result = (result * A) % C;
+		}
+		A = (A * A) % C;
+		B /= 2;
+	}
+	cout <<  result % C;
+}
+
This post is licensed under CC BY 4.0 by the author.

16236 Samsung sw test

17140 Samsung sw test

Comments powered by Disqus.

diff --git a/posts/1644/index.html b/posts/1644/index.html new file mode 100644 index 000000000..73fe4af0c --- /dev/null +++ b/posts/1644/index.html @@ -0,0 +1,149 @@ + 1644 소수의 연속합 | 디피의 개발일지
Posts 1644 소수의 연속합
Post
Cancel

1644 소수의 연속합

1644 소수의 연속합

알고리즘(에라토스테네스의 체, 두 포인터)

1
+2
+
1. 에라토스테네스의 체로, N이하의 소수를 모두 구함
+2. 두 포인터로 일치하는 해당하는 연속합을 구함
+
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+
		초기 : start = 0, end = 1, sum = prime[0]
+
+		while start < end
+			if sum == N
+				count++
+				sum -= prime[start++]		//밑에서 end를 기준으로 끝을 정했으니, start를 조작한다.
+
+			else if sum < N
+				if end == numOfPrime		//더이상 커질 수 없으므로 종료
+					break;
+				sum += prime[end++]
+
+			else
+				sum -= prime[start++]		//크므로 줄임
+```
+
+## 코드
+```cpp
+#include <iostream>
+#define MAX 4000001
+
+using namespace std;
+
+int prime[290000], tempPrime[MAX];
+int N, numOfPrime = 0;
+
+void generatePrime();
+int getResult();
+
+int main() {
+	ios::sync_with_stdio(0);
+	cout.tie(0);
+	cin >> N;
+	generatePrime();
+	cout << getResult();
+}
+
+void generatePrime() {
+	for (int i = 2; i <= N; i++)
+		tempPrime[i] = i;
+
+	for (int i = 2; i <= N / 2; i++) {
+		if (tempPrime[i] != -1)
+			for (int j = 2 * i; j <= N; j += i) {
+				tempPrime[j] = -1;
+			}
+	}
+	for (int i = 2; i <= N; i++)
+		if (tempPrime[i] != -1)
+			prime[numOfPrime++] = tempPrime[i];
+}
+
+int getResult() {
+	int start = 0, end = 1;
+	int sum = prime[0], count = 0;
+
+
+	while (start < end) {
+		if (sum == N) {
+			count++;
+			sum -= prime[start++];
+		}
+		else if (sum < N) {
+			if (end == numOfPrime)
+				break;
+			sum += prime[end++];
+		}
+		else
+			sum -= prime[start++];
+	}
+	return count;
+}
+
This post is licensed under CC BY 4.0 by the author.

1562 계단수

1647 split town

Comments powered by Disqus.

diff --git a/posts/1647/index.html b/posts/1647/index.html new file mode 100644 index 000000000..2aa51d59a --- /dev/null +++ b/posts/1647/index.html @@ -0,0 +1,191 @@ + 1647 split town | 디피의 개발일지
Posts 1647 split town
Post
Cancel

1647 split town

1647 split town

알고리즘(MST)

1
+2
+
1. MST를 만들고, 가장 거리가 긴 길을 빼면 된다.
+2. n개의 노드에 대해 n-1개의 간선만 연결되어있으므로, 1개만 빼면 딱 두개로 나눌 수 있는데, 뺀다면 가장 거리가 긴 길이기 때문이다.(2개 이상 빼면 3개이상으로 나눠짐)
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+
#include <iostream>
+#include <vector>
+#include <cstring>
+#include <algorithm>
+#define MAX_WAY 1000000
+#define MAX_NODE 100000
+
+using namespace std;
+
+typedef struct Way {
+	int node1;
+	int node2;
+	int value;
+	bool operator < (Way w) {
+		return value < w.value;
+	}
+} Way;
+
+Way way[MAX_WAY];
+int node[MAX_NODE+1];
+
+vector<vector<int>> set;
+vector<int> MST;
+int numOfWay, numOfHouse;
+
+int getMinValue();
+void makeMST();
+
+int main() {
+	cin >> numOfHouse >> numOfWay;
+	for (int i = 0; i < numOfWay; i++)
+		cin >> way[i].node1 >> way[i].node2 >> way[i].value;
+
+	cout << getMinValue();
+}
+
+int getMinValue() {
+	sort(way, &way[numOfWay]);
+	memset(node, -1, sizeof(node));
+	makeMST();
+
+	sort(MST.begin(), MST.end());
+	MST.pop_back();
+	int value = 0;
+	for (int i = 0; i < MST.size(); i++)
+		value += way[MST[i]].value;
+	return value;
+}
+
+void makeMST() {
+	for (int i = 0; i < numOfWay; i++) {
+		if (node[way[i].node1] == -1 && node[way[i].node2] == -1) {
+			vector<int> temp;
+			temp.push_back(i);
+			set.push_back(temp);
+			node[way[i].node1] = set.size() - 1;
+			node[way[i].node2] = set.size() - 1;
+		}
+		else if (node[way[i].node1] == -1) {
+			node[way[i].node1] = node[way[i].node2];
+			set[node[way[i].node1]].push_back(i);
+		}
+		else if (node[way[i].node2] == -1) {
+			node[way[i].node2] = node[way[i].node1];
+			set[node[way[i].node2]].push_back(i);
+		}
+		else {
+			if (node[way[i].node1] < node[way[i].node2]) {
+				int temp = node[way[i].node2];
+				for (int j = 0; j < set[temp].size(); j++) {
+					node[way[set[temp][j]].node1] = node[way[i].node1];
+					node[way[set[temp][j]].node2] = node[way[i].node1];
+					set[node[way[i].node1]].push_back(set[temp][j]);
+				}
+				set[node[way[i].node1]].push_back(i);
+				set[temp].clear();
+			}
+			else if (node[way[i].node1] > node[way[i].node2]) {
+				int temp = node[way[i].node1];
+				for (int j = 0; j < set[temp].size(); j++) {
+					node[way[set[temp][j]].node1] = node[way[i].node2];
+					node[way[set[temp][j]].node2] = node[way[i].node2];
+					set[node[way[i].node2]].push_back(set[temp][j]);
+				}
+				set[node[way[i].node1]].push_back(i);
+				set[temp].clear();
+			}
+		}
+
+	}
+	MST = set[0];
+	set.clear();
+}
+
This post is licensed under CC BY 4.0 by the author.

1644 소수의 연속합

1655 가운데를 말해요

Comments powered by Disqus.

diff --git a/posts/1655/index.html b/posts/1655/index.html new file mode 100644 index 000000000..099375a89 --- /dev/null +++ b/posts/1655/index.html @@ -0,0 +1,113 @@ + 1655 가운데를 말해요 | 디피의 개발일지
Posts 1655 가운데를 말해요
Post
Cancel

1655 가운데를 말해요

1655 가운데를 말해요

내 방식 (multiset)

1
+2
+3
+4
+5
+6
+7
+
1. 멀티셋으로 가운데를 트래킹해가면서 구함
+2. 첫번째 원소를 넣고, s.begin()으로 첫번째 iter를 middle로 받고 그대로 출력
+3. 두번째부턴 middle과 같은 원소가 들어오면, s.insert(s.upperbound(*middle), input) 으로 가장 앞에 넣음
+   아니면 그냥 s.insert 로 넣음
+4. 현재 넣는 것이 짝수번째 숫자이고, 넣은 원소가 현재 middle보다 작을 경우 middle--	(작은 원소가 들어왔을 때, middle은 반쪽 바로 앞에 위치하기에)
+  현재 넣는 것이 홀수번째 숫자이고, 넣은 원소가 현재 middle보다 같거나 클 경우 middle++	(같거나 큰 원소가 들어왔을 때, middle은 반쪽 바로 뒤에 위치하기에)
+5. middle을 출력
+

정석 (우선순위 큐, 최대 힙, 최소힙)

1
+2
+3
+4
+5
+6
+7
+8
+9
+
1. priority_queue로 최대힙, 최소힙 둘 다 선언
+2. 먼저 최대힙에 넣고, 다음 원소를 최소힙에 넣는 식으로 입력을 받음
+3. 입력을 받을 때마다, 최대힙의 top과 최소힙의 top을 비교하여 최대힙의 top이 더 크면 교환함
+4. 이후 최대힙의 top을 출력(이것이 중간값이니까)
+
+원리
+
+	2~3 과정을 거치면서, 최대힙에는 작은 수들이 저장되고, 최소힙에는 큰 수들이 저장된다.
+	그리고 작은 수들의 최대는 중간값이다.
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+
#include <iostream>
+#include <set>
+
+using namespace std;
+
+multiset<int> s;
+
+int main() {
+	ios::sync_with_stdio(0);
+	cin.tie(0);
+	cout.tie(0);
+
+	int numOfN, temp;
+	cin >> numOfN >> temp;
+	s.insert(temp);
+	multiset<int>::iterator middle = s.begin();
+	cout << "result : " << *middle << "\n";
+
+	for (int i = 2; i <= numOfN; i++) {
+		cin >> temp;
+		int cur = *middle;
+		if (temp == cur)
+			s.insert(s.upper_bound(temp), temp);
+		else
+			s.insert(temp);
+
+		if (i % 2 == 1) {
+			if (temp >= cur)
+				middle++;
+		}
+		else if (i % 2 == 0) {
+			if (temp < cur)
+				middle--;
+		}
+		cout << "result : " <<  *middle << "\n";
+		for (multiset<int>::iterator iter = s.begin(); iter != s.end(); iter++)
+			cout << *iter << " ";
+		cout << "\n\n";
+	}
+}
+
This post is licensed under CC BY 4.0 by the author.

1647 split town

16562 친구비

Comments powered by Disqus.

diff --git a/posts/16562/index.html b/posts/16562/index.html new file mode 100644 index 000000000..877a06b50 --- /dev/null +++ b/posts/16562/index.html @@ -0,0 +1,133 @@ + 16562 친구비 | 디피의 개발일지
Posts 16562 친구비
Post
Cancel

16562 친구비

16562 친구비

알고리즘 (union find)

1
+2
+
1. unionfind 를 구현하되, 루트에 친구비가 적은 것이 오도록함
+2. 루트만 골라 친구비를 정산한다.
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+
#include <iostream>
+#include <vector>
+#include <cstring>
+#include <algorithm>
+#define MAX_STUDENT 10001
+
+using namespace std;
+
+int numOfStudent, numOfFriendship, money, students[MAX_STUDENT], minMoney, studentGroup[MAX_STUDENT];
+
+void unionStudent(int first, int second);
+int find(int element);
+bool makeFriend();
+
+int main() {
+	ios::sync_with_stdio(0);
+	cin.tie(0);
+	cin >> numOfStudent >> numOfFriendship >> money;
+	for (int i = 1; i <= numOfStudent; i++) {
+		cin >> students[i];
+		studentGroup[i] = i;
+	}
+	for (int i = 0; i < numOfFriendship; i++) {
+		int first, second;
+		cin >> first >> second;
+		unionStudent(first, second);
+	}
+	if (makeFriend())
+		cout << minMoney;
+	else
+		cout << "Oh no";
+}
+
+//값이 작은 게 루트에 오도록
+void unionStudent(int first, int second) {
+	first = find(first);
+	second = find(second);
+
+	if (students[first] <= students[second])
+		studentGroup[second] = first;
+	else
+		studentGroup[first] = second;
+}
+
+
+int find(int element) {
+	if (studentGroup[element] == element)
+		return element;
+	else
+		return find(studentGroup[element]);
+}
+
+bool makeFriend() {
+	for (int i = 1; i <= numOfStudent; i++) {
+		if (studentGroup[i] == i) {
+			if (money < students[i])
+				return false;
+			minMoney += students[i];
+			money -= students[i];
+		}
+	}
+
+	return true;
+}
+
This post is licensed under CC BY 4.0 by the author.

1655 가운데를 말해요

16566 카드게임

Comments powered by Disqus.

diff --git a/posts/16566/index.html b/posts/16566/index.html new file mode 100644 index 000000000..270c7f557 --- /dev/null +++ b/posts/16566/index.html @@ -0,0 +1,151 @@ + 16566 카드게임 | 디피의 개발일지
Posts 16566 카드게임
Post
Cancel

16566 카드게임

16566 카드게임

알고리즘(이분탐색, 정렬, 분리집합, 세그먼트트리?)

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+
1. 고른 카드를 버킷정렬함(400만개이므로 버킷정렬이 훨씬 빠르다. sort는 NlogN)
+2. 범위를 저장하는 set<pair<int,int>>을 선언하고 첫 범위를 넣음(0-m)
+3. 철수가 뽑은 카드의 수가 저장된 범위의 끝의 수보다 작을 때의 범위를 찾고, 그 범위안에서 uppper_bound로 큰수를 찾고,
+ 그 찾은 공간을 제외한 양쪽의 두 범위를 다시 범위set에 추가하고, 현재 범위는 삭제한다.
+
+ ex) 철수가 뽑은 수 5 / 범위 : 0-3 5-7 / cards : 1 2 3 4 5 6 7 8
+	1. 범위 앞에서부터 검사
+		0-3 -> 3번째 카드는 4. 5보다 작으므로 통과
+		5-7 -> 7번째 카드는 8. 5보다 크므로 스탑
+	2. 5-7을 기준으로 upper_bound를 실시 : upper_bound(cards + (*iter).first, cards + (*iter).second + 1, 5);
+		-> 나온 포인터를 저장
+	3. 5-7에서 5보다 큰 것은 6이므로, 포인터는 card + 5를 가리키고 있음.
+	  이 cards + 5를 기준으로 두 범위로 분리시킨 후 저장.
+		-> 이때 첫번째 원소이므로 왼쪽은 없음 따라서 오른쪽범위만 추가(6-7)
+	4. 기존 5-7범위는 삭제.
+	5. 찾은 수, 6을 리턴.
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+
#include <iostream>
+#include <algorithm>
+#include <utility>
+#include <set>
+#define MAX_CARD 4000000
+#define MAX_DRAW 10000
+
+using namespace std;
+
+int cards[MAX_CARD], draw[MAX_DRAW], numOfCard, m, numOfDraw;
+bool buket[MAX_CARD + 1];
+set<pair<int, int>> bound;
+
+void buketSort();
+int findCard(int drawCard);
+
+int main() {
+	ios::sync_with_stdio(0);
+	cin.tie(0);
+	cin >> numOfCard >> m >> numOfDraw;
+	for (int i = 0; i < m; i++) {
+		int temp;
+		cin >> temp;
+		buket[temp] = true;
+	}
+	for (int i = 0; i < numOfDraw; i++) {
+		cin >> draw[i];
+	}
+	buketSort();
+	bound.insert(make_pair(0, m-1));
+
+	for (int i = 0; i < numOfDraw; i++)
+		cout << findCard(draw[i])<< "\n";
+}
+
+
+void buketSort() {
+	int index = 0;
+	for (int i = 1; i < MAX_CARD + 1; i++)
+		if (buket[i]) {
+			cards[index++] = i;
+		}
+}
+
+int findCard(int drawCard) {
+
+	for (set<pair<int, int>>::iterator iter = bound.begin(); iter != bound.end(); iter++) {
+		if (cards[(*iter).second] > drawCard) {
+			int* temp = upper_bound(cards + (*iter).first, cards + (*iter).second + 1, drawCard);
+			if(temp -cards != (*iter).first)
+				bound.insert(make_pair((*iter).first, temp - cards - 1));
+			if(temp - cards != (*iter).second)
+				bound.insert(make_pair(temp - cards + 1, (*iter).second));
+			bound.erase(iter);
+			return *temp;
+		}
+	}
+
+}
+
This post is licensed under CC BY 4.0 by the author.

16562 친구비

16724 피리부는 사나이

Comments powered by Disqus.

diff --git "a/posts/16639-\352\264\204\355\230\270-\354\266\224\352\260\200\355\225\230\352\270\260-3/index.html" "b/posts/16639-\352\264\204\355\230\270-\354\266\224\352\260\200\355\225\230\352\270\260-3/index.html" new file mode 100644 index 000000000..00fda1bfe --- /dev/null +++ "b/posts/16639-\352\264\204\355\230\270-\354\266\224\352\260\200\355\225\230\352\270\260-3/index.html" @@ -0,0 +1,121 @@ + 16639 괄호 추가하기 3 | 디피의 개발일지
Posts 16639 괄호 추가하기 3
Post
Cancel

16639 괄호 추가하기 3

알고리즘

  • 브루트포스로 함.
  • 서로 이웃한 정수끼리 연산을 하는 조합을 하는데, 모든 조합을 전부 다 돌린다.
    • 숫자는 최대 10개이고, 브루트포스로 했을때 O(n!) 이므로, 충분히 시간안에 돌릴 수 있다.

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+
#include <iostream>
+using namespace std;
+
+long long  N, num[10];
+char op[10];
+
+long long  calculate(int deep);
+long long max(long long a, long long b) {
+	return a > b ? a : b;
+}
+
+int main() {
+	cin >> N;
+	for (int i = 0; i < N; i++) {
+		if (i % 2 == 0)
+			cin >> num[i/2];
+		else
+			cin >> op[i/2];
+	}
+
+	cout << calculate(0);
+}
+
+long long  calculate(int deep) {
+	if (deep == N / 2) {
+		return num[0];
+	}
+
+	int last = (N - deep * 2) / 2;
+	long long result = -6000000000;
+	for (int i = 0; i < last; i++) {
+		char saveOp = op[i];
+		int saveNum = num[i], saveNum2 = num[i + 1];
+
+		if (saveOp == '+')
+			num[i] = saveNum + saveNum2;
+		else if (saveOp == '-')
+			num[i] = saveNum - saveNum2;
+		else
+			num[i] = saveNum * saveNum2;
+
+		for (int j = i + 1; j < last; j++)
+			num[j] = num[j + 1];
+		for (int j = i; j < last - 1; j++)
+			op[j] = op[j + 1];
+
+		result = max(result, calculate(deep + 1));
+
+		for (int j = last; j > i + 1; j--)
+			num[j] = num[j - 1];
+		for (int j = last - 1; j > i; j--)
+			op[j] = op[j - 1];
+		op[i] = saveOp;
+		num[i] = saveNum;
+		num[i + 1] = saveNum2;
+	}
+
+
+	return result;
+}
+
This post is licensed under CC BY 4.0 by the author.

18500 미네랄 2

14466 소가 길을 건너간 이유 6

Comments powered by Disqus.

diff --git a/posts/16724/index.html b/posts/16724/index.html new file mode 100644 index 000000000..80ff9837a --- /dev/null +++ b/posts/16724/index.html @@ -0,0 +1,179 @@ + 16724 피리부는 사나이 | 디피의 개발일지
Posts 16724 피리부는 사나이
Post
Cancel

16724 피리부는 사나이

16724 피리부는 사나이

알고리즘 (DFS)

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
1. 맵에 있는 서로 이어져있는 그래프의 개수를 세면 됨.
+2. visit을 만들어, 이미 검사한 그래프의 원소엔 true를 표시한다. 그리고 tempVisit을 만들어, 현재 검사하고 있는 그래프의 원소에 true를 표시한다.
+3. 먼저, 첫번째 셀부터 검사하지 않았으면 DFS를 실시한다.
+	-> 갈수있는데까지 가면서 tempVisit에 true를 표시
+	-> tempVsit이든 visit 배열이든 true로 표시돼있는 칸에 오면 중지
+		-> 이때 상태는 두가지 : 자기들끼리 돌다 만난건지, 이미 검사한 그래프로 합쳐지는 건지.
+		-> 전체 visit 배열이 true이면 원래 DFS에 합쳐지는 것 -> false 반환
+		-> 전체 visit 배열은 false이고 현재 visit배열도 false 일때 -> 진행
+		-> 전체 visit 배열은 false이고 현재 visit배열은 true일때 -> 자기끼리 만나는 것 -> true반환
+	-> 현재 검사하는 원소를 하나씩 list에 넣고, 하나씩 빼가며 전체 visit배열에 true표시하고 현재 배열은 false로 초기화하기
+4. true로 반환될 때만 새로운 그래프가 만들어지는 것이므로 safezone의 개수 1증가
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+
#include <iostream>
+#include <vector>
+#include <string>
+#define MAX_WIDTH 1000
+
+using namespace std;
+
+typedef struct Location {
+	int row;
+	int col;
+
+} Location;
+
+char map[MAX_WIDTH][MAX_WIDTH];
+int n, m, safezone;
+bool visit[MAX_WIDTH][MAX_WIDTH], tempVisit[MAX_WIDTH][MAX_WIDTH];
+
+void generateSafezone();
+bool DFS(int row, int col);
+Location getNextCell(Location cur);
+
+int main() {
+	cin >> n >> m;
+	for (int i = 0; i < n; i++) {
+		string temp;
+		cin >> temp;
+		for (int j = 0; j < m; j++) {
+			map[i][j] = temp[j];
+		}
+	}
+	generateSafezone();
+	cout << safezone;
+}
+
+
+void generateSafezone() {
+	for (int i = 0; i < n; i++)
+		for (int j = 0; j < m; j++)
+			if (!visit[i][j]) {
+				if (DFS(i, j)) {
+					safezone++;
+				}
+			}
+}
+
+bool DFS(int row, int col) {
+	vector<Location> q;
+	Location cur = { row,col };
+
+	while (!visit[cur.row][cur.col] && !tempVisit[cur.row][cur.col]) {
+		q.push_back({ cur.row, cur.col });
+		tempVisit[cur.row][cur.col] = true;
+		cur = getNextCell(cur);
+	}
+
+	bool result;
+	if (visit[cur.row][cur.col])
+		result = false;
+	else
+		result =  true;
+
+	for (int i = 0; i < q.size(); i++) {
+		visit[q[i].row][q[i].col] = true;
+		tempVisit[q[i].row][q[i].col] = false;
+	}
+	return result;
+}
+
+Location getNextCell(Location cur) {
+	if (map[cur.row][cur.col] == 'D')
+		return { cur.row + 1, cur.col };
+	else if (map[cur.row][cur.col] == 'U')
+		return { cur.row - 1, cur.col };
+	else if (map[cur.row][cur.col] == 'L')
+		return { cur.row, cur.col - 1 };
+	else
+		return { cur.row, cur.col + 1 };
+}
+
This post is licensed under CC BY 4.0 by the author.

16566 카드게임

16946 벽부수고 이동하기 4

Comments powered by Disqus.

diff --git a/posts/16928/index.html b/posts/16928/index.html new file mode 100644 index 000000000..16b3b9918 --- /dev/null +++ b/posts/16928/index.html @@ -0,0 +1,117 @@ + 16928 뱀과 사다리게임 | 디피의 개발일지
Posts 16928 뱀과 사다리게임
Post
Cancel

16928 뱀과 사다리게임

알고리즘

  1. BFS로 가능한 경우를 찾아가면 된다.

  2. 그런데 주의점은, 뱀을 타는게 더 이득일 경우가 있다는 것만 주의하자

    ex)

    1
    +2
    +3
    +4
    +
    2 1
    +2 60
    +30 99
    +65 29
    +

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+
#include <bits/stdc++.h>
+
+using namespace std;
+
+int N, M, box[110];
+list<pair<int,int>> q;
+bool visited[110];
+
+int BFS();
+
+int main() {
+	cin >> N >> M;
+	int t;
+	for (int i = 0; i < N; i++) {
+		cin >> t;
+		cin >> box[t];
+	}
+	for (int i = 0; i < M; i++) {
+		cin >> t;
+		cin >> box[t];
+	}
+	cout << BFS();
+}
+
+int BFS() {
+	visited[1] = true;
+	q.push_back({ 1,0 });
+	int result = 2000000000;
+	while (!q.empty()) {
+		int cur = q.front().first, count = q.front().second;
+		q.pop_front();
+
+		if (cur == 100) {
+			result = min(result, count);
+			continue;
+		}
+		else if (cur > 100)
+			continue;
+
+		for (int i = 1; i <= 6; i++) {
+			int next = cur + i;
+			if (!visited[next]) {
+				if (box[next] != 0) {
+					q.push_back({ box[next], count + 1 });
+					visited[box[next]] = true;
+				}
+				else
+					q.push_back({ next, count + 1 });
+				visited[next] = true;
+			}
+		}
+	}
+	return result;
+}
+
This post is licensed under CC BY 4.0 by the author.

14238 출근기록

JunctionXSeoul 2021 후기

Comments powered by Disqus.

diff --git a/posts/16946/index.html b/posts/16946/index.html new file mode 100644 index 000000000..01ef76a1d --- /dev/null +++ b/posts/16946/index.html @@ -0,0 +1,309 @@ + 16946 벽부수고 이동하기 4 | 디피의 개발일지
Posts 16946 벽부수고 이동하기 4
Post
Cancel

16946 벽부수고 이동하기 4

16946 벽부수고 이동하기 4

알고리즘 (BFS)

1
+2
+3
+4
+5
+
1. 각 맵의 빈공간마다 각자의 크기를 BFS로 구함
+2. 이때, 각자의 id를 지정하여 같은 집합에 속하면 같은 id를 갖도록 하자
+3. BFS가 끝나면 각 벽을 검사
+	-> 주변에 인접한 모든 빈공간의 아까 구한 크기를 모두 더하고 자기자신 + 1해서 리턴
+	-> 이때, 주변에 인접한 빈공간들의 id 중 겹치는 것이 있는지 확인하면서 넣자. id가 같은 것이 이미 들어가있으면 그것은 더하지 않는다.
+

주의점

1
+2
+
1. memset으로 visit을 매 BFS마다 초기화시켰는데, 최대 10만번 BFS를 실시하니, 10만 x 10만 회의 연산이 진행된다.
+	-> 따라서 BFS로 넣어진 것들만 false로 초기화 시키자
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+
#include <iostream>
+#include <string>
+#include <vector>
+#include <list>
+#include <cstring>
+#define MAX_N 1000
+#define MAX_M 1000
+
+using namespace std;
+
+typedef struct Location {
+	int row;
+	int col;
+} Location;
+
+int n, m, BFSMap[MAX_N][MAX_M][2], result[MAX_N][MAX_M];
+Location q[MAX_N * MAX_M];
+bool map[MAX_N][MAX_M], visit[MAX_N][MAX_M];
+
+void generateBFS();
+void generateResult();
+void BFS(int row, int col, int id);
+int getResult(int row, int col);
+
+int main() {
+	cin >> n >> m;
+	for (int i = 0; i < n; i++) {
+		string s;
+		cin >> s;
+		for (int j = 0; j < m; j++) {
+			map[i][j] = s[j] - '0';
+		}
+	}
+
+	generateBFS();
+	generateResult();
+	for (int i = 0; i < n; i++) {
+		for (int j = 0; j < m; j++)
+			cout << result[i][j];
+		cout << "\n";
+	}
+}
+
+void generateBFS() {
+	int id = 1;
+	for (int i = 0; i < n; i++)
+		for (int j = 0; j < m; j++)
+			if (map[i][j] == 0 && BFSMap[i][j][0] == 0)
+				BFS(i, j, id++);
+}
+
+void BFS(int row, int col, int id) {
+	int index = 0, first = 0;
+	q[index++] = { row, col };
+	visit[row][col] = true;
+
+	while (first < index) {
+		Location temp = q[first++];
+
+		if (temp.row > 0 && map[temp.row - 1][temp.col] == 0 && !visit[temp.row - 1][temp.col]) {
+			q[index++] = { temp.row - 1, temp.col };
+			visit[temp.row - 1][temp.col] = true;
+		}
+		if (temp.row < n - 1 && map[temp.row + 1][temp.col] == 0 && !visit[temp.row + 1][temp.col]) {
+			q[index++] = { temp.row + 1, temp.col };
+			visit[temp.row + 1][temp.col] = true;
+		}
+		if (temp.col > 0 && map[temp.row][temp.col - 1] == 0 && !visit[temp.row][temp.col - 1]) {
+			q[index++] = { temp.row, temp.col - 1 };
+			visit[temp.row][temp.col - 1] = true;
+		}
+		if (temp.col < m - 1 && map[temp.row][temp.col + 1] == 0 && !visit[temp.row][temp.col + 1]) {
+			q[index++] = { temp.row, temp.col + 1 };
+			visit[temp.row][temp.col + 1] = true;
+		}
+	}
+
+	for (int i = 0; i < index; i++) {
+		BFSMap[q[i].row][q[i].col][0] = index;
+		BFSMap[q[i].row][q[i].col][1] = id;
+		visit[q[i].row][q[i].col] = false;
+	}
+}
+
+void generateResult() {
+	for (int i = 0; i < n; i++) {
+		for (int j = 0; j < m; j++) {
+			if (map[i][j] == 0)
+				result[i][j] = 0;
+			else
+				result[i][j] = getResult(i, j) % 10;
+		}
+	}
+}
+
+int getResult(int row, int col) {
+	int pickedId[4] = { -1, -1, -1, -1 };
+	int length = 0;
+	int result = 0;
+
+	if (row > 0) {
+		if (BFSMap[row - 1][col][0] != 0) {
+			result += BFSMap[row - 1][col][0];
+			pickedId[length++] = BFSMap[row - 1][col][1];
+		}
+	}
+	if (row < n - 1) {
+		if (BFSMap[row + 1][col][0] != 0) {
+			bool check = true;
+			for (int i = 0; i < length; i++) {
+				if (pickedId[i] == BFSMap[row + 1][col][1])
+					check = false;
+			}
+			if (check) {
+				result += BFSMap[row + 1][col][0];
+				pickedId[length++] = BFSMap[row + 1][col][1];
+			}
+		}
+	}
+	if (col > 0) {
+		if (BFSMap[row][col-1][0] != 0) {
+			bool check = true;
+			for (int i = 0; i < length; i++) {
+				if (pickedId[i] == BFSMap[row][col-1][1])
+					check = false;
+			}
+			if (check) {
+				result += BFSMap[row][col-1][0];
+				pickedId[length++] = BFSMap[row][col-1][1];
+			}
+		}
+	}
+	if (col < m - 1) {
+		if (BFSMap[row][col+1][0] != 0) {
+			bool check = true;
+			for (int i = 0; i < length; i++) {
+				if (pickedId[i] == BFSMap[row][col+1][1])
+					check = false;
+			}
+			if (check) {
+				result += BFSMap[row][col+1][0];
+			}
+		}
+	}
+
+	return result + 1;
+}
+
This post is licensed under CC BY 4.0 by the author.

16724 피리부는 사나이

16954 움직이는 미로탈출

Comments powered by Disqus.

diff --git a/posts/16954/index.html b/posts/16954/index.html new file mode 100644 index 000000000..4308f5f1d --- /dev/null +++ b/posts/16954/index.html @@ -0,0 +1,181 @@ + 16954 움직이는 미로탈출 | 디피의 개발일지
Posts 16954 움직이는 미로탈출
Post
Cancel

16954 움직이는 미로탈출

16954 움직이는 미로탈출

알고리즘(BFS)

1
+2
+3
+4
+5
+
1. q와 nextq를 선언
+2. q엔 현재 노드를 저장. 하나식 꺼내서 이동가능한 인접 칸을 nextq에 추가.
+3. 다 추가하면, 벽을 한칸 내림
+4. 이후 q에 nextq를 넣고, nextq는 비운채 다음 루프 시작.
+5. 루프 중에 q에서 꺼낸 노드가 벽이 있는 칸이라면 추가안하고 다음 노드로, 최우측상단이라면 종료
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+
#include <bits/stdc++.h>
+
+using namespace std;
+
+char miro[8][8];
+list<pair<int, int>> q, nextq;
+bool visitcheck[8][8];
+
+bool BFS();
+
+int main() {
+	for (int i = 0; i < 8; i++)
+		for (int j = 0; j < 8; j++)
+			cin >> miro[i][j];
+
+	cout << BFS();
+}
+
+bool BFS() {
+	bool check = false;
+	nextq.push_back({ 7,0 });
+
+	while (!nextq.empty() && !check) {
+		q = nextq;
+		nextq.clear();
+		memset(visitcheck, 0, sizeof(visitcheck));
+
+		while (!q.empty()) {
+			int r = q.front().first, c = q.front().second;
+			q.pop_front();
+
+			if (miro[r][c] == '#')
+				continue;
+			if (r == 0 && c == 7) {
+				check = true;
+				break;
+			}
+
+			if (miro[r][c] != '#' && !visitcheck[r][c]) {
+				nextq.push_back({ r, c });
+				visitcheck[r][c] = true;
+			}
+			if (r > 0 && miro[r - 1][c] != '#' && !visitcheck[r - 1][c]) {
+				nextq.push_back({ r - 1,c });
+				visitcheck[r - 1][c] = true;
+			}
+			if (r > 0 && c < 7 && miro[r - 1][c + 1] != '#' && !visitcheck[r - 1][c + 1]) {
+				nextq.push_back({ r - 1,c + 1 });
+				visitcheck[r - 1][c + 1] = true;
+			}
+			if (c < 7 && miro[r][c + 1] != '#' && !visitcheck[r][c + 1]) {
+				nextq.push_back({ r, c + 1 });
+				visitcheck[r][c + 1] = true;
+			}
+			if (r < 7 && c < 7 && miro[r + 1][c + 1] != '#' && !visitcheck[r + 1][c + 1]) {
+				nextq.push_back({ r + 1,c + 1 });
+				visitcheck[r + 1][c + 1] = true;
+			}
+			if (r < 7 && miro[r + 1][c] != '#' && !visitcheck[r + 1][c]) {
+				nextq.push_back({ r + 1,c });
+				visitcheck[r + 1][c] = true;
+			}
+			if (r < 7 && c > 0 && miro[r + 1][c - 1] != '#' && !visitcheck[r + 1][c - 1]) {
+				nextq.push_back({ r + 1,c - 1 });
+				visitcheck[r + 1][c - 1] = true;
+			}
+			if (c > 0 && miro[r][c - 1] != '#' && !visitcheck[r][c - 1]) {
+				nextq.push_back({ r, c - 1 });
+				visitcheck[r][c - 1] = true;
+			}
+			if (r > 0 && c > 0 && miro[r - 1][c - 1] != '#' && !visitcheck[r - 1][c - 1]) {
+				nextq.push_back({ r - 1,c - 1 });
+				visitcheck[r - 1][c - 1] = true;
+			}
+		}
+
+		for (int i = 7; i > 1; i--)
+			for (int j = 0; j < 8; j++)
+				miro[i][j] = miro[i - 1][j];
+		for (int j = 0; j < 8; j++)
+			miro[0][j] = '.';
+	}
+
+	return check;
+}
+
This post is licensed under CC BY 4.0 by the author.

16946 벽부수고 이동하기 4

17144 Samsung sw test

Comments powered by Disqus.

diff --git "a/posts/17088-\353\223\261\354\260\250\354\210\230\354\227\264\353\260\230\355\231\230/index.html" "b/posts/17088-\353\223\261\354\260\250\354\210\230\354\227\264\353\260\230\355\231\230/index.html" new file mode 100644 index 000000000..464abd495 --- /dev/null +++ "b/posts/17088-\353\223\261\354\260\250\354\210\230\354\227\264\353\260\230\355\231\230/index.html" @@ -0,0 +1,105 @@ + 17088 등차수열 변환 | 디피의 개발일지
Posts 17088 등차수열 변환
Post
Cancel

17088 등차수열 변환

알고리즘

  1. 첫번째와 두번째 원소 사이의 공차를 구함
  2. 두번째 원소부터 마지막원소까지의 공차를 만족하는 경우만으로 dfs를 진행함
    1. 마지막원소까지 공차를 만족하는 모든 경우의 수를 구하고, 그 중 최소 연산으로 만족하는 경우의 수를 반환함.
  3. 위 과정을 첫번째 원소와 두번째 원소 사이의 공차의 경우의수만큼 진행하고, 그 중에서 최소 연산의 수를 구한다.
    1. 각 원소별로, (-1, 0, 1) 세 개의 연산을 할 수 있으므로 공차의 경우의 수는 9개.


삽질

  1. 다이나믹을 적용할 수 있을거라 생각했다.
    • 현재 depth의 원소가 depth와 이전 depth에서 어떤 연산을 적용받은 결과인지로, 현재 depth의 상태가 결정된다고 생각하였기에.
    • 하지만, 현재 depth의 상태는 depth와 현재 원소의 값으로 결정되었다.
    • 하지만 원소의 값은 최대 10억이므로 다이나믹에 사용하기엔 무리가 있었다
  2. 현재 depth에서, 현재 depth와 다음 depth에 모두 연산을 적용하였다.
    • 양쪽을 모두 연산 시켜줘야 모든 경우를 탐색하는 것이라 생각했기에 그리하였는데, 당연히 정답코드에 비해 매 깊이마다 3배의 계산을 하게 되므로 시간초과가 났다
    • 하지만 이는 매우 많은 중복계산을 불러일으키고, 매 깊이에서 다음 depth의 연산만 해줘도 충분히 모든 경우를 탐색할 수 있었다.


코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+
#include <iostream>
+#define INF 2000000000
+using namespace std;
+
+int n, ans = INF, os[9][2] = { {0,0}, {0, 1}, {0, -1}, {1, -1}, {1, 0}, {1, 1}, {-1, -1}, {-1, 0}, {-1, 1} };
+int diff, nums[100010];
+
+int min(int a, int b) {
+	return a < b ? a : b;
+}
+int abs(int a) {
+	return a < 0 ? -a : a;
+}
+
+int dfs(int d, int prev) {
+	if (d == n - 1)
+		return 0;
+
+	int res = INF;
+	for (int j = -1; j <= 1; j++) {
+		nums[d + 1] += j;
+
+		if ((nums[d + 1] - nums[d]) == diff)
+			res = min(res, dfs(d + 1, j) + abs(j));
+
+		nums[d + 1] -= j;
+	}
+	return res;
+}
+
+int main() {
+	cin >> n;
+	for (int i = 0; i < n; i++)
+		cin >> nums[i];
+	if (n <= 2) {
+		cout << 0;
+		return 0;
+	}
+
+	for (int i = 0; i < 9; i++) {
+		nums[0] += os[i][0]; nums[1] += os[i][1];
+		diff = nums[1] - nums[0];
+
+		ans = min(ans, dfs(1, nums[1]) + abs(os[i][0]) + abs(os[i][1]));
+		nums[0] -= os[i][0]; nums[1] -= os[i][1];
+	}
+
+	if (ans >= INF - 200000)
+		cout << -1;
+	else
+		cout << ans;
+}
+
This post is licensed under CC BY 4.0 by the author.

서브네팅

deadlock 발생 조건

Comments powered by Disqus.

diff --git a/posts/17140/index.html b/posts/17140/index.html new file mode 100644 index 000000000..31c9a8509 --- /dev/null +++ b/posts/17140/index.html @@ -0,0 +1,253 @@ + 17140 Samsung sw test | 디피의 개발일지
Posts 17140 Samsung sw test
Post
Cancel

17140 Samsung sw test

17140 Samsung sw test

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+
#include <iostream>
+#include <algorithm>
+#include <cstring>
+#include <vector>
+#define MAX_NUM 101
+
+using namespace std;
+
+typedef struct Cell {
+	int value;
+	int count;
+
+	bool operator < (Cell c) {
+		if (count < c.count)
+			return true;
+		else if (c.count < count)
+			return false;
+		else {
+			if (value < c.value)
+				return true;
+			else
+				return false;
+		}
+	}
+} Cell;
+
+int map[MAX_NUM][MAX_NUM];
+int width, height;
+int r, c, k;
+
+int getTimeToReachRCK();
+void print() {
+	cout << endl;
+	for (int i = 0; i < height; i++) {
+		for (int j = 0; j < width; j++)
+			cout << map[i][j] << " ";
+		cout << endl;
+	}
+}
+
+int main() {
+	cin >> r >> c >> k;
+	for (int i = 0; i < MAX_NUM; i++)
+		memset(map[i], 0, sizeof(int) * MAX_NUM);
+
+	for (int i = 0; i < 3; i++)
+		for (int j = 0; j < 3; j++)
+			cin >> map[i][j];
+
+	width = height = 3;
+	cout << getTimeToReachRCK();
+}
+
+int getTimeToReachRCK() {
+	int time = 0;
+
+	for (time; time <= 100; time++) {
+		if (map[r-1][c-1] == k)
+			break;
+
+		int temp[MAX_NUM];
+		memset(temp, 0, sizeof(int) * (MAX_NUM));
+
+		if (height >= width) {
+			int maxWidth = 0;
+			for (int i = 0; i < height; i++) {
+				for (int j = 0; j < width; j++) {
+					temp[map[i][j]]++;
+				}
+
+				vector<Cell> temp2;
+				for (int j = 1; j < MAX_NUM; j++)
+					if (temp[j] != 0)
+						temp2.push_back({ j, temp[j] });
+
+				sort(temp2.begin(), temp2.end());
+				int index = 0;
+				for (int j = 0; j < temp2.size(); j++) {
+					if (index >= 100)
+						break;
+					map[i][index++] = temp2[j].value;
+					map[i][index++] = temp2[j].count;
+				}
+				if (index < width)
+					for (int j = index; j < width; j++)
+						map[i][j] = 0;
+
+				maxWidth = index > maxWidth ? index : maxWidth;
+				memset(temp, 0, sizeof(int) * (MAX_NUM));
+			}
+			width = maxWidth;
+		}
+		else {
+			int maxHeight = 0;
+			for (int i = 0; i < width; i++) {
+				for (int j = 0; j < height; j++) {
+					temp[map[j][i]]++;
+				}
+
+				vector<Cell> temp2;
+				for (int j = 1; j < MAX_NUM; j++)
+					if (temp[j] != 0)
+						temp2.push_back({ j, temp[j] });
+
+				sort(temp2.begin(), temp2.end());
+				int index = 0;
+				for (int j = 0; j < temp2.size(); j++) {
+					if (index >= 100)
+						break;
+					map[index++][i] = temp2[j].value;
+					map[index++][i] = temp2[j].count;
+				}
+				if (index < height)
+					for (int j = index; j < height; j++)
+						map[j][i] = 0;
+
+				maxHeight = index > maxHeight ? index : maxHeight;
+				memset(temp, 0, sizeof(int) * MAX_NUM);
+			}
+			height = maxHeight;
+		}
+	}
+
+
+	return (time == 101 ? -1 : time);
+}
+
This post is licensed under CC BY 4.0 by the author.

1629 곱셈

17142 Samsung sw test

Comments powered by Disqus.

diff --git a/posts/17142/index.html b/posts/17142/index.html new file mode 100644 index 000000000..35d7d5701 --- /dev/null +++ b/posts/17142/index.html @@ -0,0 +1,257 @@ + 17142 Samsung sw test | 디피의 개발일지
Posts 17142 Samsung sw test
Post
Cancel

17142 Samsung sw test

17142 Samsung sw test

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+
#include <iostream>
+#include <cstring>
+#include <vector>
+#include <list>
+#include <algorithm>
+#define MAX_WIDTH 50
+#define WALL 1
+
+using namespace std;
+int map[MAX_WIDTH][MAX_WIDTH];
+
+typedef struct Virus {
+	int row;
+	int col;
+	int time;
+
+	bool operator < (Virus v) {
+		if (map[row][col] == 2 && map[v.row][v.col] != 2)
+			return false;
+		else if (map[row][col] != 2 && map[v.row][v.col] == 2)
+			return true;
+		else
+			return time > v.time;
+	}
+} Virus;
+
+int width, numOfActiveVirus, numOfVirus;
+vector<Virus> virus;
+bool canSpread = false;
+
+int minToSpreadVirus(vector<Virus> active, int deep);
+int min(int a, int b);
+
+int main() {
+	cin >> width >> numOfActiveVirus;
+	numOfVirus = 0;
+	for (int i = 0; i < width; i++) {
+		for (int j = 0; j < width; j++) {
+			cin >> map[i][j];
+			if (map[i][j] == 2) {
+				virus.push_back({ i, j, 0});
+				numOfVirus++;
+			}
+		}
+	}
+	vector<Virus> temp;
+	int result = minToSpreadVirus(temp, 0);
+	if (result == -1 && canSpread)
+		cout << 0;
+	else
+		cout << minToSpreadVirus(temp, 0);
+}
+
+int minToSpreadVirus(vector<Virus> active, int deep) {
+	if (active.size() == numOfActiveVirus) {
+		//퍼트리기
+		list<Virus> q;
+		bool visit[MAX_WIDTH][MAX_WIDTH] = { 0, };
+		for (int i = 0; i < active.size(); i++) {
+			q.push_back(active[i]);
+			visit[active[i].row][active[i].col] = true;
+		}
+
+		vector<Virus> minTimeCandidate;
+		while (!q.empty()) {
+			Virus v = q.front();
+			q.pop_front();
+
+			int count = 0;
+			if (v.row > 0 && !visit[v.row - 1][v.col] && map[v.row - 1][v.col] != 1) {
+				q.push_back({ v.row - 1, v.col, v.time + 1 });
+				visit[v.row - 1][v.col] = true;
+				if(map[v.row-1][v.col] == 0)
+					count++;
+			}
+			if (v.col < width - 1 && !visit[v.row][v.col + 1] && map[v.row][v.col + 1] != 1) {
+				q.push_back({ v.row, v.col + 1, v.time + 1 });
+				visit[v.row][v.col + 1] = true;
+				if(map[v.row][v.col+1] == 0)
+					count++;
+			}
+			if (v.row < width - 1 && !visit[v.row + 1][v.col] && map[v.row + 1][v.col] != 1) {
+				q.push_back({ v.row + 1, v.col, v.time + 1 });
+				visit[v.row + 1][v.col] = true;
+				if(map[v.row+1][v.col]==0)
+					count++;
+			}
+			if (v.col > 0 && !visit[v.row][v.col - 1] && map[v.row][v.col - 1] != 1) {
+				q.push_back({ v.row, v.col - 1, v.time + 1 });
+				visit[v.row][v.col - 1] = true;
+				if(map[v.row][v.col-1] == 0)
+					count++;
+			}
+
+			if (count == 0)
+				minTimeCandidate.push_back(v);
+		}
+
+		for (int i = 0; i < width; i++)
+			for (int j = 0; j < width; j++)
+				if (!visit[i][j] && map[i][j] != 1)
+					return -1;
+
+		canSpread = true;
+		sort(minTimeCandidate.begin(), minTimeCandidate.end());
+		if (map[minTimeCandidate[0].row][minTimeCandidate[0].col] == 2)
+			return -1;
+		else
+			return minTimeCandidate[0].time;
+
+	}
+	if (numOfVirus - deep < numOfActiveVirus - active.size())
+		return -1;
+
+	int a = minToSpreadVirus(active, deep + 1);
+	active.push_back(virus[deep]);
+	int b = minToSpreadVirus(active, deep + 1);
+
+	return min(a, b);
+}
+int min(int a, int b) {
+	if (a == -1)
+		return b;
+	else if (b == -1)
+		return a;
+	else
+		return a < b ? a : b;
+}
+
This post is licensed under CC BY 4.0 by the author.

17140 Samsung sw test

1748 수 이어쓰기 1

Comments powered by Disqus.

diff --git a/posts/17144/index.html b/posts/17144/index.html new file mode 100644 index 000000000..6c6ab976d --- /dev/null +++ b/posts/17144/index.html @@ -0,0 +1,275 @@ + 17144 Samsung sw test | 디피의 개발일지
Posts 17144 Samsung sw test
Post
Cancel

17144 Samsung sw test

17144 Samsung sw test

알고리즘

1
+2
+3
+
1. 기존 map에서 미세먼지가 있는 부분만, 기존에 있던 미세먼지만 확산시키는 게 관건
+2. 따라서 tempMap을 만들어서 map을 복사한 다음에, map에 있는 미세먼지 양을 받아서, tempMap에 확산시키고, 다 확산하면 tempMap을 map에 복사하고 순환하여야 한다.
+3. 자꾸 갱신되는 상황을 잊지 말자
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+
#include <iostream>
+#include <cstring>
+#define MAX_WIDTH 50
+
+using namespace std;
+
+int map[MAX_WIDTH][MAX_WIDTH];
+int tempMap[MAX_WIDTH][MAX_WIDTH];
+int width, height, time;
+int airCirculatorRow[2];
+
+int airCirculator();
+void diffuseDust(int row, int col);
+void runAirCirculator();
+
+int main() {
+	cin >> height >> width >> time;
+	int index = 0;
+	for (int i = 0; i < height; i++) {
+		for (int j = 0; j < width; j++) {
+			cin >> map[i][j];
+			if (map[i][j] == -1)
+				airCirculatorRow[index++] = i;
+		}
+	}
+	cout << endl;
+	cout << airCirculator();
+}
+
+int airCirculator() {
+	for (int second = 0; second < time; second++) {
+		for (int i = 0; i < height; i++)
+			memcpy(tempMap[i], map[i], sizeof(int) * width);
+
+		//확산
+		for (int i = 0; i < height; i++)
+			for (int j = 0; j < width; j++)
+				if(map[i][j] != 0)
+					diffuseDust(i, j);
+
+
+		for (int i = 0; i < height; i++)
+			memcpy(map[i], tempMap[i], sizeof(int) * width);
+
+		cout << endl;
+		for (int i = 0; i < height; i++) {
+			for (int j = 0; j < width; j++)
+				cout << map[i][j] << " ";
+			cout << endl;
+		}
+		//청정기
+		runAirCirculator();
+	}
+
+	int dust = 0;
+	for (int i = 0; i < height; i++)
+		for (int j = 0; j < width; j++)
+			dust += map[i][j];
+	return dust + 2;
+}
+
+void diffuseDust(int row, int col) {
+	int count = 0;
+	int dustDiffusionAmount = map[row][col] / 5;
+
+	if (row > 0 && tempMap[row - 1][col] != -1) {
+		tempMap[row - 1][col] += dustDiffusionAmount;
+		count++;
+	}
+	if (col < width - 1) {
+		tempMap[row][col + 1] += dustDiffusionAmount;
+		count++;
+	}
+	if (row < height - 1 && tempMap[row + 1][col] != -1) {
+		tempMap[row + 1][col] += dustDiffusionAmount;
+		count++;
+	}
+	if (col > 0 && tempMap[row][col-1] != -1) {
+		tempMap[row][col - 1] += dustDiffusionAmount;
+		count++;
+	}
+
+	tempMap[row][col] -= (dustDiffusionAmount * count);
+}
+
+void runAirCirculator() {
+	//위
+	int temp = map[airCirculatorRow[0]][1];
+	map[airCirculatorRow[0]][1] = 0;
+	for (int i = 2; i < width; i++) {
+		int temp2 = map[airCirculatorRow[0]][i];
+		map[airCirculatorRow[0]][i] = temp;
+		temp = temp2;
+	}
+	for (int i = airCirculatorRow[0]-1; i >= 0; i--) {
+		int temp2 = map[i][width - 1];
+		map[i][width - 1] = temp;
+		temp = temp2;
+	}
+	for (int i = width - 2; i >= 0; i--) {
+		int temp2 = map[0][i];
+		map[0][i] = temp;
+		temp = temp2;
+	}
+	for (int i = 1; i < airCirculatorRow[0]; i++) {
+		int temp2 = map[i][0];
+		map[i][0] = temp;
+		temp = temp2;
+	}
+
+	//아래
+	temp = map[airCirculatorRow[1]][1];
+	map[airCirculatorRow[1]][1] = 0;
+	for (int i = 2; i < width; i++) {
+		int temp2 = map[airCirculatorRow[1]][i];
+		map[airCirculatorRow[1]][i] = temp;
+		temp = temp2;
+	}
+	for (int i = airCirculatorRow[1] + 1; i < height; i++) {
+		int temp2 = map[i][width - 1];
+		map[i][width - 1] = temp;
+		temp = temp2;
+	}
+	for (int i = width - 2; i >= 0; i--) {
+		int temp2 = map[height - 1][i];
+		map[height - 1][i] = temp;
+		temp = temp2;
+	}
+	for (int i = height - 2; i > airCirculatorRow[1]; i--) {
+		int temp2 = map[i][0];
+		map[i][0] = temp;
+		temp = temp2;
+	}
+}
+
This post is licensed under CC BY 4.0 by the author.

16954 움직이는 미로탈출

17404 RGB Distance 2

Comments powered by Disqus.

diff --git "a/posts/17387-\354\204\240\353\266\204\352\265\220\354\260\250-2/index.html" "b/posts/17387-\354\204\240\353\266\204\352\265\220\354\260\250-2/index.html" new file mode 100644 index 000000000..e54bf99f4 --- /dev/null +++ "b/posts/17387-\354\204\240\353\266\204\352\265\220\354\260\250-2/index.html" @@ -0,0 +1,157 @@ + 17387 선분교차 2 | 디피의 개발일지
Posts 17387 선분교차 2
Post
Cancel

17387 선분교차 2

알고리즘

  • 양 선분이 평행할 경우, 또는 한 쪽이 x축에 평행하고, 다른 축이 y축에 평행할 경우엔 조건문을 통해 범위를 잘 검사하여 처리

  • 나머지 경우엔 다음과 같은 방법을 사용한다.

    • 첫번째 선분을 이루는 직선이 두번째 선분을 양분하고, 그 반대도 양분하면 두 선분은 교차한다.

    • 이때 한 직선이 한 선분을 양분하는지 알아보는 식은 다음과 같다.

      f1 = (y21-y11)(x12-x11) - (x21-x11)(y12-y11)

      f2 = (y22-y11)(x12-x11) - (x22-x11)(y12-y11)

      이고, f1 * f2 < 0 일때 양분함.

    • 이때, 이 문제에서는 끝 점에 지나는 것도 교차한다고 판단하니, f1 * f2 <= 0 일때 참으로 판단하자.

      단, 이러면 평행할때도 0이 나오니, 평행 검사는 이 방법보다 먼저 하자.

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+
#include <iostream>
+typedef long long ll;
+using namespace std;
+
+struct line {
+	ll h, t, ch, ct;
+};
+
+void swap(ll* a, ll* b) {
+	ll t = *a;
+	*a = *b;
+	*b = t;
+}
+void makeLine(line* l, ll x1, ll x2, ll y1, ll y2) {
+	ll b = x2 - x1, h = y2 - y1;
+	if (b < 0 && h < 0) {
+		b *= -1;
+		h *= -1;
+	}
+
+	ll ch = b * y1 - h * x1, ct = b;
+	*l = { h, b, ch, ct };
+}
+
+bool check(ll x1, ll x2, ll x3, ll y1, ll y2, ll y3) {
+	if ((((x3 >= x1 && x3 <= x2) && (y3 >= y1 && y3 <= y2))|| ((x3 <= x1 && x3 >= x2) && (y3 <= y1 && y3 >= y2))))
+		return true;
+	return false;
+}
+
+bool is_cross(ll x1, ll x2, ll x3, ll x4, ll y1, ll y2, ll y3, ll y4) {
+	ll f1 = ((y3 - y1) * (x2 - x1)) - ((x3 - x1) * (y2 - y1)), f2 = ((y4 - y1) * (x2 - x1)) - ((x4 - x1) * (y2 - y1));
+	if ((f1 <= 0 && f2 >= 0) || (f1 >= 0 && f2 <= 0))
+		return true;
+	return false;
+}
+
+bool solve() {
+	ll x1, x2, x3, x4, y1, y2, y3, y4;
+	cin >> x1 >> y1 >> x2 >> y2 >> x3 >> y3 >> x4 >> y4;
+
+	if (x1 > x2) {
+		swap(&x1, &x2);
+		swap(&y1, &y2);
+	}
+	if (x3 > x4) {
+		swap(&x3, &x4);
+		swap(&y3, &y4);
+	}
+
+	line l1, l2;
+	makeLine(&l1, x1, x2, y1, y2);
+	makeLine(&l2, x3, x4, y3, y4);
+
+	if (l1.h * l2.t == l1.t * l2.h) {
+		if (l1.ch * l2.ct == l1.ct * l2.ch && (check(x1, x2, x3, y1, y2, y3) || check(x1, x2, x4, y1, y2, y4) || check(x3, x4, x1, y3, y4, y1) || check(x3, x4, x2, y3, y4, y2)))
+			return 1;
+		return 0;
+	}
+	else if (l1.t == 0 && l2.h == 0) {
+		if (x1 >= x3 && x1 <= x4 && ((y3 >= y1 && y3 <= y2) || (y3 <= y1 && y3 >= y2)))
+			return 1;
+		return 0;
+	}
+	else if (l2.t == 0 && l1.h == 0) {
+		if (x3 >= x1 && x3 <= x2 && ((y1 >= y3 && y1 <= y4) || (y1 <= y3 && y1 >= y4)))
+			return 1;
+		return 0;
+	}
+
+	if (is_cross(x1, x2, x3, x4, y1, y2, y3, y4) && is_cross(x3, x4, x1, x2, y3, y4, y1, y2))
+		return 1;
+	return 0;
+}
+
+int main() {
+	cout << solve();
+}
+
This post is licensed under CC BY 4.0 by the author.

11779 최소비용구하기 2

2096 내려가기

Comments powered by Disqus.

diff --git a/posts/17404/index.html b/posts/17404/index.html new file mode 100644 index 000000000..83764f548 --- /dev/null +++ b/posts/17404/index.html @@ -0,0 +1,153 @@ + 17404 RGB Distance 2 | 디피의 개발일지
Posts 17404 RGB Distance 2
Post
Cancel

17404 RGB Distance 2

17404 RGB Distance 2

알고리즘 : DP

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+
1. bottom-top 방식으로 품
+2. DP 구성 : (현재 깊이, 이전에 고른 색깔)칸에 현재 깊이에서의 최솟값 저장.
+3. DP 작동
+	a. 첫번째 집은 for문으로 하나씩 들어감.
+	b. 두번째 집부터 이전에 고른 색이 아닌 색으로 이동
+	c. 마지막집 + 1에 도착하면 0을 리턴
+	d. 마지막집은 0리턴 받은 것에 고른 색의 값 더함. 총 가능한 모든 색 값 중의 최솟값을 DP[deep][before]에 저장함
+	e. 하나씩 올라가면서 DP에 저장
+	f. DP에 저장돼있는게 있으면 바로 리턴
+4. 주의점
+	a. 첫번째 집이랑 마지막 집이랑 색깔이 달라야함 -> 첫번째 집의 색깔에 따라서도 상태가 달라짐
+		-> DP구성을 첫번째 집의 상태까지 고려하여 해야함
+		-> 하지만 나는 for문으로 첫번째 집은 하나씩 들어가고 나올때마다 DP를 초기화 해줬기에 괜찮음
+	b. 고를 수 있는 색의 조건문 구성을 조심해야함
+		before != 0 && (deep == numOfHouse -1 && first != 0)
+
+		-> 이 조건문은, deep이 마지막집이 아닐때 뒷 조건문에서 false가 나와 집이 2개가 아닌이상에야 아예 작동을 안함.
+
+		따라서 밑처럼 짜줘야한다.
+		before != 0 && (deep != numOfHouse -1 || first != 0)
+		뒷 조건문은, 마지막이 아닐 경우 true를 반환하고, 마지막집일때는 first가 0이 아니여야만 true가 나옴
+		-> 잘 만족한다.
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+
#include <iostream>
+#include <cstring>
+#define MAX_HOUSE 1001
+#define INFINITE 2000000000
+
+using namespace std;
+
+int numOfHouse, house[MAX_HOUSE][3], DP[MAX_HOUSE][3], first;
+
+int getMinCost(int before, int deep);
+int min(int a, int b) {
+	return a < b ? a : b;
+}
+
+int main() {
+	ios::sync_with_stdio(0);
+	cin.tie(0);
+
+	cin >> numOfHouse;
+	for (int i = 0; i < numOfHouse; i++) {
+		cin >> house[i][0] >> house[i][1] >> house[i][2];
+	}
+
+	int minCost = INFINITE;
+	for (int i = 0; i < 3; i++) {
+		first = i;
+		minCost = min(getMinCost(i, 1) + house[0][i], minCost);
+		memset(DP, 0, sizeof(DP));
+	}
+	cout << minCost;
+}
+
+int getMinCost(int before, int deep) {
+	if (deep == numOfHouse) {
+		return 0;
+	}
+	if (DP[deep][before] != 0) {
+		return DP[deep][before];
+	}
+
+
+
+	int a= INFINITE, b = INFINITE, c = INFINITE;
+	if (before != 0 && (deep != numOfHouse -1 || first != 0))
+		a = getMinCost(0, deep + 1) + house[deep][0];
+	if (before != 1 && (deep != numOfHouse - 1 || first != 1))
+		b = getMinCost(1, deep + 1) + house[deep][1];
+	if (before != 2 && (deep != numOfHouse - 1 || first != 2))
+		c = getMinCost(2, deep + 1) + house[deep][2];
+
+	int tempMin = min(min(a, b), c);
+
+	return DP[deep][before] = tempMin;
+}
+
This post is licensed under CC BY 4.0 by the author.

17144 Samsung sw test

1753 최단경로

Comments powered by Disqus.

diff --git a/posts/1748/index.html b/posts/1748/index.html new file mode 100644 index 000000000..7f1a68f0f --- /dev/null +++ b/posts/1748/index.html @@ -0,0 +1,55 @@ + 1748 수 이어쓰기 1 | 디피의 개발일지
Posts 1748 수 이어쓰기 1
Post
Cancel

1748 수 이어쓰기 1

1748 수 이어쓰기 1

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+
#include <iostream>
+
+using namespace std;
+
+int N;
+
+int main() {
+	cin >> N;
+
+	long long result = 0;
+
+	int d = 1, count = 0;
+	while (N / d != 0) {		// 1~9 : 1 / 10~99 : 2 / 100~999 : 3
+		d *= 10;
+		count++;
+	}
+
+	while (count != 0) {
+		d /= 10;
+		result += count * (N - d + 1);
+
+		N = d - 1;
+		count--;
+	}
+
+	cout << result;
+}
+
This post is licensed under CC BY 4.0 by the author.

17142 Samsung sw test

17779 Samsung sw test

Comments powered by Disqus.

diff --git a/posts/1753/index.html b/posts/1753/index.html new file mode 100644 index 000000000..ba8cf6166 --- /dev/null +++ b/posts/1753/index.html @@ -0,0 +1,123 @@ + 1753 최단경로 | 디피의 개발일지
Posts 1753 최단경로
Post
Cancel

1753 최단경로

1753 최단경로

알고리즘

1
+
1. 다익스트라
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+
#include <iostream>
+#include <queue>
+#include <vector>
+#define INF 2000000000;
+using namespace std;
+
+int V, E, s, length[20001];
+vector<pair<int, int>> node[20001];
+priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> q;
+bool check[20001];
+
+void dijkstra();
+
+int main() {
+	ios::sync_with_stdio(0);
+	cin.tie(0);
+	cout.tie(0);
+	cin >> V >> E >> s;
+
+	for (int i = 0; i < E; i++) {
+		int a, b, c;
+		cin >> a >> b >> c;
+		node[a].push_back({ b, c });
+	}
+
+	for (int i = 1; i <= V; i++)
+		length[i] = INF;
+
+	length[s] = 0;
+
+	dijkstra();
+
+	for (int i = 1; i <= V; i++) {
+		if (length[i] != 2000000000)
+			cout << length[i] << '\n';
+		else
+			cout << "INF\n";
+	}
+}
+
+void dijkstra() {
+	q.push({ 0, s });
+
+	while (!q.empty()) {
+		int d = q.top().first, cur = q.top().second;
+		q.pop();
+
+		if (d > length[cur] || check[cur])
+			continue;
+
+		for (int i = 0; i < node[cur].size(); i++) {
+			int next = node[cur][i].first, nd = d + node[cur][i].second;
+			if (nd < length[next]) {
+				q.push({ nd, next });
+				length[next] = nd;
+			}
+		}
+		check[cur] = true;
+	}
+}
+
This post is licensed under CC BY 4.0 by the author.

17404 RGB Distance 2

17837 새로운게임2 Samsung sw test

Comments powered by Disqus.

diff --git a/posts/17779/index.html b/posts/17779/index.html new file mode 100644 index 000000000..570c629d6 --- /dev/null +++ b/posts/17779/index.html @@ -0,0 +1,283 @@ + 17779 Samsung sw test | 디피의 개발일지
Posts 17779 Samsung sw test
Post
Cancel

17779 Samsung sw test

17779 Samsung sw test

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+
#include <iostream>
+#define MAX_WIDTH 21
+
+using namespace std;
+
+int map[MAX_WIDTH][MAX_WIDTH];
+int width;
+
+int minDifference();
+int getMin(int d1, int d2, int x, int y);
+
+int main() {
+	cin >> width;
+	for (int i = 0; i < width; i++)
+		for (int j = 0; j < width; j++)
+			cin >> map[i][j];
+
+	cout << minDifference();
+}
+
+int minDifference() {
+	int minDifference = 2000000000;
+	for (int d1 = 1; d1 < width - 1; d1++) {
+		for (int d2 = 1; d2 < width - 1; d2++) {
+			int last = width - (d1 + d2);
+			for (int x = 0; x < last; x++) {
+				for (int y = d1; y < (width - d2); y++) {
+					int temp = getMin(d1, d2, x, y);
+					if (temp < minDifference) {
+						minDifference = temp;
+					}
+				}
+			}
+		}
+	}
+
+	return minDifference;
+}
+
+int getMin(int d1, int d2, int x, int y) {
+	bool tempMap[MAX_WIDTH][MAX_WIDTH] = { 0, };
+
+	int t2 = y;
+	for (int t1 = x; t1 <= (x + d1); t1++) {
+		tempMap[t2][t1] = true;
+		t2--;
+	}
+	t2 = y;
+	for (int t1 = x; t1 <= (x + d2); t1++) {
+		tempMap[t2][t1] = true;
+		t2++;
+	}
+	t2 = y - d1;
+	for (int t1 = x + d1; t1 <= (x + d1 + d2); t1++) {
+		tempMap[t2][t1] = true;
+		t2++;
+	}
+	t2 = y + d2;
+	for (int t1 = x + d2; t1 <= (x + d2 + d1); t1++) {
+		tempMap[t2][t1] = true;
+		t2--;
+	}
+
+	int people[5] = { 0, };
+	for (int i = 0; i < y; i++) {
+		for (int j = 0; j <= (x+d1); j++) {
+			if (tempMap[i][j]) {
+				tempMap[i][j] = false;
+				break;
+			}
+			people[0] += map[i][j];
+			tempMap[i][j] = true;
+		}
+	}
+	bool checkRow = true;
+	for (int i = 0; i <= (y + d2 - d1); i++) {
+		bool check = false;
+		if (checkRow)
+			check = true;
+		for (int j = (x + d1 + 1); j < width; j++) {
+			if (j == (x + d1 + 1) && tempMap[i][j]) {
+				checkRow = false;
+			}
+
+			if (tempMap[i][j]) {
+				tempMap[i][j] = false;
+				check = true;
+			}
+			else if (check) {
+				people[1] += map[i][j];
+				tempMap[i][j] = true;
+			}
+		}
+	}
+	for (int i = y; i < width; i++) {
+		for (int j = 0; j < (x + d2); j++) {
+			if (tempMap[i][j]) {
+				tempMap[i][j] = false;
+				break;
+			}
+			people[2] += map[i][j];
+			tempMap[i][j] = true;
+		}
+	}
+	checkRow = false;
+	for (int i = (y + d2 - d1 + 1); i < width; i++) {
+		bool check = false;
+		if (checkRow)
+			check = true;
+		for (int j = (x + d2); j < width; j++) {
+			if (j == (x + d2) && tempMap[i][j]) {
+				checkRow = true;
+			}
+
+			if (tempMap[i][j]) {
+				check = true;
+				tempMap[i][j] = false;
+			}
+			else if (check) {
+				people[3] += map[i][j];
+				tempMap[i][j] = true;
+			}
+		}
+	}
+	for (int i = 0; i < width; i++) {
+		for (int j = 0; j < width; j++) {
+			if (!tempMap[i][j])
+				people[4] += map[i][j];
+		}
+	}
+
+	int minN = 200000000;
+	int maxN = 0;
+	for (int i = 0; i < 5; i++) {
+		if (people[i] < minN)
+			minN = people[i];
+		if (people[i] > maxN)
+			maxN = people[i];
+	}
+	return maxN - minN;
+}
+
This post is licensed under CC BY 4.0 by the author.

1748 수 이어쓰기 1

17822 Samsung sw test

Comments powered by Disqus.

diff --git a/posts/17822/index.html b/posts/17822/index.html new file mode 100644 index 000000000..f66fd40a2 --- /dev/null +++ b/posts/17822/index.html @@ -0,0 +1,275 @@ + 17822 Samsung sw test | 디피의 개발일지
Posts 17822 Samsung sw test
Post
Cancel

17822 Samsung sw test

17822 Samsung sw test

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+
#include <iostream>
+#include <cstring>
+#include <vector>
+#define MAX_BOARD 50
+#define MAX_TURN 50
+
+using namespace std;
+
+int board[MAX_BOARD][MAX_BOARD];
+int numOfTurn, numOfBoard, numOfNum;
+int turn[MAX_TURN][3];	//xi, 0 or 1, ki
+
+int getSumOfBoardNum();
+void rotateBoard(vector<int> rotate, int direction, int ki);
+void updateBoard();
+
+int main() {
+	cin >> numOfBoard >> numOfNum >> numOfTurn;
+	for (int i = 0; i < numOfBoard; i++) {
+		for (int j = 0; j < numOfNum; j++) {
+			cin >> board[i][j];
+		}
+	}
+	for (int i = 0; i < numOfTurn; i++) {
+		cin >> turn[i][0] >> turn[i][1] >> turn[i][2];
+	}
+
+	cout << getSumOfBoardNum();
+}
+
+int getSumOfBoardNum() {
+	for (int current = 0; current < numOfTurn; current++) {
+		int xi = turn[current][0];
+		vector<int> rotate;
+		for (int i = 0; i < numOfBoard; i++) {
+			if (((i + 1) % xi) == 0)
+				rotate.push_back(i);
+		}
+
+		rotateBoard(rotate, turn[current][1], turn[current][2]);
+		updateBoard();
+	}
+
+	int sum = 0;
+	for (int i = 0; i < numOfBoard; i++)
+		for (int j = 0; j < numOfNum; j++)
+			sum += board[i][j];
+	return sum;
+}
+
+void rotateBoard(vector<int> rotate, int direction, int ki) {
+	if (direction == 0) {	//시계
+		for (int i = 0; i < rotate.size(); i++) {
+			for (int k = 0; k < ki; k++) {
+				int temp = board[rotate[i]][0];
+				for (int j = 1; j < numOfNum; j++) {
+					int temp2 = board[rotate[i]][j];
+					board[rotate[i]][j] = temp;
+					temp = temp2;
+				}
+				board[rotate[i]][0] = temp;
+			}
+		}
+	}
+	else if (direction == 1) {
+		for (int i = 0; i < rotate.size(); i++) {
+			for (int k = 0; k < ki; k++) {
+				int temp = board[rotate[i]][numOfNum-1];
+				for (int j = numOfNum-1; j >= 0; j--) {
+					int temp2 = board[rotate[i]][j];
+					board[rotate[i]][j] = temp;
+					temp = temp2;
+				}
+				board[rotate[i]][numOfNum-1] = temp;
+			}
+		}
+	}
+}
+
+void updateBoard() {
+	int tempBoard[MAX_BOARD][MAX_BOARD];
+	bool check = false;
+	for (int i = 0; i < numOfBoard; i++) {
+		if (board[i][0] == board[i][1] || board[i][0] == board[i][numOfNum - 1] ||
+			(i != 0 && board[i][0] == board[i - 1][0]) || (i != (numOfBoard - 1) && board[i][0] == board[i + 1][0])) {
+			tempBoard[i][0] = 0;
+			if(board[i][0] != 0) check = true;
+		}
+		else
+			tempBoard[i][0] = board[i][0];
+
+		for (int j = 1; j < numOfNum - 1; j++) {
+			if (board[i][j] == board[i][j + 1] || board[i][j] == board[i][j - 1] ||
+				(i != 0 && board[i][j] == board[i - 1][j]) || (i != (numOfBoard - 1) && board[i][j] == board[i + 1][j])) {
+				tempBoard[i][j] = 0;
+				if(board[i][j] != 0) check = true;
+			}
+			else
+				tempBoard[i][j] = board[i][j];
+		}
+
+		if (board[i][numOfNum - 1] == board[i][0] || board[i][numOfNum - 1] == board[i][numOfNum - 2] ||
+			(i != 0 && board[i][numOfNum - 1] == board[i - 1][numOfNum - 1]) || (i != (numOfBoard - 1) && board[i][numOfNum - 1] == board[i + 1][numOfNum - 1])) {
+			tempBoard[i][numOfNum - 1] = 0;
+			if(board[i][numOfNum - 1] != 0) check = true;
+		}
+		else
+			tempBoard[i][numOfNum - 1] = board[i][numOfNum - 1];
+	}
+	if (!check) {
+		int sum = 0;
+		int count = 0;
+		for (int i = 0; i < numOfBoard; i++) {
+			for (int j = 0; j < numOfNum; j++) {
+				if (tempBoard[i][j] != 0) {
+					sum += tempBoard[i][j];
+					count++;
+				}
+			}
+		}
+		if (count != 0) {
+			float ave = (float)sum / (float)count;
+			for(int i = 0; i<numOfBoard; i++) {
+				for (int j = 0; j < numOfNum; j++) {
+					if (tempBoard[i][j] != 0) {
+						if (tempBoard[i][j] < ave)
+							tempBoard[i][j]++;
+						else if (tempBoard[i][j] > ave)
+							tempBoard[i][j]--;
+					}
+				}
+			}
+		}
+	}
+	memcpy(board, tempBoard, sizeof(tempBoard));
+
+}
+
This post is licensed under CC BY 4.0 by the author.

17779 Samsung sw test

17825 Samsung sw test

Comments powered by Disqus.

diff --git a/posts/17825/index.html b/posts/17825/index.html new file mode 100644 index 000000000..4fc6f9f5b --- /dev/null +++ b/posts/17825/index.html @@ -0,0 +1,147 @@ + 17825 Samsung sw test | 디피의 개발일지
Posts 17825 Samsung sw test
Post
Cancel

17825 Samsung sw test

17825 Samsung sw test

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+
#include <iostream>
+#include <vector>
+using namespace std;
+
+typedef struct Map {
+	int score;
+	int next;
+	int blueNext;
+} Map;
+
+int dice[10];
+Map map[33] = { {0, 1, -1},{2, 2, -1}, {4, 3, -1}, {6,4 , -1}, {8, 5, -1},
+				{10, 6, 21}, {12, 7, -1}, {14, 8, -1}, {16, 9, -1}, {18, 10, -1},
+				{20, 11, 24 }, { 22, 12, -1 }, { 24, 13, -1 }, { 26, 14, -1 }, { 28, 15, -1 },
+				{30, 16, 26}, {32, 17, -1}, {34, 18, -1}, {36, 19, -1}, {38, 20, -1},
+				{40, 32, -1}, {13, 22, -1}, {16, 23, -1}, {19, 29, -1}, {22, 25, -1},
+				{24, 29, -1}, {28, 27, -1}, {27, 28, -1}, {26, 29, -1}, {25, 30, -1},
+				{30, 31, -1}, {35, 20, -1}, {0, -1, -1} };
+
+int getScore(int piece[4], int score, int deep);
+
+int main() {
+	for (int i = 0; i < 10; i++)
+		cin >> dice[i];
+
+	int temp[4] = { 0,0,0,0 };
+	cout << getScore(temp, 0, 0);
+
+}
+
+int getScore(int piece[4], int score, int deep) {
+	if (deep == 10)
+		return score;
+	int maxScore = 0;
+	for (int i = 0; i < 4; i++) {
+		int current = piece[i];
+		if (current >= 32)
+			continue;
+		int next = map[current].blueNext != -1 ? map[current].blueNext : map[current].next;
+		for (int j = 0; j < dice[deep]; j++) {
+			current = next;
+			if (current == 32)
+				break;
+			next = map[current].next;
+		}
+
+		int temp[4];
+		for (int j = 0; j < 4; j++) {
+			if (i != j)
+				temp[j] = piece[j];
+			else
+				temp[j] = current;
+		}
+
+		if (current == 32) {
+			int a = getScore(temp, score, deep + 1);
+			maxScore = a > maxScore ? a : maxScore;
+			continue;
+		}
+
+		bool check = false;
+		for (int j = 0; j < 4; j++) {
+			if (piece[j] == current)
+				check = true;
+		}
+		if (!check) {
+			int a = getScore(temp, score + map[current].score, deep + 1);
+			maxScore = a > maxScore ? a : maxScore;
+		}
+	}
+
+	return maxScore;
+}
+
This post is licensed under CC BY 4.0 by the author.

17822 Samsung sw test

1799 bishop

Comments powered by Disqus.

diff --git a/posts/17837/index.html b/posts/17837/index.html new file mode 100644 index 000000000..921534f1b --- /dev/null +++ b/posts/17837/index.html @@ -0,0 +1,367 @@ + 17837 새로운게임2 Samsung sw test | 디피의 개발일지
Posts 17837 새로운게임2 Samsung sw test
Post
Cancel

17837 새로운게임2 Samsung sw test

17837 새로운게임2 Samsung sw test

알고리즘(구현)

1
+2
+3
+4
+5
+6
+7
+8
+9
+
1. 색깔을 저장한 map, 말을 저장하는 3차원 배열 맵(각 칸마다 4칸만 있으면 됨), 각 칸에 말이 몇개있는지 저장하는 인덱스맵
+2. 턴을 계속 진행함.
+	각 말을 순차적으로 검사함. 이때 각 말들을 몇 번 검사했는지 저장하는 temp 배열을 생성.
+		방향과 색깔에 맞춰 이동 시킴.
+			-두번째면 그냥 넘기고 다음 거 검사
+			-빨강이면 위에서부터 옆으로 이동시킴
+			-흰색이면 현재 말부터 옆으로 이동시킴
+			-파랑이나 맵 밖이면 방향을 바꾸고 현재말을 다시 검사함. 단, 이미 두번째 검사라면 방향은 변화시키지 않음.
+			-어느 순간이든, 4개가 되면 그때의 turn을 리턴.
+

주의점

1
+2
+
-파랑이나 맵 밖이면, 방향을 바꿀때, 두번째 검사라면 방향을 변화시키면 안된다.
+	-> 현재거 밑에 뭔가가 있을 수 있고, 그 밑에 있는게 방향이 현재 말과 다르다면 이동 후 크게 달라질 수 있기 때문에.
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+
#include <iostream>
+#include <cstring>
+#include <vector>
+#define MAX_WIDTH 13
+
+using namespace std;
+
+typedef struct Piece {
+	int row;
+	int col;
+	int direction; // 1: 오 2 : 왼 3 : 위 4 : 아
+} Piece;
+
+Piece piece[11];
+int map[MAX_WIDTH][MAX_WIDTH];
+int pieceMap[MAX_WIDTH][MAX_WIDTH][4];
+int indexMap[MAX_WIDTH][MAX_WIDTH];
+int width, numOfPiece;
+
+int getLastTurnNum();
+
+int main() {
+	cin >> width >> numOfPiece;
+	for (int i = 0; i < width; i++) {
+		for (int j = 0; j < width; j++) {
+			cin >> map[i][j];
+		}
+	}
+	memset(pieceMap, -1, sizeof(pieceMap));
+	memset(indexMap, 0, sizeof(indexMap));
+	for (int i = 0; i < numOfPiece; i++) {
+		cin >> piece[i].row >> piece[i].col >> piece[i].direction;
+		piece[i].row--;
+		piece[i].col--;
+		pieceMap[piece[i].row][piece[i].col][indexMap[piece[i].row][piece[i].col]++] = i;
+	}
+
+	cout << getLastTurnNum();
+}
+
+int getLastTurnNum() {
+	for (int turn = 1; turn <= 1000; turn++) {
+		int temp[10];
+		memset(temp, 0, sizeof(temp));
+
+		for (int i = 0; i < numOfPiece; i++) {
+			if (temp[i] >= 2)
+				continue;
+			else
+				temp[i]++;
+
+			int row = piece[i].row;
+			int col = piece[i].col;
+			int stackIndex = 0;
+			int lastIndex = indexMap[row][col];
+			for (stackIndex; stackIndex < lastIndex; stackIndex++)
+				if (pieceMap[row][col][stackIndex] == i)
+					break;
+
+			if (piece[i].direction == 1) {
+				if (col >= (width - 1) || map[row][col + 1] == 2) {
+					if(temp[i] != 2)
+						piece[i].direction = 2;
+					i--;
+				}
+				else if (map[row][col + 1] == 1) {
+					for (int j = lastIndex - 1; j >= stackIndex; j--) {
+						pieceMap[row][col + 1][indexMap[row][col + 1]++] = pieceMap[row][col][j];
+						if (indexMap[row][col + 1] >= 4)
+							return turn;
+						piece[pieceMap[row][col][j]].col++;
+						pieceMap[row][col][j] = -1;
+						indexMap[row][col]--;
+					}
+				}
+				else if (map[row][col + 1] == 0) {
+					for (int j = stackIndex; j < lastIndex; j++) {
+						pieceMap[row][col + 1][indexMap[row][col + 1]++] = pieceMap[row][col][j];
+						if (indexMap[row][col + 1] >= 4)
+							return turn;
+						piece[pieceMap[row][col][j]].col++;
+						pieceMap[row][col][j] = -1;
+						indexMap[row][col]--;
+					}
+				}
+			}
+			else if (piece[i].direction == 2) {
+				if (col <= 0 || map[row][col - 1] == 2) {
+					if (temp[i] != 2)
+						piece[i].direction = 1;
+					i--;
+				}
+				else if (map[row][col - 1] == 1) {
+					for (int j = lastIndex - 1; j >= stackIndex; j--) {
+						pieceMap[row][col - 1][indexMap[row][col - 1]++] = pieceMap[row][col][j];
+						if (indexMap[row][col - 1] >= 4)
+							return turn;
+						piece[pieceMap[row][col][j]].col--;
+						pieceMap[row][col][j] = -1;
+						indexMap[row][col]--;
+					}
+				}
+				else if (map[row][col - 1] == 0) {
+					for (int j = stackIndex; j < lastIndex; j++) {
+						pieceMap[row][col - 1][indexMap[row][col - 1]++] = pieceMap[row][col][j];
+						if (indexMap[row][col - 1] >= 4)
+							return turn;
+						piece[pieceMap[row][col][j]].col--;
+						pieceMap[row][col][j] = -1;
+						indexMap[row][col]--;
+					}
+				}
+			}
+			else if (piece[i].direction == 3) {
+				if (row <= 0 || map[row-1][col] == 2) {
+					if (temp[i] != 2)
+						piece[i].direction = 4;
+					i--;
+				}
+				else if (map[row-1][col] == 1) {
+					for (int j = lastIndex - 1; j >= stackIndex; j--) {
+						pieceMap[row-1][col][indexMap[row-1][col]++] = pieceMap[row][col][j];
+						if (indexMap[row-1][col] >= 4)
+							return turn;
+						piece[pieceMap[row][col][j]].row--;
+						pieceMap[row][col][j] = -1;
+						indexMap[row][col]--;
+					}
+				}
+				else if (map[row-1][col] == 0) {
+					for (int j = stackIndex; j < lastIndex; j++) {
+						pieceMap[row - 1][col][indexMap[row - 1][col]++] = pieceMap[row][col][j];
+						if (indexMap[row - 1][col] >= 4)
+							return turn;
+						piece[pieceMap[row][col][j]].row--;
+						pieceMap[row][col][j] = -1;
+						indexMap[row][col]--;
+					}
+				}
+			}
+			else if(piece[i].direction == 4) {
+				if (row >= (width - 1) || map[row+1][col] == 2) {
+					if (temp[i] != 2)
+						piece[i].direction = 3;
+					i--;
+				}
+				else if (map[row+1][col] == 1) {
+					for (int j = lastIndex - 1; j >= stackIndex; j--) {
+						pieceMap[row + 1][col][indexMap[row + 1][col]++] = pieceMap[row][col][j];
+						if (indexMap[row + 1][col] >= 4)
+							return turn;
+						piece[pieceMap[row][col][j]].row++;
+						pieceMap[row][col][j] = -1;
+						indexMap[row][col]--;
+					}
+				}
+				else if (map[row+1][col] == 0) {
+					for (int j = stackIndex; j < lastIndex; j++) {
+						pieceMap[row + 1][col][indexMap[row + 1][col]++] = pieceMap[row][col][j];
+						if (indexMap[row + 1][col] >= 4)
+							return turn;
+						piece[pieceMap[row][col][j]].row++;
+						pieceMap[row][col][j] = -1;
+						indexMap[row][col]--;
+					}
+				}
+			}
+		}
+	}
+
+	return -1;
+}
+
This post is licensed under CC BY 4.0 by the author.

1753 최단경로

1806 partial sum

Comments powered by Disqus.

diff --git a/posts/1799/index.html b/posts/1799/index.html new file mode 100644 index 000000000..a42e2e9c8 --- /dev/null +++ b/posts/1799/index.html @@ -0,0 +1,111 @@ + 1799 bishop | 디피의 개발일지
Posts 1799 bishop
Post
Cancel

1799 bishop

1799 bishop

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+
#include <iostream>
+#include <cstring>
+#define MAX_N 10
+
+using namespace std;
+
+int map[MAX_N][MAX_N];
+bool tempMap[MAX_N][MAX_N] = { 0, };
+int width, maxCount = 0;
+
+void findMaxBishop(int diagonal, int count);
+bool check(int row, int col);
+
+int main() {
+	cin >> width;
+	for (int i = 0; i < width; i++)
+		for (int j = 0; j < width; j++)
+			cin >> map[i][j];
+	findMaxBishop(0, 0);
+	cout << maxCount;
+}
+
+void findMaxBishop(int diagonal, int count) {
+	if (diagonal == 2 * width - 1) {
+		maxCount = maxCount > count ? maxCount : count;
+		return;
+	}
+	if ((2 * (width - 1)) - diagonal < maxCount - count)
+		return;
+
+	int row = 0, col = 0;
+	if (diagonal < width)
+		col = diagonal;
+	else {
+		row = diagonal - width + 1;
+		col = width-1;
+	}
+
+	for (; row < width && col >= 0; row++) {
+		if (map[row][col] == 1 && check(row - 1, col - 1)) {
+			tempMap[row][col] = true;
+			findMaxBishop(diagonal + 1, count + 1);
+			tempMap[row][col] = false;
+		}
+		col--;
+	}
+	findMaxBishop(diagonal + 1, count);
+}
+
+bool check(int row, int col) {
+	for (; row >= 0 && col >= 0; row--)
+		if (tempMap[row][col--])
+			return false;
+	return true;
+}
+
This post is licensed under CC BY 4.0 by the author.

17825 Samsung sw test

19236 Samsung sw test

Comments powered by Disqus.

diff --git a/posts/1806/index.html b/posts/1806/index.html new file mode 100644 index 000000000..a00baa6f4 --- /dev/null +++ b/posts/1806/index.html @@ -0,0 +1,127 @@ + 1806 partial sum | 디피의 개발일지
Posts 1806 partial sum
Post
Cancel

1806 partial sum

1806 partial sum

알고리즘 (브루트포스)

1
+2
+3
+4
+5
+6
+
1. 길이를 1부터 N까지 검사
+2. 한 길이를 검사할 때 다음을 실행
+	1. 수열의 처음부터 길이만큼 더하고 sum에 저장, 첫번째를 따로 first에 저장. -> S를 넘는지 확인하고 안넘으면 2 실행
+	2. 수열의 (길이)번째부터 검사. 위에서 구한 sum에서 first를 빼고, 현재의 수열원소를 집어넣음. -> 검사 -> 아니면 first에 두번째 저장
+3. 다 했는데도 안되면 0 반환
+-> 통과는 되지만, 아주 느림
+

알고리즘(두 포인터)

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+
1. length에 최대 길이를 저장, first = 0, last = 1, sum = num[0] 를 저장.
+2. first <= last 이고 last <= N일때 반복
+	1. sum이 S보다 작을 때
+		sum += num[last++]
+	2. sum이 S와 같을 때
+		length = min(length, last-first)
+		sum += num[last++]
+	3. sum이 S보다 클 떄
+		length = min(length, last - first)
+		sum -= num[first++]
+
+3. 쉽게 얘기해서 검사할 길이의 처음과 끝을 표현하는 두 변수를 선언하고,
+	끝을 하나씩 늘려가면서 sum을 구하되, sum이 S와 같아지면 길이를 저장하고 끝을 하나 더 늘리고, 커지면 길이를 저장하고 처음을 하나 줄이는 식으로 계속 반복.
+-> 훨씬 빠르다.
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+
#include <iostream>
+#define MAX_N 100000
+
+using namespace std;
+
+int num[MAX_N];
+int sumNum[MAX_N];
+int N, S;
+
+int shortestSequence(int start);
+
+int main() {
+	cin >> N >> S;
+	for (int i = 0; i < N; i++)
+		cin >> num[i];
+
+	int start = S / 10000;
+	cout << shortestSequence(start);
+}
+
+
+int shortestSequence(int start) {
+	sumNum[0] = num[0];
+	for (int i = 1; i < N; i++) {
+		sumNum[i] = sumNum[i - 1] + num[i];
+	}
+
+	for (int i = start; i <= N; i++) {
+		int first = num[0];
+		int sum = sumNum[i - 1];
+		if (sum >= S)
+			return i;
+
+		for (int j = i; j < N; j++) {
+			sum -= first;
+			sum += num[j];
+			if (sum >= S)
+				return i;
+			first = num[j - i + 1];
+		}
+	}
+	return 0;
+}
+
This post is licensed under CC BY 4.0 by the author.

17837 새로운게임2 Samsung sw test

1865 웜홀

Comments powered by Disqus.

diff --git "a/posts/18500-\353\257\270\353\204\244\353\236\204-2/index.html" "b/posts/18500-\353\257\270\353\204\244\353\236\204-2/index.html" new file mode 100644 index 000000000..a413cea12 --- /dev/null +++ "b/posts/18500-\353\257\270\353\204\244\353\236\204-2/index.html" @@ -0,0 +1,273 @@ + 18500 미네랄 2 | 디피의 개발일지
Posts 18500 미네랄 2
Post
Cancel

18500 미네랄 2

알고리즘

  • 2933 미네랄과 상당히 유사한 문제이지만, “분리된 클러스터의 각 열중 맨 아래 부분이 아닌 부분이 다른 클러스터 위에 떨어질 수 있다”라는 조건을 하나 더 생각해야함.
  • 막대를 던지고 클러스터가 분리되면 바닥에 닿았는지 판단.
  • 닿지 않았다면 아래로 내리는데, 내려갈 수 있는 미네랄을 다 검사.
  • 가장 적게 내려갈 수 있는 거리만큼 클러스터를 내림.

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+
#include <iostream>
+#include <list>
+#include <algorithm>
+using namespace std;
+
+int R, C, listIdx;
+char map[101][101];
+int offset[4][2] = { {-1, 0}, {0,1}, {1, 0}, {0, -1} };
+bool visit[101][101];
+pair<int, int> l[10010];
+
+void throwStick(int h, bool toRight);
+bool isTouchFloor(int h, int w);
+void downCluster();
+
+int main() {
+	string t;
+	cin >> R >> C;
+	for (int i = 0; i < R; i++) {
+		cin >> t;
+		for (int j = 0; j < C; j++) {
+			map[i][j] = t[j];
+		}
+	}
+
+	int n, input;
+	bool toRight = true;
+	cin >> n;
+	while (n--) {
+		cin >> input;
+		throwStick(input, toRight);
+		toRight = !toRight;
+	}
+	for (int i = 0; i < R; i++) {
+		for (int j = 0; j < C; j++)
+			cout << map[i][j];
+		cout << endl;
+	}
+}
+
+// 던지고 맞으면, 맞은 거부터 상하좌우 검색하여 클러스터 찾고 내리기.
+void throwStick(int h, bool toRight) {
+	bool hit = false;
+	int w = 0;
+	h = R - h;
+	if (toRight) {
+		for (int i = 0; i < C; i++)
+			if (map[h][i] == 'x') {
+				hit = true;
+				w = i;
+				break;
+			}
+	}
+	else {
+		for (int i = C - 1; i >= 0; i--)
+			if (map[h][i] == 'x') {
+				hit = true;
+				w = i;
+				break;
+			}
+	}
+
+	if (!hit)
+		return;
+
+	map[h][w] = '.';
+
+	for (int i = 0; i < 4; i++) {
+		int ho = h + offset[i][0], wo = w + offset[i][1];
+		if (ho >= 0 && ho < R && wo >= 0 && wo < C && map[ho][wo] == 'x') {
+			if (!isTouchFloor(ho, wo))
+				downCluster();
+			for (int i = 0; i < listIdx; i++)
+				visit[l[i].first][l[i].second] = false;
+			listIdx = 0;
+		}
+	}
+}
+
+
+bool isTouchFloor(int h, int w) {
+	int curIdx = 0;
+	listIdx = 0;
+	l[listIdx++] = { h,w };
+	visit[h][w] = true;
+
+	bool touch = false;
+
+	while (curIdx != listIdx) {
+		int h = l[curIdx].first, w=l[curIdx].second;
+		curIdx++;
+
+		if (h == R - 1) {
+			touch = true;
+			break;
+		}
+
+		for (int i = 0; i < 4; i++) {
+			int ho = h + offset[i][0], wo = w + offset[i][1];
+			if (!visit[ho][wo] && ho >= 0 && ho < R && wo >= 0 && wo < C && map[ho][wo] == 'x') {
+				l[listIdx++] = { ho, wo };
+				visit[ho][wo] = true;
+			}
+		}
+	}
+
+	if (touch)
+		return true;
+	return false;
+}
+
+//밑에 딴게 없는 것만 검사 -> 몇칸 내려가면 되는지 추산하기
+
+void downCluster() {
+	sort(&l[0], &l[listIdx]);
+	int minDistance = 2000000000;
+
+	for (int s = listIdx - 1; s >= 0; s--) {
+		if (map[l[s].first + 1][l[s].second] != 'x') {
+			int fromH = l[s].first + 1;
+			for (; fromH < R; fromH++) {
+				if (map[fromH][l[s].second] == 'x') {
+					if (visit[fromH][l[s].second])
+						fromH = 2000000000;
+					break;
+				}
+			}
+			minDistance = min(minDistance, fromH - l[s].first -1);
+
+		}
+	}
+	for (int s = listIdx - 1; s >= 0; s--) {
+		map[l[s].first + minDistance][l[s].second] = 'x';
+		map[l[s].first][l[s].second] = '.';
+	}
+}
+
This post is licensed under CC BY 4.0 by the author.

10021 watering the field

16639 괄호 추가하기 3

Comments powered by Disqus.

diff --git a/posts/1865/index.html b/posts/1865/index.html new file mode 100644 index 000000000..a6ffb022c --- /dev/null +++ b/posts/1865/index.html @@ -0,0 +1,137 @@ + 1865 웜홀 | 디피의 개발일지
Posts 1865 웜홀
Post
Cancel

1865 웜홀

1865 웜홀

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+
#include <bits/stdc++.h>
+
+using namespace std;
+
+int tc, N, M, W, road[501][501];
+long long d[501];
+bool check[501][501];
+vector<int> connect[501];
+
+bool belmanford();
+
+int main() {
+	ios::sync_with_stdio(0);
+	cin.tie(0);
+	cout.tie(0);
+
+	cin >> tc;
+
+	while (tc--) {
+		cin >> N >> M >> W;
+		int a, b, c;
+
+		for (int i = 1; i <= N; i++) {
+			d[i] = 2000000000000;
+			connect[i].clear();
+		}
+		d[1] = 0;
+		memset(check, 0, sizeof(check));
+
+		for (int i = 0; i < M; i++) {
+			cin >> a >> b >> c;
+			connect[a].push_back(b);
+			connect[b].push_back(a);
+			if (check[a][b])
+				road[a][b] = min(road[a][b], c);
+			else
+				road[a][b] = c;
+			road[b][a] = road[a][b];
+			check[a][b] = true;
+			check[b][a] = true;
+		}
+		for (int i = 0; i < W; i++) {
+			cin >> a >> b >> c;
+			connect[a].push_back(b);
+			road[a][b] = min(road[a][b], -c);
+		}
+		if (belmanford())
+			cout << "YES\n";
+		else
+			cout << "NO\n";
+	}
+}
+
+bool belmanford() {
+	for (int i = 0; i < N - 1; i++) {
+		for (int j = 1; j <= N; j++) {
+			for (int k = 0; k < connect[j].size(); k++)
+				d[connect[j][k]] = min(d[connect[j][k]], d[j] + road[j][connect[j][k]]);
+		}
+	}
+
+	for (int j = 1; j <= N; j++) {
+		for (int k = 0; k < connect[j].size(); k++)
+			if (d[connect[j][k]] > d[j] + road[j][connect[j][k]])
+				return true;
+	}
+	return false;
+}
+
This post is licensed under CC BY 4.0 by the author.

1806 partial sum

19235 모노미노도미노

Comments powered by Disqus.

diff --git "a/posts/1890-\354\240\220\355\224\204/index.html" "b/posts/1890-\354\240\220\355\224\204/index.html" new file mode 100644 index 000000000..741886df3 --- /dev/null +++ "b/posts/1890-\354\240\220\355\224\204/index.html" @@ -0,0 +1,65 @@ + 1890 점프 | 디피의 개발일지
Posts 1890 점프
Post
Cancel

1890 점프

알고리즘

  • save[0][0] = 1 부터 시작해서, 모든 점을 순서대로 방문하며 자신의 위, 왼쪽 점들 중 자신에게로 올 수 있는 모든 점들을 save[i][j]에 저장한다.

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+
#include <iostream>
+typedef long long ll;
+using namespace std;
+
+int n, map[101][101];
+ll save[101][101];
+
+ll dp();
+
+int main() {
+	cin >> n;
+	for (int i = 0; i < n; i++)
+		for (int j = 0; j < n; j++)
+			cin >> map[i][j];
+	cout << dp();
+}
+
+ll dp() {
+	save[0][0] = 1;
+	for (int i = 0; i < n; i++) {
+		for (int j = 0; j < n; j++) {
+			for (int ki = i - 1; ki >= 0; ki--)
+				if (map[ki][j] == i - ki)
+					save[i][j] += save[ki][j];
+			for (int kj = j - 1; kj >= 0; kj--)
+				if (map[i][kj] == j - kj)
+					save[i][j] += save[i][kj];
+		}
+	}
+
+	return save[n - 1][n - 1];
+}
+
This post is licensed under CC BY 4.0 by the author.

11048 이동하기

15989 1,2,3 더하기 4

Comments powered by Disqus.

diff --git "a/posts/1918-\355\233\204\354\234\204\355\221\234\352\270\260\354\213\235/index.html" "b/posts/1918-\355\233\204\354\234\204\355\221\234\352\270\260\354\213\235/index.html" new file mode 100644 index 000000000..35dffc399 --- /dev/null +++ "b/posts/1918-\355\233\204\354\234\204\355\221\234\352\270\260\354\213\235/index.html" @@ -0,0 +1,149 @@ + 1918 후위표기식 | 디피의 개발일지
Posts 1918 후위표기식
Post
Cancel

1918 후위표기식

알고리즘

  • 괄호치기
    • / 는 앞에서부터 가까운 두 괄호를 묶음.
    • 그다음 + - 로 마찬가지로 함.
    • 양쪽에 모두 없으면 패스함
  • 출력
    • 여는괄호, 연산자는 스택에 넣기.
    • 닫는 괄호가 나오면 여는괄호가 나올때까지 쭉 빼기.
    • 알파벳은 그냥 출력.

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+
#include <iostream>
+#include <string>
+#include <stack>
+
+using namespace std;
+
+string first;
+char result[101];
+int resultl;
+stack<char> s;
+
+void bind(char a, char b);
+
+int main() {
+	cin >> first;
+
+	bind('*', '/');
+	bind('+', '-');
+	for (int i = 0; i < first.size(); i++) {
+		if (first[i] == '(' || first[i] == '*' || first[i] == '+' || first[i] == '-' || first[i] == '/')
+			s.push(first[i]);
+		else if (first[i] == ')') {
+			while (s.top() != '(') {
+				cout << s.top();
+				s.pop();
+			}
+			s.pop();
+		}
+		else
+			cout << first[i];
+	}
+}
+
+void bind(char a, char b) {
+	for (int i = 0; i < first.size(); i++) {
+		int lput = -200, rput = -200, length = first.size(), lc = 0, rc = 0;
+		if (first[i] == a || first[i] == b) {
+			for (int j = i - 1; j >= 0; j--) {
+				if (first[j] == ')')
+					lc++;
+				else if (first[j] == '(' && lc > 0)
+					lc--;
+				if (lc == 0 && (first[j] == '(' || (first[j] >= 'A' && first[j] <= 'Z'))) {
+					lput = j - 1;
+					break;
+				}
+			}
+			for (int j = i + 1; j < length; j++) {
+				if (first[j] == '(')
+					rc++;
+				else if (first[j] == ')' && rc > 0)
+					rc--;
+				if (rc == 0 && (first[j] == ')' || (first[j] >= 'A' && first[j] <= 'Z'))) {
+					rput = j + 1;
+					break;
+				}
+			}
+
+			if (lput == -200 || rput == -200)
+				continue;
+
+			if (lput < 0)
+				first = "(" + first;
+			else
+				first = first.substr(0, lput + 1) + "(" + first.substr(lput + 1, length);
+
+			if (rput == length)
+				first = first + ")";
+			else
+				first = first.substr(0, rput + 1) + ")" + first.substr(rput + 1, first.size());
+			i++;
+		}
+	}
+}
+
This post is licensed under CC BY 4.0 by the author.

expo 배포

타입스크립트

Comments powered by Disqus.

diff --git a/posts/19235/index.html b/posts/19235/index.html new file mode 100644 index 000000000..a194f9339 --- /dev/null +++ b/posts/19235/index.html @@ -0,0 +1,561 @@ + 19235 모노미노도미노 | 디피의 개발일지
Posts 19235 모노미노도미노
Post
Cancel

19235 모노미노도미노

19235 모노미노도미노

알고리즘(구현)

1
+2
+3
+4
+5
+6
+7
+
1. 파란 맵과 초록 맵을 따로 만듬.(bool 형으로 해서 true면 블록이 있고, false면 없는 거로 취급)
+2. 매 실행마다 블록을 잘 놓고 -> 업데이트 하면서 점수 얻고 -> 연한부분 있으면 당긴 다음에 -> 다음 실행
+3. 실행마다 얻은 점수를 잘 기록해서 리턴함. 그리고 남은 블록 수를 세서 출력.
+4. 단, 모노미노도미노 1인 경우 - bool형이 아닌 int형으로 하고 0이면 블록 없음, 1 이상이면 각 블록을 나타내서, 옮길 때 어떤 블록이 있는지 어떤 것끼리 같은지 확인해야함
+5. 점수가 나서 내릴 때, 파란부분이면 점수 난 열부터 왼쪽으로 열순으로 옮기기 / 녹색부분이면 점수난 행부터 위쪽으로 행순으로 옮긴다.
+6. 이때, 1x1블록이나 파란부분이면 1x2블록, 녹색부분이면 2x1블록은 그냥 옮겨도 괜찮으나, 파란부분-2x1블록, 녹색부분-1x2블록은 잘 검사하여 옮긴다.
+7. 그리고 블록을 만날때까지 옮기고나서는 break문을 써서 더 검사하지 않도록 한다.
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+
#include <iostream>
+#include <cstring>
+#define MAX_TILE 10000
+#define WIDTH 6
+#define HEIGHT 4
+
+using namespace std;
+
+typedef struct Tile {
+	int type;
+	int row;
+	int col;
+} Tile;
+
+int blueMap[HEIGHT][WIDTH];
+int greenMap[WIDTH][HEIGHT];
+int numOfTile;
+Tile tile[MAX_TILE];
+
+int getScore();
+void putTile(int index);
+void putTile(int row, int col, int map);
+int updateMap();
+int getRestTile();
+
+int main() {
+	cin >> numOfTile;
+	for (int i = 0; i < numOfTile; i++) {
+		cin >> tile[i].type >> tile[i].row >> tile[i].col;
+	}
+	memset(blueMap, 0, sizeof(blueMap));
+	memset(greenMap, 0, sizeof(blueMap));
+
+	cout << getScore() << endl;
+	cout << getRestTile() << endl;
+}
+
+int getScore() {
+	int score = 0;
+	for (int i = 0; i < numOfTile; i++) {
+		putTile(i);
+		score += updateMap();
+	}
+
+	return score;
+}
+
+void putTile(int index) {
+	if (tile[index].type == 1) {
+		for (int i = 2; i < WIDTH; i++) {
+			if (blueMap[tile[index].row][i] != 0) {
+				blueMap[tile[index].row][i - 1] = index+1;
+				break;
+			}
+			else if (i == WIDTH - 1)
+				blueMap[tile[index].row][i] = index+1;
+		}
+
+		for (int i = 2; i < WIDTH; i++) {
+			if (greenMap[i][tile[index].col] != 0) {
+				greenMap[i - 1][tile[index].col] = index+1;
+				break;
+			}
+			else if (i == WIDTH - 1)
+				greenMap[i][tile[index].col] = index+1;
+		}
+	}
+	else if (tile[index].type == 2) {	//1x2
+		for (int i = 2; i < WIDTH; i++) {
+			if (blueMap[tile[index].row][i] != 0) {
+				blueMap[tile[index].row][i - 1] = index+1;
+				blueMap[tile[index].row][i - 2] = index+1;
+				break;
+			}
+			else if (i == WIDTH - 1) {
+				blueMap[tile[index].row][i] = index+1;
+				blueMap[tile[index].row][i - 1] = index+1;
+			}
+		}
+
+		for (int i = 2; i < WIDTH; i++) {
+			if (greenMap[i][tile[index].col] != 0|| greenMap[i][tile[index].col + 1] != 0) {
+				greenMap[i - 1][tile[index].col] = index+1;
+				greenMap[i - 1][tile[index].col + 1] = index+1;
+				break;
+			}
+			else if (i == WIDTH - 1) {
+				greenMap[i][tile[index].col] = index+1;
+				greenMap[i][tile[index].col + 1] = index+1;
+			}
+		}
+	}
+	else if (tile[index].type == 3) { //2x1
+		for (int i = 2; i < WIDTH; i++) {
+			if (blueMap[tile[index].row][i] != 0 || blueMap[tile[index].row + 1][i] != 0) {
+				blueMap[tile[index].row][i - 1] = index+1;
+				blueMap[tile[index].row + 1][i - 1] = index+1;
+				break;
+			}
+			else if (i == WIDTH - 1) {
+				blueMap[tile[index].row][i] = index+1;
+				blueMap[tile[index].row + 1][i] = index+1;
+			}
+		}
+
+		for (int i = 2; i < WIDTH; i++) {
+			if (greenMap[i][tile[index].col] != 0) {
+				greenMap[i - 2][tile[index].col] = index+1;
+				greenMap[i - 1][tile[index].col] = index+1;
+				break;
+			}
+			else if (i == WIDTH - 1) {
+				greenMap[i][tile[index].col] = index+1;
+				greenMap[i - 1][tile[index].col] = index+1;
+			}
+		}
+
+	}
+}
+
+void putTile(int row, int col, int map) {
+	if (map == 0) {
+		int temp = blueMap[row][col];
+		blueMap[row][col] = 0;
+		blueMap[row + 1][col] = 0;
+		for (int i = col + 1; i < WIDTH; i++) {
+			if (blueMap[row][i] != 0 || blueMap[row + 1][i] != 0) {
+				blueMap[row][i - 1] = temp;
+				blueMap[row + 1][i - 1] = temp;
+				break;
+			}
+			else if (i == WIDTH - 1) {
+				blueMap[row][i] = temp;
+				blueMap[row + 1][i] = temp;
+			}
+		}
+	}
+	else if (map == 1) {
+		int temp = greenMap[row][col];
+		greenMap[row][col] = 0;
+		greenMap[row][col + 1] = 0;
+		for (int i = row + 1; i < WIDTH; i++) {
+			if (greenMap[i][col] != 0 || greenMap[i][col + 1] != 0) {
+				greenMap[i - 1][col] = temp;
+				greenMap[i - 1][col + 1] = temp;
+				break;
+			}
+			else if (i == WIDTH - 1) {
+				greenMap[i][col] = temp;
+				greenMap[i][col + 1] = temp;
+			}
+		}
+
+	}
+}
+
+int updateMap() {
+	int score = 0;
+	//파랑
+	for (int i = 2; i < WIDTH; i++) {
+		int count = 0;
+		for (int j = 0; j < HEIGHT; j++) {
+			if (blueMap[j][i] != 0)
+				count++;
+		}
+		if (count == 4) {
+			score++;
+			for (int k = 0; k < 4; k++) blueMap[k][i] = 0;
+			//2*1 일때만 따로 처리해주면 됨
+			for (int j = i-1; j >=0; j--) {
+				for (int k = 0; k <= HEIGHT; k++) {
+					if (blueMap[k][j] != 0) {
+						if (k != HEIGHT - 1 && blueMap[k + 1][j] == blueMap[k][j])
+							putTile(k, j, 0);
+						else {
+							int temp = blueMap[k][j];
+							blueMap[k][j] = 0;
+							for (int tempCol = j + 1; tempCol < WIDTH; tempCol++) {
+								if (blueMap[k][tempCol] != 0) {
+									blueMap[k][tempCol - 1] = temp;
+									break;
+								}
+								else if (tempCol == WIDTH - 1)
+									blueMap[k][tempCol] = temp;
+							}
+						}
+					}
+				}
+			}
+			i = 2;
+		}
+	}
+	for (int i = 0; i < 2; i++) {
+		bool check = false;
+		for (int j = 0; j < HEIGHT; j++) {
+			if (blueMap[j][i])
+				check = true;
+		}
+		if (check) {
+			int temp[4] = { blueMap[0][i], blueMap[1][i], blueMap[2][i], blueMap[3][i] };
+			for (int k = 0; k < 4; k++) blueMap[k][i] = 0;
+			for (int col = i + 1; col < WIDTH; col++) {
+				int temp2[4] = { blueMap[0][col], blueMap[1][col], blueMap[2][col], blueMap[3][col] };
+				for (int k = 0; k < 4; k++) blueMap[k][col] = temp[k];
+				memcpy(temp, temp2, sizeof(temp));
+			}
+		}
+	}
+
+	//초록
+	for (int i = 2; i < WIDTH; i++) {
+		int count = 0;
+		for (int j = 0; j < HEIGHT; j++) {
+			if (greenMap[i][j] != 0)
+				count++;
+		}
+		if (count == 4) {
+			score++;
+			for (int k = 0; k < 4; k++) greenMap[i][k] = 0;
+			for (int j = i-1; j >= 0; j--) {
+				for (int k = 0; k < HEIGHT; k++) {
+					if (greenMap[j][k] != 0) {
+						if (k != HEIGHT - 1 && greenMap[j][k] == greenMap[j][k + 1]) {//옯기고, 다음 k에서 출동?
+							putTile(j, k, 1);
+						}
+						else {
+							int temp = greenMap[j][k];
+							greenMap[j][k] = 0;
+							for (int tempRow = j + 1; tempRow < WIDTH; tempRow++) {
+								if (greenMap[tempRow][k] != 0) {
+									greenMap[tempRow - 1][k] = temp;
+									break;
+								}
+								else if (tempRow == WIDTH - 1)
+									greenMap[tempRow][k] = temp;
+							}
+						}
+					}
+				}
+			}
+			i = 2;
+		}
+	}
+	for (int i = 0; i < 2; i++) {
+		bool check = false;
+		for (int j = 0; j < HEIGHT; j++) {
+			if (greenMap[i][j])
+				check = true;
+		}
+		if (check) {
+			int temp[4] = { greenMap[i][0], greenMap[i][1], greenMap[i][2], greenMap[i][3] };
+			for (int k = 0; k < 4; k++) greenMap[i][k] = 0;
+			for (int row = i + 1; row < WIDTH; row++) {
+				int temp2[4] = { greenMap[row][0], greenMap[row][1], greenMap[row][2], greenMap[row][3] };
+				for (int k = 0; k < 4; k++) greenMap[row][k] = temp[k];
+				memcpy(temp, temp2, sizeof(temp));
+			}
+		}
+	}
+
+	return score;
+}
+
+int getRestTile() {
+	int count = 0;
+	for (int i = 0; i < 24; i++) {
+		if (blueMap[i / WIDTH][i % WIDTH] != 0)
+			count++;
+		if (greenMap[i / HEIGHT][i % HEIGHT] != 0)
+			count++;
+	}
+	return count;
+}
+
This post is licensed under CC BY 4.0 by the author.

1865 웜홀

1956 운동

Comments powered by Disqus.

diff --git a/posts/19236/index.html b/posts/19236/index.html new file mode 100644 index 000000000..e6e810a2c --- /dev/null +++ b/posts/19236/index.html @@ -0,0 +1,363 @@ + 19236 Samsung sw test | 디피의 개발일지
Posts 19236 Samsung sw test
Post
Cancel

19236 Samsung sw test

19236 Samsung sw test

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+
#include <iostream>
+#include <cstring>
+#define WIDTH 4
+#define SHARK 100
+#define BLANK -1
+
+using namespace std;
+
+typedef struct Fish {
+	int num;
+	int row;
+	int col;
+	int direction;	// 위부터 반시계방향으로 1~8
+	bool isAlive;
+} Fish;
+
+int map[WIDTH][WIDTH];
+Fish fish[WIDTH*WIDTH+1];
+Fish shark = { 0,0,0,0 };
+
+int getSumOfFishNum();
+void eat(int row, int col);
+void moveFish();
+void changeFish(int index, int dstIndex, int row = 0, int col = 0);
+int getNextSharkRow() {
+	if (shark.direction == 1 || shark.direction == 2 || shark.direction == 8)
+		return -1;
+	else if (shark.direction == 4 || shark.direction == 5 || shark.direction == 6)
+		return 1;
+	else
+		return 0;
+}
+int getNextSharkCol() {
+	if (shark.direction == 2 || shark.direction == 3 || shark.direction == 4)
+		return -1;
+	else if (shark.direction == 6 || shark.direction == 7 || shark.direction == 8)
+		return 1;
+	else
+		return 0;
+}
+
+int main() {
+	for (int i = 0; i < WIDTH; i++) {
+		for (int j = 0; j < WIDTH; j++) {
+			Fish temp = { 0, i, j, 0, true};
+			cin >> temp.num >> temp.direction;
+			fish[temp.num] = temp;
+			map[i][j] = temp.num;
+		}
+	}
+	/*
+	for (int i = 0; i < WIDTH; i++) {
+		for (int j = 0; j < WIDTH; j++)
+			cout << map[i][j] << " ";
+		cout << endl;
+	}
+	for (int i = 0; i < WIDTH * WIDTH + 1; i++) {
+		cout << fish[i].num << " " << fish[i].row << " " << fish[i].col << " " << fish[i].direction << " " << fish[i].isAlive << endl;
+	}
+	*/
+
+	eat(0, 0);
+	cout << getSumOfFishNum();
+}
+
+int getSumOfFishNum() {
+	moveFish();
+
+	//상어 옮기기. 가능한 곳에 옮기고, 옮길때마다 값 저장 후 다시 초기화
+	int nextRow = getNextSharkRow();
+	int nextCol = getNextSharkCol();
+	Fish tempShark = shark;
+	Fish tempFish[WIDTH*WIDTH+1];
+	memcpy(tempFish, fish, sizeof(tempFish));
+	int tempMap[WIDTH][WIDTH];
+	memcpy(tempMap, map, sizeof(map));
+	int deep = 1, maxNum = 0;
+	while (true) {
+		int r = shark.row + deep*nextRow;
+		int c = shark.col + deep*nextCol;
+		if (r < 0 || r >= WIDTH || c < 0 || c >= WIDTH) {
+			break;
+		}
+		else if (map[r][c] == BLANK) {
+			deep++;
+		}
+		else {
+			eat(r, c);
+			int a = getSumOfFishNum();
+			maxNum = a > maxNum ? a : maxNum;
+			memcpy(map, tempMap, sizeof(map));
+			memcpy(fish, tempFish, sizeof(tempFish));
+			shark = tempShark;
+			deep++;
+		}
+	}
+	if (maxNum == 0)
+		return shark.num;
+	return maxNum;
+}
+
+void eat(int row, int col) {
+	int index = map[row][col];
+	map[shark.row][shark.col] = BLANK;
+	map[row][col] = SHARK;
+	shark.row = row;
+	shark.col = col;
+	shark.num += fish[index].num;
+	shark.direction = fish[index].direction;
+	fish[index].isAlive = false;
+}
+
+void moveFish() {
+	int count = 0;
+	for (int i = 1; i <= 16; i++) {
+		if (!fish[i].isAlive)
+			continue;
+
+		if (fish[i].direction == 1 && fish[i].row != 0 && map[fish[i].row - 1][fish[i].col] != SHARK) {
+			changeFish(i, map[fish[i].row - 1][fish[i].col], fish[i].row-1, fish[i].col);
+			count = 0;
+		}
+		else if (fish[i].direction == 2 && (fish[i].row != 0 && fish[i].col != 0) && map[fish[i].row - 1][fish[i].col - 1] != SHARK) {
+			changeFish(i, map[fish[i].row - 1][fish[i].col - 1], fish[i].row - 1, fish[i].col-1);
+			count = 0;
+		}
+		else if (fish[i].direction == 3 && fish[i].col != 0 && map[fish[i].row][fish[i].col - 1] != SHARK) {
+			changeFish(i, map[fish[i].row][fish[i].col - 1], fish[i].row, fish[i].col-1);
+			count = 0;
+		}
+		else if (fish[i].direction == 4 && (fish[i].row != 3 && fish[i].col != 0) && map[fish[i].row + 1][fish[i].col - 1] != SHARK) {
+			changeFish(i, map[fish[i].row + 1][fish[i].col - 1], fish[i].row +  1, fish[i].col-1);
+			count = 0;
+		}
+		else if (fish[i].direction == 5 && fish[i].row != 3 && map[fish[i].row + 1][fish[i].col] != SHARK) {
+			changeFish(i, map[fish[i].row + 1][fish[i].col], fish[i].row + 1, fish[i].col);
+			count = 0;
+		}
+		else if (fish[i].direction == 6 && (fish[i].row != 3 && fish[i].col != 3)&& map[fish[i].row + 1][fish[i].col +1] != SHARK) {
+			changeFish(i, map[fish[i].row + 1][fish[i].col +1], fish[i].row + 1, fish[i].col+1);
+			count = 0;
+		}
+		else if (fish[i].direction == 7 && fish[i].col != 3 && map[fish[i].row][fish[i].col+1] != SHARK) {
+			changeFish(i, map[fish[i].row][fish[i].col+1], fish[i].row, fish[i].col+1);
+			count = 0;
+		}
+		else if (fish[i].direction == 8 && (fish[i].row != 0 && fish[i].col != 3) && map[fish[i].row - 1][fish[i].col+1] != SHARK) {
+			changeFish(i, map[fish[i].row - 1][fish[i].col+1], fish[i].row - 1, fish[i].col+1);
+			count = 0;
+		}
+		else {
+			if (fish[i].direction != 8)
+				fish[i].direction++;
+			else
+				fish[i].direction = 1;
+			count++;
+			if (count >= 8)
+				continue;
+			i--;
+		}
+	}
+}
+
+void changeFish(int index, int dstIndex, int row, int col) {
+	if (dstIndex == BLANK) {
+		map[fish[index].row][fish[index].col] = BLANK;
+		map[row][col] = index;
+		fish[index].row = row;
+		fish[index].col = col;
+	}
+	else {
+		int tempRow = fish[dstIndex].row;
+		int tempCol = fish[dstIndex].col;
+		map[fish[dstIndex].row][fish[dstIndex].col] = index;
+		map[fish[index].row][fish[index].col] = dstIndex;
+		fish[dstIndex].row = fish[index].row;
+		fish[dstIndex].col = fish[index].col;
+		fish[index].row = tempRow;
+		fish[index].col = tempCol;
+	}
+}
+
This post is licensed under CC BY 4.0 by the author.

1799 bishop

19238 Samsung sw test

Comments powered by Disqus.

diff --git a/posts/19238/index.html b/posts/19238/index.html new file mode 100644 index 000000000..42afe2aee --- /dev/null +++ b/posts/19238/index.html @@ -0,0 +1,393 @@ + 19238 Samsung sw test | 디피의 개발일지
Posts 19238 Samsung sw test
Post
Cancel

19238 Samsung sw test

19238 Samsung sw test

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+
#include <iostream>
+#include <list>
+#include <vector>
+#include <algorithm>
+#define MAX_WIDTH 20
+#define MAX_CUSTOMER 400
+#define CUSTOMER 123
+
+using namespace std;
+
+typedef struct Taxi {
+	int row;
+	int col;
+	int fuel;
+} Taxi;
+
+typedef struct Customer {
+	int currentRow;
+	int currentCol;
+	int dstRow;
+	int dstCol;
+	int distance;
+} Customer;
+
+typedef struct Candidate {
+	int row;
+	int col;
+	int fuel;
+	int index;
+	bool operator < (Candidate t) {
+		if (fuel < t.fuel)
+			return true;
+		else if (fuel > t.fuel)
+			return false;
+		else {
+			if (row < t.row)
+				return true;
+			else if (row > t.row)
+				return false;
+			else {
+				if (col < t.col)
+					return true;
+				else
+					return false;
+			}
+		}
+	}
+} Candidate;
+
+int map[MAX_WIDTH][MAX_WIDTH];
+int width, numOfCustomer;
+Taxi taxi;
+Customer customer[MAX_CUSTOMER];
+
+
+//nxn 크기 맵에 각 칸엔 벽 또는 빈칸
+//빈칸엔 승객과 택시가 올 수 있음
+//m 명을 태워야하고, m명은 각각 흩어져 있음
+//현재 택시위치에서 가장 가까운 승객을 태우고, 여러명이면 행이 작고, 열이 작은 승객 먼저
+//택시와 승객이 같은 위치면 0, 승객을 성공적으로 태워다주면, 태워다주느라 소모한 연료의 두배가 충전됨.
+//태우는 도중에 연료가 소진되면 그걸로 업무 종료. 도착과 동시에 소진은 괜찮다.
+//승객을 태우러 가는 도중에도 연료는 소모된다.
+
+int startWork();
+void getCustomerDistance();
+bool check();
+
+int main() {
+	cin >> width >> numOfCustomer >> taxi.fuel;
+	for (int i = 0; i < width; i++) {
+		for (int j = 0; j < width; j++)
+			cin >> map[i][j];
+	}
+	cin >> taxi.row >> taxi.col;
+	taxi.row--;
+	taxi.col--;
+	for (int i = 0; i < numOfCustomer; i++) {
+		cin >> customer[i].currentRow >> customer[i].currentCol >> customer[i].dstRow >> customer[i].dstCol;
+		customer[i].currentRow--;
+		customer[i].currentCol--;
+		customer[i].dstRow--;
+		customer[i].dstCol--;
+		customer[i].distance = -1;
+		map[customer[i].currentRow][customer[i].currentCol] = CUSTOMER + i;
+	}
+	getCustomerDistance();
+	if (check())
+		cout << startWork();
+	else
+		cout << -1;
+}
+
+void getCustomerDistance() {
+	for (int i = 0; i < numOfCustomer; i++) {
+		bool visit[MAX_WIDTH][MAX_WIDTH] = { 0, };
+		list<Taxi> q;
+		vector<int> candidate;
+		q.push_back({ customer[i].currentRow, customer[i].currentCol, 0 });
+		visit[customer[i].currentRow][customer[i].currentCol] = true;
+
+		while (!q.empty()) {
+			Taxi temp = q.front();
+			q.pop_front();
+
+			if (temp.row == customer[i].dstRow && temp.col == customer[i].dstCol) {
+				customer[i].distance = temp.fuel;
+				break;
+			}
+
+			if (temp.row != 0 && map[temp.row - 1][temp.col] != 1 && !visit[temp.row - 1][temp.col]) {
+				q.push_back({ temp.row - 1, temp.col, temp.fuel + 1 });
+				visit[temp.row - 1][temp.col] = true;
+			}
+			if (temp.row != width - 1 && map[temp.row + 1][temp.col] != 1 && !visit[temp.row + 1][temp.col]) {
+				q.push_back({ temp.row + 1, temp.col, temp.fuel + 1 });
+				visit[temp.row + 1][temp.col] = true;
+			}
+			if (temp.col != 0 && map[temp.row][temp.col - 1] != 1 && !visit[temp.row][temp.col - 1]) {
+				q.push_back({ temp.row, temp.col - 1, temp.fuel + 1 });
+				visit[temp.row][temp.col - 1] = true;
+			}
+			if (temp.col != width - 1 && map[temp.row][temp.col + 1] != 1 && !visit[temp.row][temp.col + 1]) {
+				q.push_back({ temp.row, temp.col + 1, temp.fuel + 1 });
+				visit[temp.row][temp.col + 1] = true;
+			}
+		}
+	}
+}
+
+bool check() {
+	for (int i = 0; i < numOfCustomer; i++)
+		if (customer[i].distance == -1)
+			return false;
+	return true;
+}
+
+int startWork() {
+	for (int i = 0; i < numOfCustomer; i++) {
+		//가장 가까운 승객 뽑아서 밑 변수에 저장.
+		Customer crtCustomer;
+		int crtCustomerDistance = 0;
+		if (map[taxi.row][taxi.col] >= CUSTOMER)
+			crtCustomer = customer[map[taxi.row][taxi.col] - CUSTOMER];
+		else {
+			bool visit[MAX_WIDTH][MAX_WIDTH] = { 0, };
+			visit[taxi.row][taxi.col] = true;
+			list<Taxi> q;
+			q.push_back({ taxi.row, taxi.col, 0 });
+			vector<Candidate> candidate;
+
+			while (!q.empty()) {
+				Taxi temp = q.front();
+				q.pop_front();
+
+				if(map[temp.row][temp.col] >= CUSTOMER)
+					candidate.push_back({ temp.row , temp.col, temp.fuel, map[temp.row][temp.col] - CUSTOMER });
+
+				if (temp.row != 0 && map[temp.row - 1][temp.col] != 1 && !visit[temp.row - 1][temp.col]) {
+					q.push_back({ temp.row - 1, temp.col, temp.fuel + 1 });
+					visit[temp.row - 1][temp.col] = true;
+				}
+				if (temp.row != width - 1 && map[temp.row + 1][temp.col] != 1 && !visit[temp.row + 1][temp.col]) {
+					q.push_back({ temp.row + 1, temp.col, temp.fuel + 1 });
+					visit[temp.row + 1][temp.col] = true;
+				}
+				if (temp.col != 0 && map[temp.row][temp.col - 1] != 1 && !visit[temp.row][temp.col - 1]) {
+					q.push_back({ temp.row, temp.col - 1, temp.fuel + 1 });
+					visit[temp.row][temp.col - 1] = true;
+				}
+				if (temp.col != width - 1 && map[temp.row][temp.col + 1] != 1 && !visit[temp.row][temp.col + 1]) {
+					q.push_back({ temp.row, temp.col + 1, temp.fuel + 1 });
+					visit[temp.row][temp.col + 1] = true;
+				}
+			}
+			if (candidate.size() == 0)
+				return -1;
+			sort(candidate.begin(), candidate.end());
+			crtCustomer = customer[candidate[0].index];
+			crtCustomerDistance = candidate[0].fuel;
+		}
+
+		//가장 가까운 승객을 데리고, 연료 충분한지, 충분하면 이동시킴
+		map[crtCustomer.currentRow][crtCustomer.currentCol] = 0;
+		int distance = crtCustomer.distance + crtCustomerDistance;
+		if (distance <= taxi.fuel) {
+			taxi.row = crtCustomer.dstRow;
+			taxi.col = crtCustomer.dstCol;
+			taxi.fuel -= distance;
+			taxi.fuel += (2*crtCustomer.distance);
+		}
+		else
+			return -1;
+	}
+
+	return taxi.fuel;
+}
+
This post is licensed under CC BY 4.0 by the author.

19236 Samsung sw test

20056 Samsung sw test

Comments powered by Disqus.

diff --git a/posts/1956/index.html b/posts/1956/index.html new file mode 100644 index 000000000..ddd9ea9d3 --- /dev/null +++ b/posts/1956/index.html @@ -0,0 +1,91 @@ + 1956 운동 | 디피의 개발일지
Posts 1956 운동
Post
Cancel

1956 운동

1956 운동

알고리즘(플로이드 워셜)

1
+2
+3
+4
+5
+6
+7
+
1. 문제 파악 : 그래프 상의 어떤 점들 집합이건 최소로 사이클을 이루는 집합이면 됨
+2. 그래프 관련 알고리즘
+	1. 다익스트라 : 출발점에서 모든 정점까지의 거리 -> 해당 x
+	2. 밸만포드 : 다익스트라와 마찬가지
+	3. MST : 최소신장트리 -> 사이클은 감지할 수 있으나, 그것이 최소 사이클인지 모름 and 사이클만을 추출해내기 어려움
+	4. 플로이드 워셜 : 모든 정점 사이의 거리
+		-> 자기 자신은 원래 0으로 표시하지만, 이 문제에선 INF로 한다면 사이클을 감지할 수 있고 또 최소 사이클도 감지하기 쉬움
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+
#include <bits/stdc++.h>
+#define INF 2000000000
+
+using namespace std;
+
+long long N, E, edges[401][401];
+
+int floid();
+
+int main() {
+	cin >> N >> E;
+	for (int i = 0; i < 401; i++)
+		for (int j = 0; j < 401; j++)
+			edges[i][j] = INF;
+	for (int i = 0; i < E; i++) {
+		int a, b, c;
+		cin >> a >> b >> c;
+		edges[a][b] = c;
+	}
+
+	cout << floid();
+}
+
+int floid() {
+	for (int i = 1; i <= N; i++)
+		for (int j = 1; j <= N; j++)
+			for (int k = 1; k <= N; k++)
+				if (edges[j][k] > edges[j][i] + edges[i][k])
+					edges[j][k] = edges[j][i] + edges[i][k];
+
+	int result = INF;
+	for (int i = 1; i <= N; i++)
+		result = result < edges[i][i] ? result : edges[i][i];
+
+	if (result == INF)
+		return -1;
+	return result;
+}
+
This post is licensed under CC BY 4.0 by the author.

19235 모노미노도미노

1987 Alphabet

Comments powered by Disqus.

diff --git a/posts/1987/index.html b/posts/1987/index.html new file mode 100644 index 000000000..99bc1bdbf --- /dev/null +++ b/posts/1987/index.html @@ -0,0 +1,127 @@ + 1987 Alphabet | 디피의 개발일지
Posts 1987 Alphabet
Post
Cancel

1987 Alphabet

1987 Alphabet

알고리즘(백트래킹)

1
+2
+3
+4
+
1. 어떤 알파벳을 검사했는지 기록하는 건 check[26] 배열을 선언하고, 각 알파벳에서 65 값을 빼서 각 자리로 접근한다.('A' - 65 == 0)
+2. 인접한 칸을 검사하고, 가능하면 check 표시하고 인접한 칸으로 이동. 안되면 돌아와서(Back Tracking) 다른 인접한 칸 검사
+3. 갈 수 있는 인접한 칸이 없으면 그 칸이 마지막 칸이므로 maxCount 보다 크면 기록 -> 종료
+4. 만약 deep이 27에 다다르면, 알파벳 26개를 전부 검사한 것이므로, maxCount에 26기록후 종료
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+
#include <iostream>
+#include <cstring>
+#define MAX_WIDTH 20
+#define ASCII_A 65
+
+using namespace std;
+
+char map[MAX_WIDTH][MAX_WIDTH];
+bool check[26];
+int height, width, maxCount = 0;
+
+void DFS(int row, int col, int deep);
+
+int main() {
+	cin >> height >> width;
+	for (int i = 0; i < height; i++)
+		for (int j = 0; j < width; j++)
+			cin >> map[i][j];
+
+	check[map[0][0] - ASCII_A] = true;
+	DFS(0,0,1);
+	cout << maxCount;
+}
+
+void DFS(int row, int col, int deep) {
+	if (deep == 27) {
+		maxCount = deep-1;
+		return;
+	}
+
+	bool isEnd = true;
+	if (col < width - 1 && check[map[row][col + 1] - ASCII_A] == false) {
+		check[map[row][col + 1] - ASCII_A] = true;
+		DFS(row, col + 1, deep + 1);
+		check[map[row][col + 1] - ASCII_A] = false;
+		isEnd = false;
+	}
+	if (row < height - 1 && check[map[row + 1][col] - ASCII_A] == false) {
+		check[map[row + 1][col] - ASCII_A] = true;
+		DFS(row + 1, col, deep + 1);
+		check[map[row + 1][col] - ASCII_A] = false;
+		isEnd = false;
+	}
+	if (col > 0 && check[map[row][col - 1] - ASCII_A] == false) {
+		check[map[row][col - 1] - ASCII_A] = true;
+		DFS(row, col - 1, deep + 1);
+		check[map[row][col - 1] - ASCII_A] = false;
+		isEnd = false;
+	}
+	if (row > 0 && check[map[row - 1][col] - ASCII_A] == false) {
+		check[map[row - 1][col] - ASCII_A] = true;
+		DFS(row - 1, col, deep + 1);
+		check[map[row - 1][col] - ASCII_A] = false;
+		isEnd = false;
+	}
+
+	if (isEnd)
+		maxCount = maxCount > deep ? maxCount : deep;
+}
+
This post is licensed under CC BY 4.0 by the author.

1956 운동

20055 Samsung sw test

Comments powered by Disqus.

diff --git a/posts/20055/index.html b/posts/20055/index.html new file mode 100644 index 000000000..9b64b4856 --- /dev/null +++ b/posts/20055/index.html @@ -0,0 +1,167 @@ + 20055 Samsung sw test | 디피의 개발일지
Posts 20055 Samsung sw test
Post
Cancel

20055 Samsung sw test

20055 Samsung sw test

알고리즘 (구현)

1
+2
+3
+
1. durability와 isThereRobot 변수로 구성된 Cell 구조체로 컨베이어 벨트를 만듦
+2. 무한 루프 안에서, 이동시키고 -> 내리는 위치 로봇빼고 -> 로봇이동 시키고(이동시 내구도 1감소) -> 내리는 위치 로봇빼고 -> 올리는 위치 로봇 올리고(내구도 1감소)
+	-> 내구도 0인 칸이 한계점에 닿으면 종료
+

기타

1
+
1. 매번 내구도 0인 칸이 몇개인지 세기보단, 내구도를 깎는 칸에 깎고나서 내구도 0이면 count++를 하여, count가 한계점에 닿는지 확인하는 편이 훨씬 빠르게 할 수 있다.
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+
#include <iostream>
+#define MAX_WIDTH 100
+
+using namespace std;
+
+typedef struct Cell {
+	int durability;
+	bool robot;
+} Cell;
+
+Cell belt[2][MAX_WIDTH];
+int width, maxDurability;
+
+int getLastSecond();
+bool checkDurability();
+
+int main() {
+	cin >> width >> maxDurability;
+	for (int i = 0; i < width; i++) {
+		cin >> belt[0][i].durability;
+		belt[0][i].robot = false;
+	}
+	for (int i = width - 1; i >= 0; i--) {
+		cin >> belt[1][i].durability;
+		belt[1][i].robot = false;
+	}
+
+	cout << getLastSecond();
+}
+
+int getLastSecond() {
+	int second = 0;
+	int zero = 0;
+	while (true) {
+		second++;
+		//벨트 한칸 씩 이동
+		Cell temp = belt[0][0];
+		for (int i = 1; i < width; i++) {
+			Cell temp2 = belt[0][i];
+			belt[0][i] = temp;
+			temp = temp2;
+		}
+		for (int i = width - 1; i >= 0; i--) {
+			Cell temp2 = belt[1][i];
+			belt[1][i] = temp;
+			temp = temp2;
+		}
+		belt[0][0] = temp;
+		belt[0][width - 1].robot = false;
+
+		//로봇 이동
+		for (int i = width - 2; i >= 0; i--) {
+			if (belt[0][i].robot) {
+				if (!belt[0][i + 1].robot && belt[0][i + 1].durability > 0) {
+					belt[0][i + 1].robot = true;
+					belt[0][i + 1].durability--;
+					if (belt[0][i + 1].durability == 0)
+						zero++;
+					belt[0][i].robot = false;
+				}
+			}
+		}
+		belt[0][width - 1].robot = false;
+
+		//로봇 올리기
+		if (!belt[0][0].robot && belt[0][0].durability > 0) {
+			belt[0][0].durability--;
+			if (belt[0][0].durability == 0)
+				zero++;
+			belt[0][0].robot = true;
+		}
+
+		//체크
+		if (zero >= maxDurability)
+			break;
+	}
+
+	return second;
+}
+
This post is licensed under CC BY 4.0 by the author.

1987 Alphabet

2056 task

Comments powered by Disqus.

diff --git a/posts/20056/index.html b/posts/20056/index.html new file mode 100644 index 000000000..76146ff5d --- /dev/null +++ b/posts/20056/index.html @@ -0,0 +1,335 @@ + 20056 Samsung sw test | 디피의 개발일지
Posts 20056 Samsung sw test
Post
Cancel

20056 Samsung sw test

20056 Samsung sw test

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+
#include <iostream>
+#include <cstring>
+#include <vector>
+#define MAX_WIDTH 50
+
+using namespace std;
+
+typedef struct Fire {
+	int mass;
+	int direction;	//0이 위, 시계방향으로 0~7
+	int speed;
+} Fire;
+typedef struct Cell {
+	vector<Fire> fire;
+} Cell;
+
+int width, numOfCommand, numOfFire;
+Cell map[MAX_WIDTH][MAX_WIDTH];
+Cell tempMap[MAX_WIDTH][MAX_WIDTH];
+
+int getRestFireBallMass();
+void moveFireBall();
+void splitFireBall();
+int nextRow(int row, int speed, bool isUp) {
+	int reminder = speed % width;
+	if (isUp) {
+		if (row < reminder)
+			return width - (reminder - row);
+		else
+			return row - reminder;
+	}
+	else {
+		if ((width - 1 - row) < reminder)
+			return reminder - width + row;
+		else
+			return row + reminder;
+	}
+}
+int nextCol(int col, int speed, bool isLeft) {
+	int reminder = speed % width;
+	if (isLeft) {
+		if (col < reminder)
+			return width - (reminder - col);
+		else
+			return col - reminder;
+	}
+	else {
+		if ((width - 1 - col) < reminder)
+			return reminder - width + col;
+		else
+			return col + reminder;
+	}
+}
+
+int main() {
+	cin >> width >> numOfFire >> numOfCommand;
+	for (int i = 0; i < numOfFire; i++) {
+		int row, col;
+		cin >> row >> col;
+		row--;
+		col--;
+		Fire temp;
+		cin >> temp.mass >> temp.speed >> temp.direction;
+		map[row][col].fire.push_back(temp);
+	}
+
+	cout << getRestFireBallMass();
+}
+
+int getRestFireBallMass() {
+	for (int i = 0; i < numOfCommand; i++) {
+		//이동
+		moveFireBall();
+		//분리
+		splitFireBall();
+	}
+
+	int mass = 0;
+	for (int i = 0; i < width; i++) {
+		for (int j = 0; j < width; j++) {
+			for (int k = 0; k < map[i][j].fire.size(); k++)
+				mass += map[i][j].fire[k].mass;
+		}
+	}
+
+	return mass;
+}
+
+void moveFireBall() {
+	for (int i = 0; i < width; i++) {
+		for (int j = 0; j < width; j++) {
+			tempMap[i][j].fire.clear();
+		}
+	}
+	for (int row = 0; row < width; row++) {
+		for (int col = 0; col < width; col++) {
+			for (int i = 0; i < map[row][col].fire.size(); i++) {
+				int direction = map[row][col].fire[i].direction;
+				int speed = map[row][col].fire[i].speed;
+				//스피드가 엄청 빠를때를 처리 안함
+				if (direction == 0)
+					tempMap[nextRow(row, speed, true)][col].fire.push_back(map[row][col].fire[i]);
+				else if (direction == 1)
+					tempMap[nextRow(row, speed, true)][nextCol(col, speed, false)].fire.push_back(map[row][col].fire[i]);
+				else if (direction == 2)
+					tempMap[row][nextCol(col, speed, false)].fire.push_back(map[row][col].fire[i]);
+				else if (direction == 3)
+					tempMap[nextRow(row, speed, false)][nextCol(col, speed, false)].fire.push_back(map[row][col].fire[i]);
+				else if (direction == 4)
+					tempMap[nextRow(row, speed, false)][col].fire.push_back(map[row][col].fire[i]);
+				else if (direction == 5)
+					tempMap[nextRow(row, speed, false)][nextCol(col, speed, true)].fire.push_back(map[row][col].fire[i]);
+				else if (direction == 6)
+					tempMap[row][nextCol(col, speed, true)].fire.push_back(map[row][col].fire[i]);
+				else if (direction == 7)
+					tempMap[nextRow(row, speed, true)][nextCol(col, speed, true)].fire.push_back(map[row][col].fire[i]);
+			}
+
+		}
+	}
+
+	for (int i = 0; i < width; i++) {
+		for (int j = 0; j < width; j++) {
+			map[i][j].fire.resize(tempMap[i][j].fire.size());
+			copy(tempMap[i][j].fire.begin(), tempMap[i][j].fire.end(), map[i][j].fire.begin());
+		}
+	}
+}
+void splitFireBall() {
+	for (int row = 0; row < width; row++) {
+		for (int col = 0; col < width; col++) {
+			if (map[row][col].fire.size() >= 2) {
+				int mass = 0, speed = 0;
+				bool isEven = true, isOdd = true;
+				for (int i = 0; i < map[row][col].fire.size(); i++) {
+					mass += map[row][col].fire[i].mass;
+					speed += map[row][col].fire[i].speed;
+					if (map[row][col].fire[i].direction % 2 == 0) {
+						isEven = isEven && true;
+						isOdd = isOdd && false;
+					}
+					else {
+						isEven = isEven && false;
+						isOdd = isOdd && true;
+					}
+				}
+				mass /= 5;
+				speed /= map[row][col].fire.size();
+				map[row][col].fire.clear();
+				if (mass != 0) {
+					if (isEven || isOdd) {
+						map[row][col].fire.push_back({ mass, 0, speed });
+						map[row][col].fire.push_back({ mass, 2, speed });
+						map[row][col].fire.push_back({ mass, 4, speed });
+						map[row][col].fire.push_back({ mass, 6, speed });
+					}
+					else {
+						map[row][col].fire.push_back({ mass, 1, speed });
+						map[row][col].fire.push_back({ mass, 3, speed });
+						map[row][col].fire.push_back({ mass, 5, speed });
+						map[row][col].fire.push_back({ mass, 7, speed });
+					}
+				}
+			}
+		}
+	}
+}
+
This post is licensed under CC BY 4.0 by the author.

19238 Samsung sw test

20057 Samsung sw test

Comments powered by Disqus.

diff --git a/posts/20057/index.html b/posts/20057/index.html new file mode 100644 index 000000000..93b728a35 --- /dev/null +++ b/posts/20057/index.html @@ -0,0 +1,377 @@ + 20057 Samsung sw test | 디피의 개발일지
Posts 20057 Samsung sw test
Post
Cancel

20057 Samsung sw test

20057 Samsung sw test

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+
#include <iostream>
+#define MAX_WIDTH 499
+
+using namespace std;
+
+int map[MAX_WIDTH][MAX_WIDTH];
+bool visit[MAX_WIDTH][MAX_WIDTH];
+int width, amountOfSand = 0;
+int ten, seven, five, two, one, rest;
+
+int getLostAmountSand();
+void setNum(int row, int col) {
+	ten = (float)map[row][col] * 0.1;
+	seven = (float)map[row][col] * 0.07;
+	five = (float)map[row][col] * 0.05;
+	two = (float)map[row][col] * 0.02;
+	one = (float)map[row][col] * 0.01;
+	rest = map[row][col] - (2 * ten + 2 * seven + five + 2 * two + 2 * one);
+}
+void spreadLeftSand(int row, int col);
+void spreadRightSand(int row, int col);
+void spreadUpSand(int row, int col);
+void spreadDownSand(int row, int col);
+
+int main() {
+	cin >> width;
+	for (int i = 0; i < width; i++) {
+		for (int j = 0; j < width; j++) {
+			cin >> map[i][j];
+			if (map[i][j] != 0)
+				amountOfSand += map[i][j];
+		}
+	}
+
+	cout << getLostAmountSand();
+}
+
+int getLostAmountSand() {
+	visit[width / 2][width / 2] = true;
+	bool left = true, down = false, right = false, up = false;
+	int row = width / 2, col = width / 2;
+	for (int i = 1; i < width * width; i++) {
+		cout << row << " " << col << endl;
+
+		if (left) {
+			col--;
+			spreadLeftSand(row, col);
+		}
+		else if (down) {
+			row++;
+			spreadDownSand(row, col);
+		}
+		else if (right) {
+			col++;
+			spreadRightSand(row, col);
+		}
+		else if (up) {
+			row--;
+			spreadUpSand(row, col);
+		}
+		visit[row][col] = true;
+		if (left && !visit[row + 1][col]) {
+			left = false;
+			down = true;
+		}
+		else if (down && !visit[row][col + 1]) {
+			down = false;
+			right = true;
+		}
+		else if (right && !visit[row - 1][col]) {
+			right = false;
+			up = true;
+		}
+		else if (up && !visit[row][col - 1]) {
+			up = false;
+			left = true;
+		}
+		for (int j = 0; j < width; j++) {
+			for (int k = 0; k < width; k++) {
+				cout << map[j][k] << " ";
+			}
+			cout << endl;
+		}
+		cout << endl;
+	}
+
+	int sand = 0;
+	for (int i = 0; i < width; i++)
+		for (int j = 0; j < width; j++)
+			sand += map[i][j];
+	return amountOfSand - sand;
+}
+void spreadLeftSand(int row, int col) {
+	setNum(row, col);
+	if (row > 1)
+		map[row - 2][col] += two;
+	if (row > 0) {
+		map[row - 1][col] += seven;
+		map[row - 1][col + 1] += one;
+	}
+	if (row > 0 && col > 0)
+		map[row - 1][col - 1] += ten;
+	if (col > 1)
+		map[row][col - 2] += five;
+	if (row < width - 1 && col > 0)
+		map[row + 1][col - 1] += ten;
+	if (row < width - 1) {
+		map[row + 1][col] += seven;
+		map[row + 1][col + 1] += one;
+	}
+	if (row < width - 2)
+		map[row + 2][col] += two;
+	if (col > 0)
+		map[row][col - 1] += rest;
+	map[row][col] = 0;
+}
+void spreadRightSand(int row, int col) {
+	setNum(row, col);
+	if (row > 1)
+		map[row - 2][col] += two;
+	if (row > 0) {
+		map[row - 1][col] += seven;
+		map[row - 1][col - 1] += one;
+	}
+	if (row > 0 && col < width -1)
+		map[row - 1][col + 1] += ten;
+	if (col < width - 2)
+		map[row][col + 2] += five;
+	if (row < width - 1 && col < width-1)
+		map[row + 1][col + 1] += ten;
+	if (row < width - 1) {
+		map[row + 1][col] += seven;
+		map[row + 1][col - 1] += one;
+	}
+	if (row < width - 2)
+		map[row + 2][col] += two;
+	if (col < width - 1)
+		map[row][col + 1] += rest;
+	map[row][col] = 0;
+}
+void spreadUpSand(int row, int col) {
+	setNum(row, col);
+	if (col > 1)
+		map[row][col - 2] += two;
+	if (col > 0) {
+		map[row][col - 1] += seven;
+		map[row + 1][col - 1] += one;
+	}
+	if (row > 0 && col > 0)
+		map[row - 1][col - 1] += ten;
+	if (row > 1)
+		map[row - 2][col] += five;
+	if (row > 0 && col < width-1)
+		map[row - 1][col + 1] += ten;
+	if (col < width - 1) {
+		map[row][col + 1] += seven;
+		map[row + 1][col + 1] += one;
+	}
+	if (col < width - 2)
+		map[row][col + 2] += two;
+	if (row > 0)
+		map[row - 1][col] += rest;
+	map[row][col] = 0;
+}
+void spreadDownSand(int row, int col) {
+	setNum(row, col);
+	if (col > 1)
+		map[row][col - 2] += two;
+	if (col > 0) {
+		map[row][col - 1] += seven;
+		map[row - 1][col - 1] += one;
+	}
+	if (row < width - 1 && col < width - 1)
+		map[row + 1][col + 1] += ten;
+	if (row < width - 2)
+		map[row + 2][col] += five;
+	if (row < width - 1 && col > 0)
+		map[row + 1][col - 1] += ten;
+	if (col < width - 1) {
+		map[row][col + 1] += seven;
+		map[row - 1][col + 1] += one;
+	}
+	if (col < width - 2)
+		map[row][col + 2] += two;
+	if (row < width - 1)
+		map[row + 1][col] += rest;
+	map[row][col] = 0;
+}
+
This post is licensed under CC BY 4.0 by the author.

20056 Samsung sw test

20058 Samsung sw test

Comments powered by Disqus.

diff --git a/posts/20058/index.html b/posts/20058/index.html new file mode 100644 index 000000000..f19b3c0ed --- /dev/null +++ b/posts/20058/index.html @@ -0,0 +1,265 @@ + 20058 Samsung sw test | 디피의 개발일지
Posts 20058 Samsung sw test
Post
Cancel

20058 Samsung sw test

20058 Samsung sw test

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+
#include <iostream>
+#include <list>
+#include <cstring>
+#define MAX_WIDTH 64
+#define MAX_COMMAND 1000
+
+using namespace std;
+
+typedef struct Location {
+	int row;
+	int col;
+} Location;
+
+int map[MAX_WIDTH][MAX_WIDTH];
+int tempMap[MAX_WIDTH][MAX_WIDTH];
+int command[MAX_COMMAND];
+int width, numOfCommand;
+
+int getRestIce();
+int getBigestIce();
+void rotate(int row, int col, int length);
+
+int main() {
+	cin >> width >> numOfCommand;
+	int temp = 1;
+	for (int i = 0; i < width; i++)
+		temp *= 2;
+	width = temp;
+	for (int i = 0; i < width; i++)
+		for (int j = 0; j < width; j++)
+			cin >> map[i][j];
+	for (int i = 0; i < numOfCommand; i++)
+		cin >> command[i];
+
+	cout << getRestIce() << endl;
+	cout << getBigestIce();
+}
+
+int getRestIce() {
+	for (int i = 0; i < numOfCommand; i++) {
+		int cmd = command[i];
+		int temp = 1;
+		for (int i = 0; i < cmd; i++)
+			temp *= 2;
+		cmd = temp;
+		for (int row = 0; row < width; row += cmd)
+			for (int col = 0; col < width; col += cmd)
+				rotate(row, col, cmd);
+
+		memset(tempMap, 0, sizeof(tempMap));
+		//얼음 녹이기
+		for (int row = 0; row < width; row++) {
+			for (int col = 0; col < width; col++) {
+				if (map[row][col] > 0) {
+					int count = 0;
+					if (row > 0 && map[row - 1][col] > 0)
+						count++;
+					if (col > 0 && map[row][col - 1] > 0)
+						count++;
+					if (row < width - 1 && map[row + 1][col] >0)
+						count++;
+					if (col < width - 1 && map[row][col + 1] >0)
+						count++;
+					if (count < 3)
+						tempMap[row][col]--;
+				}
+			}
+		}
+		for (int i = 0; i < width; i++)
+			for (int j = 0; j < width; j++)
+				map[i][j] += tempMap[i][j];
+	}
+
+	int count = 0;
+	for (int i = 0; i < width; i++)
+		for (int j = 0; j < width; j++)
+			count += map[i][j];
+	return count;
+}
+
+void rotate(int row, int col, int length) {
+	for (int i = 0; i < length; i++)
+		for (int j = 0; j < length; j++)
+			tempMap[i][j] = map[row + i][col + j];
+
+	int tempRow = 0;
+	for (int i = length - 1; i >= 0; i--) {
+		for (int j = 0; j < length; j++) {
+			map[row + j][col + i] = tempMap[tempRow][j];
+		}
+		tempRow++;
+	}
+}
+
+int getBigestIce() {
+	bool visit[MAX_WIDTH][MAX_WIDTH] = { 0, };
+
+	int max = 0;
+	for (int i = 0; i < width; i++) {
+		for (int j = 0; j < width; j++) {
+			if (!visit[i][j] && map[i][j] > 0) {
+				list<Location> q;
+				q.push_back({ i,j });
+				int count = -1;
+				while (!q.empty()) {
+					Location temp = q.front();
+					q.pop_front();
+					count++;
+					if (temp.row > 0 && !visit[temp.row - 1][temp.col] && map[temp.row - 1][temp.col] > 0) {
+						q.push_back({ temp.row - 1, temp.col });
+						visit[temp.row - 1][temp.col] = true;
+					}
+					if (temp.col < width - 1 && !visit[temp.row][temp.col + 1] && map[temp.row][temp.col + 1] > 0) {
+						q.push_back({ temp.row, temp.col + 1 });
+						visit[temp.row][temp.col + 1] = true;
+					}
+					if (temp.row < width - 1 && !visit[temp.row + 1][temp.col] && map[temp.row + 1][temp.col] >0) {
+						q.push_back({ temp.row + 1, temp.col });
+						visit[temp.row + 1][temp.col] = true;
+					}
+					if (temp.col > 0 && !visit[temp.row][temp.col - 1] && map[temp.row][temp.col - 1] > 0) {
+						q.push_back({ temp.row, temp.col - 1 });
+						visit[temp.row][temp.col - 1] = true;
+					}
+				}
+				max = count > max ? count : max;
+			}
+		}
+	}
+
+	return max;
+}
+
This post is licensed under CC BY 4.0 by the author.

20057 Samsung sw test

2473 Three Liquid

Comments powered by Disqus.

diff --git a/posts/2056/index.html b/posts/2056/index.html new file mode 100644 index 000000000..f4ca095ce --- /dev/null +++ b/posts/2056/index.html @@ -0,0 +1,103 @@ + 2056 task | 디피의 개발일지
Posts 2056 task
Post
Cancel

2056 task

2056 task

알고리즘(위상정렬)

1
+2
+3
+4
+5
+6
+7
+
1. Task 구조체를 선언하여, time : 현재 task가 걸리는 시간을 저장 / timeTaken : 현재까지 오는데 선행으로 필요한 작업을 수행하면서 걸리는 시간 중 가장 긴 시간 저장
+			next : 다음으로 이어지는 작업들을 저장
+2. Task 구조체 배열을 앞에서부터 검사
+	-> 현재 task의 time과 timeTaken을 더해 temp에 저장.
+	-> 이어지는 작업들을 하나씩 검사 -> 이어지는 작업의 timeTaken보다 현재의 temp가 더 길면 temp를 저장
+	-> 검사가 끝나고 현재의 temp가 전체 temp중 가장 큰지 검사.(그냥 이어지는 작업 검사 끝나고 바로 maxTime 검사하면 됨)
+3. 번호와 선행관계가 뒤죽박죽이 아닌, 선행 작업은 무조건 현재번호보다 앞에 있으므로 그냥 for문으로 앞에서부터 검사하면 된다.
+

평가.

1
+
선행관계가 똑바로 돼있어서 아주편하게 코딩함
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+
#include <iostream>
+#include <vector>
+#define MAX_TASK 10000
+
+using namespace std;
+
+typedef struct Task {
+	int time;
+	int timeTaken;
+	vector<int> next;
+} Task;
+
+Task task[MAX_TASK];
+int numOfTask;
+
+int minTimeComplete();
+
+int main() {
+	cin >> numOfTask;
+	for (int i = 0; i < numOfTask; i++) {
+		cin >> task[i].time;
+		int numOfBefore;
+		cin >> numOfBefore;
+		for (int j = 0; j < numOfBefore; j++) {
+			int before;
+			cin >> before;
+			task[before-1].next.push_back(i);
+		}
+	}
+
+	cout << minTimeComplete();
+}
+
+int minTimeComplete() {
+	int maxTime = 0;
+	for (int i = 0; i < numOfTask; i++) {
+		int time = task[i].time + task[i].timeTaken;
+		for (int j = 0; j < task[i].next.size(); j++)
+			task[task[i].next[j]].timeTaken = task[task[i].next[j]].timeTaken > time ? task[task[i].next[j]].timeTaken : time;
+		maxTime = maxTime > time ? maxTime : time;
+	}
+	return maxTime;
+}
+
This post is licensed under CC BY 4.0 by the author.

20055 Samsung sw test

2098 TSP

Comments powered by Disqus.

diff --git "a/posts/2096-\353\202\264\353\240\244\352\260\200\352\270\260/index.html" "b/posts/2096-\353\202\264\353\240\244\352\260\200\352\270\260/index.html" new file mode 100644 index 000000000..5f601abb6 --- /dev/null +++ "b/posts/2096-\353\202\264\353\240\244\352\260\200\352\270\260/index.html" @@ -0,0 +1,85 @@ + 2096 내려가기 | 디피의 개발일지
Posts 2096 내려가기
Post
Cancel

2096 내려가기

알고리즘

  • 내려올 수 있는 곳은 굉장히 한정되어있고, 순차적으로 가장 작은값을 or 가장 큰값을 구하기만 하면 되는 문제이기에 일차원 DP로 해결이 가능하다.
  • 이런 종류의 문제를 “슬라이딩 윈도우” 라고 부르는 것 같다

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+
#include <iostream>
+using namespace std;
+
+int n, ansMax = -1, ansMin = 2000000000, cur[2][3], tCur[2][3];
+
+int min(int a, int b) {
+	return a < b ? a : b;
+}
+int max(int a, int b) {
+	return a > b ? a : b;
+}
+
+int main() {
+	ios::sync_with_stdio(0);
+	cin.tie(0);
+
+	cin >> n;
+
+	int a, b, c;
+	cin >> cur[0][0] >> cur[0][1] >> cur[0][2];
+	for (int i = 0; i < 3; i++)
+		cur[1][i] = cur[0][i];
+
+	for (int i = 1; i < n; i++) {
+		cin >> a >> b >> c;
+		memcpy(tCur, cur, sizeof(cur));
+		cur[0][0] = max(tCur[0][0], tCur[0][1]) + a;
+		cur[0][1] = max(max(tCur[0][0], tCur[0][1]), tCur[0][2]) + b;
+		cur[0][2] = max(tCur[0][1], tCur[0][2]) + c;
+		cur[1][0] = min(tCur[1][0], tCur[1][1]) + a;
+		cur[1][1] = min(min(tCur[1][0], tCur[1][1]), tCur[1][2]) + b;
+		cur[1][2] = min(tCur[1][1], tCur[1][2]) + c;
+	}
+
+	for (int i = 0; i < 3; i++) {
+		ansMax = max(ansMax, cur[0][i]);
+		ansMin = min(ansMin, cur[1][i]);
+	}
+
+	cout << ansMax << " " << ansMin;
+}
+
+
This post is licensed under CC BY 4.0 by the author.

17387 선분교차 2

9251 LCS

Comments powered by Disqus.

diff --git a/posts/2098/index.html b/posts/2098/index.html new file mode 100644 index 000000000..001d6dad0 --- /dev/null +++ b/posts/2098/index.html @@ -0,0 +1,137 @@ + 2098 TSP | 디피의 개발일지
Posts 2098 TSP
Post
Cancel

2098 TSP

2098 TSP

알고리즘(외판원 순회, DP, 비트마스크)

1
+2
+3
+4
+5
+
1. 비트마스크로 각 비트마다 하나의 도시라고 치고, 검사를 함.
+2. 방문하지 않았고, 갈수 있는 도시를 방문하는 식으로 끝까지감.
+	-> 끝까지 갔을 때 다시 돌아갈 수 있는 길이 있으면 그 값 반환, 없으면 IMPOSSIBLE 반환
+	-> 그 전 재귀로 돌아와서 가장 최솟값을 dp[current][bitMask] 에 저장함.
+3. 각 재귀 처음엔 돌아갈 수 마지막인지 확인하고, 이미 방문됐는지 확인한다.
+

새겨둘 점.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
1. 모든 출발점에 대해 실행할 필요는 없다.
+	-> 최소로 가능한 경로가 있을 때, 어디서 출발하든 그 값은 같기 때문에.
+2. 끝까지 갔을 때, 그때부터 값을 하나씩 더해오는 식으로 계산한다.
+	-> 값을 들고가면 INFINITY값과 충돌을 일으켜 잘못된 값이 반환될 수 있게됨.
+	-> 또 이렇게하면 자동으로 dp엔 이후 경로의 최솟값이 저장됨.
+3. 따라서 각 단계에서 최솟값이 INFINITY 값이라 하더라도 무조건 dp에 저장하자.
+	-> INFINITY값이 저장됐다는 거는, 현재상태에서 순회할 수 있는 길이 없다는 뜻이므로.
+4. INFINITY값을 지정할 땐, 현재 value값을 더해도 오버플로우가 발생하지 않도록 지정해둘 필요가 있다.
+5. 비트마스크로 각 도시가 이미 방문됐는지 확인하는 코드는 다음과 같다.
+	 bitMask & (1 << i)
+ 이미 방문됐다면 이 값은 참을 가리키게 된다. ( 1011 & 0010 => 0010 => 참  / 1011 & 0100 => 0000 => 거짓)
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+
#include <iostream>
+#include <cstring>
+#define MAX_TOWN 16
+#define INFINITY 2000000123
+
+using namespace std;
+
+bool check[MAX_TOWN];
+int dp[MAX_TOWN][1 << MAX_TOWN];
+int map[MAX_TOWN][MAX_TOWN];
+int numOfTown, start, endBit;
+
+int getMinValue(int current, int bitMask);
+int min(int a, int b);
+
+int main() {
+	cin >> numOfTown;
+	for (int i = 0; i < numOfTown; i++)
+		for (int j = 0; j < numOfTown; j++) {
+			cin >> map[i][j];
+		}
+
+	endBit = (1 << numOfTown) - 1;
+	memset(dp, -1, sizeof(dp));
+	check[0] = true;
+	start = 0;
+	cout << getMinValue(0, 1);
+}
+
+int min(int a, int b) {
+	return a < b ? a : b;
+}
+
+int getMinValue(int current,int bitMask) {
+	if (bitMask == endBit) {
+		if (map[current][start] == 0)
+			return INFINITY;
+		return map[current][start];
+	}
+	if (dp[current][bitMask] != -1)
+		return dp[current][bitMask];
+
+	int tempMin = INFINITY;
+	for (int i = 0; i < numOfTown; i++) {
+		if (!check[i] && map[current][i] != 0) {
+			check[i] = true;
+			tempMin = min(tempMin, map[current][i] + getMinValue(i, bitMask + (1 << i)));
+			check[i] = false;
+		}
+	}
+	return dp[current][bitMask] = tempMin;
+}
+
This post is licensed under CC BY 4.0 by the author.

2056 task

2143 sumOfTwoArray

Comments powered by Disqus.

diff --git a/posts/2143/index.html b/posts/2143/index.html new file mode 100644 index 000000000..d260cfd6a --- /dev/null +++ b/posts/2143/index.html @@ -0,0 +1,165 @@ + 2143 sumOfTwoArray | 디피의 개발일지
Posts 2143 sumOfTwoArray
Post
Cancel

2143 sumOfTwoArray

2143 sumOfTwoArray

알고리즘(이분탐색, 누적합)

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
1. A배열은 그대로 두고, B 배열은 가능한 모든 누적합의 집합을 만들고, 그것을 정렬함
+		-> 이때 그냥 배열에 집어넣으면 최대 500500개의 원소가 있어서 메모리에서 박살나고, 찾는데 시간도 박살남
+		-> 난 set을 택했음
+		-> Pair 구조체를 선언해서, 누적합과 그 누적합이 나온 횟수를 저장함. 그리고 정렬과 비교연산자를 오버로딩하여 set에서 활용할 수 있게함
+		-> B의 누적합을 하나씩 만들어가며, set에 있는지 확인 -> 있으면 set에 이미 있는 Pair 객체의 count에 1을 더하고, 새로운 pair 객체에 넣음.
+									그 다음, 원래 있던 Pair 객체를 지우고, 새로만든 Pair 객체를 집어넣음
+								     -> 없으면 set에 구한 누적합과 count에 1을 넣어 만든 Pair 객체를 set에 삽입
+2. A배열의 누적합을 하나씩 만들어가며 B에 (T-누적합)을 가지는 Pair객체가 있는 지 확인
+	-> 있으면, 그 객체의 count를 전체 count에 더함
+	-> 없으면, 그냥 패스
+3. 전체 count는 int 범위를 넘을 수 있음
+

주의점

1
+2
+3
+4
+5
+
1. set에 구조체를 넣을때, 구조체 안에 오버로딩을 선언하는 경우 다음처럼 해야한다.
+	bool operator < (const Pair& p) const{
+		return value < p.value;
+	}
+	안그럼 오류남
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+
#include <iostream>
+#include <set>
+#define MAX_N 1000
+
+using namespace std;
+
+typedef struct Pair {
+	int value;
+	int count;
+	bool operator < (const Pair& p) const{
+		return value < p.value;
+	}
+	bool operator == (const Pair&  p) const{
+		return value == p.value;
+	}
+} Pair;
+
+int A[MAX_N];
+int B[MAX_N];
+set<Pair> Bset;
+int numOfA, numOfB, T;
+
+void makeBSet();
+long long getCount();
+
+int main() {
+	cin >> T >> numOfA;
+	for (int i = 0; i < numOfA; i++)
+		cin >> A[i];
+	cin >> numOfB;
+	for (int i = 0; i < numOfB; i++)
+		cin >> B[i];
+
+	makeBSet();
+	cout << getCount();
+}
+
+void makeBSet() {
+	for (int i = 0; i < numOfB; i++) {
+		int arraySum = 0;
+		for (int j = i; j < numOfB; j++) {
+			arraySum += B[j];
+			Pair temp = { arraySum, 1 };
+			set<Pair>::iterator iter = Bset.find(temp);
+			if (iter != Bset.end()) {
+				temp.count += iter->count;
+				Bset.erase(iter);
+			}
+			Bset.insert(temp);
+		}
+	}
+}
+
+long long getCount() {
+	long long count = 0;
+	for (int i = 0; i < numOfA; i++) {
+		int arraySum = 0;
+		for (int j = i; j < numOfA; j++) {
+			arraySum += A[j];
+			set<Pair>::iterator iter = Bset.find({ T - arraySum, 0 });
+			if (iter != Bset.end())
+				count += iter->count;
+		}
+	}
+	return count;
+}
+
This post is licensed under CC BY 4.0 by the author.

2098 TSP

2151 거울설치

Comments powered by Disqus.

diff --git a/posts/2151/index.html b/posts/2151/index.html new file mode 100644 index 000000000..41e2471d6 --- /dev/null +++ b/posts/2151/index.html @@ -0,0 +1,177 @@ + 2151 거울설치 | 디피의 개발일지
Posts 2151 거울설치
Post
Cancel

2151 거울설치

2151 거울설치

알고리즘 (BFS)

1
+2
+3
+4
+5
+6
+7
+
1. 자기 방향으로만 쭉 가다가 ! 를 만나면 자기방향 + 양옆으로 이동. 양옆으로 이동할 땐 거울을 설치하는 것이므로 거울 + 1.
+2. 이때 visit으로 이미왔던곳을 안가면, 다른 루트로 가는 빛은 중간에 끊기기에, visit에 빛이 그곳에 도착했을 때의 거울의 개수를 저장함.
+  그리고 다음 빛이 그곳에 도착했을 때, 거울 개수가 적을 때만 이동 아니면 이동시키지 않는다.
+	ex)
+		if( ....   &&  n + 1 < visit[r][c])
+			visit[r][c] = n + 1
+3. 이때 물론 거울을 사용하지 않고 그냥 자기 방향으로 가는 빛은 visit 검사하지 않는다.
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+
#include <bits/stdc++.h>
+
+using namespace std;
+
+char house[50][50];
+int DP[50][50];
+list<pair<pair<int, int>, pair<int, int>>> q;
+int N, r, c;
+
+int BFS();
+void nextRC(int* row, int* col, int way);
+
+int main() {
+	cin >> N;
+	for (int i = 0; i < N; i++)
+		for (int j = 0; j < N; j++) {
+			cin >> house[i][j];
+			if (house[i][j] == '#') {
+				r = i;
+				c = j;
+			}
+			DP[i][j] = 2000000000;
+		}
+
+	cout << BFS();
+}
+
+
+int BFS() {
+	int result = 2000000000;
+
+	if(r > 0 && house[r-1][c] != '*')
+		q.push_back({ {r, c}, {1, 0} });
+	if (r < N - 1 && house[r + 1][c] != '*')
+		q.push_back({ {r, c}, {3, 0} });
+	if (c < N - 1 && house[r][c + 1] != '*')
+		q.push_back({ {r, c}, {2, 0} });
+	if (c > 0 && house[r][c - 1] != '*')
+		q.push_back({ {r, c}, {4, 0} });
+
+	while (!q.empty()) {
+		int row = q.front().first.first, col = q.front().first.second, w = q.front().second.first, n = q.front().second.second;
+		q.pop_front();
+
+		if (house[row][col] == '#' && !(row == r && col == c)) {
+			result = result < n ? result : n;
+			continue;
+		}
+
+		nextRC(&row, &col, w);
+
+		//위로 갈수도, 느낌표일경우 좌우 가능
+
+		if ((row >= 0 && row <= N - 1) && (col >= 0 && col <= N - 1) && house[row][col] != '*') {
+			q.push_back({ {row, col}, {w, n} });
+			if (house[row][col] == '!' && n + 1 < DP[row][col]) {
+				DP[row][col] = n + 1;
+				if (w == 1 || w == 3) {
+					q.push_back({ {row, col}, {2, n + 1} });
+					q.push_back({ {row, col}, {4, n + 1} });
+				}
+				else {
+					q.push_back({ { row, col }, { 1, n + 1 } });
+					q.push_back({ { row, col }, { 3, n + 1 } });
+				}
+			}
+		}
+	}
+	return result;
+}
+
+void nextRC(int* row, int* col, int way) {
+	if (way == 1)
+		*row = (*row) - 1;
+	else if (way == 2)
+		*col = (*col) + 1;
+	else if (way == 3)
+		*row = (*row) + 1;
+	else if (way == 4)
+		*col = (*col) - 1;
+}
+
This post is licensed under CC BY 4.0 by the author.

2143 sumOfTwoArray

2166 areaOfPolygon

Comments powered by Disqus.

diff --git a/posts/2166/index.html b/posts/2166/index.html new file mode 100644 index 000000000..8ecbbf2a3 --- /dev/null +++ b/posts/2166/index.html @@ -0,0 +1,77 @@ + 2166 areaOfPolygon | 디피의 개발일지
Posts 2166 areaOfPolygon
Post
Cancel

2166 areaOfPolygon

2166 areaOfPolygon

알고리즘(기하학)

1
+
1. 사선정리 사용
+

구현법

1
+2
+
1. 최대 4000000000000이 나올 수 있으므로, int대신 long long, float은 쓰지말고 double로 통일 시킨다.
+2. double은 최대 15자리수를 표현가능하므로, 위 값도 충분히 들어갈 수 있다.
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+
#include <iostream>
+#include <cstdio>
+#include <cmath>
+#define MAX_POINT 10000
+
+using namespace std;
+
+typedef struct Point {
+	long long x;
+	long long y;
+} Point;
+
+Point point[MAX_POINT];
+int numOfPoint;
+
+double getAreaOfPolygon();
+
+int main() {
+	cin >> numOfPoint;
+	for (int i = 0; i < numOfPoint; i++)
+		cin >> point[i].x >> point[i].y;
+
+	printf("%.1f", getAreaOfPolygon());
+}
+
+double getAreaOfPolygon() {
+	double area = 0;
+
+	for (int i = 0; i < numOfPoint - 1; i++)
+		area += ((point[i].x * point[i + 1].y) - (point[i + 1].x * point[i].y));
+	area += (point[numOfPoint - 1].x * point[0].y) - (point[0].x * point[numOfPoint - 1].y);
+	area = area < 0 ? (-1) * area : area;
+	return area /= 2;
+}
+
+
This post is licensed under CC BY 4.0 by the author.

2151 거울설치

2186 문자판

Comments powered by Disqus.

diff --git a/posts/2186/index.html b/posts/2186/index.html new file mode 100644 index 000000000..6c93c7f47 --- /dev/null +++ b/posts/2186/index.html @@ -0,0 +1,109 @@ + 2186 문자판 | 디피의 개발일지
Posts 2186 문자판
Post
Cancel

2186 문자판

2186 문자판

알고리즘(DFS, DP)

1
+2
+
1. 문자판을 주어진 조건대로 순회하되, 결과값이 int 안이라는 걸보고 엄청 클 수도 잇다는 걸 예측 or 빙글빙글 계속 돌 수 있으니 DP가 잇어야겠다고 예측
+2. DP를 DP[r][c][d] 로 구현하되, -1로 초기화를 시키고, 0인것도 기록 -> 문자열이 아예 없을 수도 잇으니 0도 기록하여 더 빨리 DP가 가능해짐.
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+
#include <bits/stdc++.h>
+
+using namespace std;
+
+int N, M, K, length, DP[101][101][81];
+char board[101][101], keyword[81];
+
+int generateDFS();
+int DFS(int, int, int);
+
+int main() {
+	cin >> N >> M >> K;
+	for (int i = 0; i < N; i++)
+		for (int j = 0; j < M; j++)
+			cin >> board[i][j];
+	string t;
+	cin >> t;
+	for (int i = 0; i < t.size(); i++)
+		keyword[i] = t[i];
+	length = t.size();
+
+	cout << generateDFS();
+}
+
+int generateDFS() {
+	int result = 0;
+	for (int i = 0; i < N; i++)
+		for (int j = 0; j < M; j++)
+			if (board[i][j] == keyword[0])
+				result += DFS(i, j, 0);
+	return result;
+}
+
+int DFS(int r, int c, int d) {
+	if (d == length - 1)
+		return 1;
+	if (DP[r][c][d] != 0)
+		return DP[r][c][d];
+
+	int result = 0;
+	for (int i = 1; i <= K; i++) {
+		if (r - i >= 0 && board[r - i][c] == keyword[d + 1])
+			result += DFS(r - i, c, d + 1);
+		if (r + i <= N - 1 && board[r + i][c] == keyword[d + 1])
+			result += DFS(r + i, c, d + 1);
+		if (c - i >= 0 && board[r][c - i] == keyword[d + 1])
+			result += DFS(r, c - i, d + 1);
+		if (c + i <= M - 1 && board[r][c + i] == keyword[d + 1])
+			result += DFS(r, c + i, d + 1);
+	}
+	return DP[r][c][d] = result;
+}
+
This post is licensed under CC BY 4.0 by the author.

2166 areaOfPolygon

2239 sudoku

Comments powered by Disqus.

diff --git a/posts/2239/index.html b/posts/2239/index.html new file mode 100644 index 000000000..0768cc177 --- /dev/null +++ b/posts/2239/index.html @@ -0,0 +1,263 @@ + 2239 sudoku | 디피의 개발일지
Posts 2239 sudoku
Post
Cancel

2239 sudoku

2239 sudoku

알고리즘(백트래킹)

1
+2
+3
+4
+
1. 칸을 하나씩 탐방하면서, 0으로 기록돼있다면 수직, 수평, 3x3사각형을 검사하여 가능한 숫자를 하나씩 뽑아서 넣고 다음 칸 검사
+2. 만약 0으로 기록되지않은 칸으로 갔다면 다음칸으로 넘겨주되, 마지막 칸일 경우 스도쿠가 완성된 것이므로 true를 반환한다.
+3. 마찬가지로, 넣고 다음 칸으로 이동하는데, 마지막칸일 경우 스도쿠가 완성된 것이므로 true를 반환한다.
+4. 최소일때를 반환하는 것이므로 먼저 검사하는 칸을 왼쪽 위로하고, 1부터 검사하여 가능한 걸 넣으면 된다.
+

구현

1
+2
+3
+4
+5
+6
+7
+8
+9
+
1. 내가 구현한 건 맨처음에 가능한 숫자를 먼저 뽑고, 그 가능한 숫자만 가지고 검사하고 넣고 빼고 했다.
+2. 덕분에 시간이 좀 줄었지만, 체크하는데 매번 체크하여 시간이 좀 걸린 거 같다.
+3. 체크하는 걸 좀 더 간결히 하는 방법은 각 칸마다 3개의 9개의 int원소를 가지는 배열을 가진다.
+	ex) int checkMap[WIDTH][WIDTH][3][WIDTH+1];
+ 그리고 맨 처음에, 숫자를 넣었을 때마다 각각 수직, 수평, 3x3 사각형의 각 칸에 원래 가지고 있던 숫자 또는 넣은 숫자를 기록한다.
+ 수평일 경우 checmMap[row][col][0][value], 수직일 경우 checkMap[row][col][1][value], 사각형일경우 checkMap[row][col][2][value]
+ 이렇게하면 넣을 수 있는지 검사할 때 그냥 3개의 배열을 검사하여 있는지만 확인하면 되므로 더 간결해진다.
+ 또, 처음에 가능한 숫자를 먼저 뽑지 않아도 된다.  물론 false가 반환됐을 시, 다시 0으로 바꿔줄 필요도 있다.
+-> 내일 해보자.
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+
#include <iostream>
+#include <cstring>
+#include <cmath>
+#define WIDTH 9
+
+using namespace std;
+
+int map[WIDTH][WIDTH];
+int possibleNum[WIDTH][WIDTH][WIDTH+1];
+
+void generatePossibleNum();
+bool solveSudoku(int row, int col);
+bool checkVertical(int row, int col, int value);
+bool checkHorizontal(int row, int col, int value);
+bool checkSquare(int row, int col, int value);
+
+int main() {
+	int input[WIDTH];
+	for (int i = 0; i < WIDTH; i++)
+			cin >> input[i];
+	for (int i = 0; i < WIDTH; i++) {
+		for (int j = WIDTH - 1; j >=0; j--) {
+			map[i][j] = input[i] % 10;
+			input[i] /= 10;
+		}
+	}
+	generatePossibleNum();
+	solveSudoku(0,0);
+
+	for (int i = 0; i < WIDTH; i++) {
+		for (int j = 0; j < WIDTH; j++)
+			cout << map[i][j];
+		cout << endl;
+	}
+}
+
+void generatePossibleNum() {
+	for (int i = 0; i < WIDTH; i++)
+		for (int j = 0; j < WIDTH; j++)
+			if (map[i][j] == 0) {
+				for (int k = 1; k <= WIDTH; k++)
+					possibleNum[i][j][k] = k;
+				for (int vert = 0; vert < WIDTH; vert++)
+					possibleNum[i][j][map[vert][j]] = 0;
+				for (int horiz = 0; horiz < WIDTH; horiz++)
+					possibleNum[i][j][map[i][horiz]] = 0;
+			}
+
+	for (int i = 0; i < 3; i++) {
+		for (int j = 0; j < 3; j++) {
+			int row = i * 3, col = j * 3;
+			for (int r = 0; r < 3; r++) {
+				for (int c = 0; c < 3; c++) {
+					for (int r2 = 0; r2 < 3; r2++)
+						for (int c2 = 0; c2 < 3; c2++)
+							possibleNum[row + r][col + c][map[row + r2][col + c2]] = 0;
+				}
+			}
+		}
+	}
+}
+
+bool solveSudoku(int row, int col) {
+	if (map[row][col] != 0) {
+		if (col < WIDTH - 1)
+			return solveSudoku(row, col + 1);
+		else if (row < WIDTH - 1)
+			return solveSudoku(row + 1, 0);
+		else
+			return true;
+	}
+
+	for (int i = 1; i <= WIDTH; i++) {
+		if (possibleNum[row][col][i] != 0 && checkVertical(row, col, i) && checkHorizontal(row, col, i) && checkSquare(row, col, i)) {
+			map[row][col] = i;
+			if (col < WIDTH - 1) {
+				if (solveSudoku(row, col + 1))
+					return true;
+			}
+			else if (row < WIDTH - 1) {
+				if (solveSudoku(row + 1, 0))
+					return true;
+			}
+			else
+				return true;
+			map[row][col] = 0;
+		}
+	}
+	return false;
+}
+
+bool checkVertical(int row, int col, int value) {
+	for (int vert = 0; vert < WIDTH; vert++) {
+		if (map[vert][col] == value)
+			return false;
+	}
+	return true;
+}
+bool checkHorizontal(int row, int col, int value) {
+	for (int horiz = 0; horiz < WIDTH; horiz++) {
+		if (map[row][horiz] == value)
+			return false;
+	}
+	return true;
+}
+bool checkSquare(int row, int col, int value) {
+	int sr = row / 3, sc = col / 3;
+	sr *= 3;
+	sc *= 3;
+
+	for (int r = 0; r < 3; r++) {
+		for (int c = 0; c < 3; c++) {
+			if (map[sr + r][sc + c] == value)
+				return false;
+		}
+	}
+	return true;
+}
+
This post is licensed under CC BY 4.0 by the author.

2186 문자판

2252 줄세우기

Comments powered by Disqus.

diff --git a/posts/2252/index.html b/posts/2252/index.html new file mode 100644 index 000000000..54eb5e0de --- /dev/null +++ b/posts/2252/index.html @@ -0,0 +1,167 @@ + 2252 줄세우기 | 디피의 개발일지
Posts 2252 줄세우기
Post
Cancel

2252 줄세우기

2252 줄세우기

알고리즘(위상정렬)

1
+
1. 그냥 위상정렬하면된다.
+

구현

1
+2
+3
+4
+5
+6
+7
+8
+9
+
1. Student 구조체 : beforeCount : 이전에 몇개있는지 셈, next : 큰 학생들 나열, isChecked : 라인에 넣어졌는지 확인
+2. 처음에 beforeCount가 0인 학생들을 큐에 넣고, isCheked에 체크함
+3. 하나씩 큐에서 빼면서 아래 시행
+	-라인에 넣고
+	-next에 있는 학생들의 beforeCount를 1개씩 줄임
+	-이때, 그 학생의 beforeCount가 0이 되면, 임시 큐에 넣고 isChecked에 TRUE 표시
+4. 큐에서 다 빼면, 임시 큐를 큐에 복사
+5. 다 빠질 때까지 반복
+6. 안넣어진 학생들을 따로 뒤에 집어넣음
+

기타

1
+2
+3
+4
+5
+
1. next에 있는 학생들의 beforeCount를 하나씩 줄이고, 0되면 바로 집어넣는 게, 다시 처음부터 검사하고 넣는 것보다 훨씬 빠르다.
+2. for(int i = 0; i<q.size(); i++) {
+	int cur = q.front();
+	q.pop_front();
+ 는 매우 위험하다. pop_front() 될 때마다 size가 달라지므로 size는 처음에 다른 곳에 length로 저장해놓고 하는게 좋다.
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+
#include <iostream>
+#include <vector>
+#include <list>
+#define MAX_STUDENT 32000
+using namespace std;
+
+typedef struct Student {
+	int beforeCount;
+	bool isChecked;
+	vector<int> next;
+} Student;
+
+Student student[MAX_STUDENT];
+int numOfStudent;
+int line[MAX_STUDENT];
+
+void setLine();
+
+int main() {
+	int numOfCompare;
+	cin >> numOfStudent >> numOfCompare;
+	for (int i = 0; i < numOfCompare; i++) {
+		int a, b;
+		cin >> a >> b;
+		a--;
+		b--;
+		student[a].next.push_back(b);
+		student[b].beforeCount++;
+	}
+
+	setLine();
+	for (int i = 0; i < numOfStudent; i++)
+		cout << line[i] + 1 << " ";
+}
+
+void setLine() {
+	list<int> q;
+	for (int i = 0; i < numOfStudent; i++)
+		if (student[i].beforeCount == 0) {
+			q.push_back(i);
+			student[i].isChecked = true;
+		}
+
+	int lineIndex = 0;
+	while (!q.empty()) {
+		list<int> temp;
+		int length = q.size();
+		//꺼내면서 줄 세우고, next로 연결될 곳 before 하나씩 지우자.
+		for (int i = 0; i < length; i++) {
+			int cur = q.front();
+			q.pop_front();
+			line[lineIndex++] = cur;
+			for (int j = 0; j < student[cur].next.size(); j++) {
+				student[student[cur].next[j]].beforeCount--;
+				if (student[student[cur].next[j]].beforeCount == 0 && !student[student[cur].next[j]].isChecked) {
+					temp.push_back(student[cur].next[j]);
+					student[student[cur].next[j]].isChecked = true;
+				}
+			}
+		}
+		q = temp;
+	}
+
+	//check 안된거 그냥 하나씩 집어넣기
+	for (int i = 0; i < numOfStudent; i++)
+		if (!student[i].isChecked)
+			line[lineIndex++] = i;
+}
+
This post is licensed under CC BY 4.0 by the author.

2239 sudoku

2342 Dance Dance Revolution

Comments powered by Disqus.

diff --git a/posts/2342/index.html b/posts/2342/index.html new file mode 100644 index 000000000..e16f69f7b --- /dev/null +++ b/posts/2342/index.html @@ -0,0 +1,215 @@ + 2342 Dance Dance Revolution | 디피의 개발일지
Posts 2342 Dance Dance Revolution
Post
Cancel

2342 Dance Dance Revolution

2342 Dance Dance Revolution

알고리즘(DP)

1
+2
+3
+
1. 왼발 위치, 오른발 위치, 명령 번호를 기준으로 dp 자료를 만듬
+	dp[5][5][MAX_COMMAND]
+2. 명령과 같은 칸에 있을 경우 그대로, 다른 위치에 있을 경우 가능한 위치엔 전부 옮기는 식으로 DP 시행.
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+
#include <iostream>
+#include <cstring>
+#define MAX_COMMAND 1000000
+
+using namespace std;
+
+int command[MAX_COMMAND];
+int numOfCommand;
+int foot[2];
+int dp[5][5][MAX_COMMAND];
+
+int getMinEnergy(int cmd);
+int min(int a, int b) {
+	return a < b ? a : b;
+}
+bool isClose(int f, int cmd);
+bool isOpposite(int f, int cmd);
+
+int main() {
+	while (true) {
+		cin >> command[numOfCommand];
+		if (command[numOfCommand] == 0)
+			break;
+		numOfCommand++;
+	}
+
+	cout << getMinEnergy(0);
+}
+
+int getMinEnergy(int cmd) {
+	if (cmd == numOfCommand)
+		return 0;
+	if (dp[foot[0]][foot[1]][cmd] != 0)
+		return dp[foot[0]][foot[1]][cmd];
+
+	int minimum = 2000000000;
+	//같은 자리에 있는 발이 있는지 검사
+	if (command[cmd] == foot[0] || command[cmd] == foot[1]) {
+		minimum = min(minimum, getMinEnergy(cmd + 1) + 1);
+	}
+	else {
+		//중앙에 있는 지 확인
+		if (foot[0] == 0) {
+			foot[0] = command[cmd];
+			minimum = min(minimum, getMinEnergy(cmd + 1) + 2);
+			foot[0] = 0;
+		}
+		if (foot[1] == 0) {
+			foot[1] = command[cmd];
+			minimum = min(minimum, getMinEnergy(cmd + 1) + 2);
+			foot[1] = 0;
+		}
+
+		int tempLeft = foot[0];
+		int tempRight = foot[1];
+		//인접한지 확인
+		if (isClose(foot[0], command[cmd])) {
+			foot[0] = command[cmd];
+			minimum = min(minimum, getMinEnergy(cmd + 1) + 3);
+			foot[0] = tempLeft;
+		}
+		if (isClose(foot[1], command[cmd])) {
+			foot[1] = command[cmd];
+			minimum = min(minimum, getMinEnergy(cmd + 1) + 3);
+			foot[1] = tempRight;
+		}
+		//반대편으로
+		if (isOpposite(foot[0], command[cmd])) {
+			foot[0] = command[cmd];
+			minimum = min(minimum, getMinEnergy(cmd + 1) + 4);
+			foot[0] = tempLeft;
+		}
+		if (isOpposite(foot[1], command[cmd])) {
+			foot[1] = command[cmd];
+			minimum = min(minimum, getMinEnergy(cmd + 1) + 4);
+			foot[1] = tempRight;
+		}
+	}
+	return dp[foot[0]][foot[1]][cmd] = minimum;
+}
+
+bool isClose(int f, int cmd) {
+	if (f == 1 && (cmd == 2 || cmd == 4))
+		return true;
+	if (f == 2 && (cmd == 1 || cmd == 3))
+		return true;
+	if (f == 3 && (cmd == 2 || cmd == 4))
+		return true;
+	if (f == 4 && (cmd == 1 || cmd == 3))
+		return true;
+	return false;
+}
+
+bool isOpposite(int f, int cmd) {
+	if (f == 1 && cmd == 3)
+		return true;
+	if (f == 2 && cmd == 4)
+		return true;
+	if (f == 3 && cmd == 1)
+		return true;
+	if (f == 4 && cmd == 2)
+		return true;
+	return false;
+}
+
This post is licensed under CC BY 4.0 by the author.

2252 줄세우기

2529 부등호

Comments powered by Disqus.

diff --git a/posts/2437/index.html b/posts/2437/index.html new file mode 100644 index 000000000..4427c8d79 --- /dev/null +++ b/posts/2437/index.html @@ -0,0 +1,43 @@ + 2437 저울 | 디피의 개발일지
Posts 2437 저울
Post
Cancel

2437 저울

알고리즘

  1. 정렬 후 앞에서부터 검사
  2. (이전까지 연속적으로 잴 수 있는 무게의 최댓값 + 1) 보다 현재 추가 더 무거우면 break. 최댓값 + 1이 잴수없는 최소의 값임.

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+
#include <bits/stdc++.h>
+
+using namespace std;
+
+int n, weights[1010];
+
+int main() {
+	cin >> n;
+	for (int i = 0; i < n; i++)
+		cin >> weights[i];
+	sort(&weights[0], &weights[n]);
+
+	int result = 0;
+	for (int i = 0; i < n; i++) {
+		if (weights[i] > result + 1)
+			break;
+		result += weights[i];
+	}
+
+	cout << result + 1;
+}
+
This post is licensed under CC BY 4.0 by the author.

15971 두 로봇

14238 출근기록

Comments powered by Disqus.

diff --git a/posts/2473/index.html b/posts/2473/index.html new file mode 100644 index 000000000..b9b8c47b6 --- /dev/null +++ b/posts/2473/index.html @@ -0,0 +1,267 @@ + 2473 Three Liquid | 디피의 개발일지
Posts 2473 Three Liquid
Post
Cancel

2473 Three Liquid

2473 Three Liquid

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+
#include <iostream>
+#include <algorithm>
+#include <set>
+#define MAX_LIQUID 5000
+
+using namespace std;
+
+int liquid[MAX_LIQUID];
+int numOfLiquid;
+int threeLiquid[3];
+
+void getZerost();
+int abs(int a) {
+	return a < 0 ? (-1) * a : a;
+}
+void setThreeLiquid(int f, int s, int t) {
+	threeLiquid[0] = f;
+	threeLiquid[1] = s;
+	threeLiquid[2] = t;
+}
+
+int main() {
+	cin >> numOfLiquid;
+	for (int i = 0; i < numOfLiquid; i++)
+		cin >> liquid[i];
+
+	getZerost();
+	for (int i = 0; i < 3; i++)
+		cout << threeLiquid[i] << " ";
+}
+
+void getZerost() {
+	sort(liquid, &liquid[numOfLiquid]);
+	int firstPlus = numOfLiquid;
+	for (int i = 0; i < numOfLiquid; i++)
+		if (liquid[i] > 0) {
+			firstPlus = i;
+			break;
+		}
+	if (firstPlus == numOfLiquid) {
+		setThreeLiquid(liquid[numOfLiquid - 3], liquid[numOfLiquid - 2], liquid[numOfLiquid - 1]);
+		return;
+	}
+	if (firstPlus == 0) {
+		setThreeLiquid(liquid[0], liquid[1], liquid[2]);
+		return;
+	}
+
+	int zerostValue = 2000000213;
+	if (firstPlus > 2 && firstPlus <= numOfLiquid - 3) {
+		if (abs(liquid[firstPlus-3] + liquid[firstPlus-2] + liquid[firstPlus-1]) < liquid[firstPlus] + liquid[firstPlus + 1] + liquid[firstPlus + 2]) {
+			zerostValue = abs(liquid[firstPlus - 3] + liquid[firstPlus - 2] + liquid[firstPlus - 1]);
+			setThreeLiquid(liquid[firstPlus - 3], liquid[firstPlus - 2], liquid[firstPlus - 1]);
+		}
+		else {
+			zerostValue = liquid[firstPlus] + liquid[firstPlus + 1] + liquid[firstPlus + 2];
+			setThreeLiquid(liquid[firstPlus], liquid[firstPlus + 1] ,liquid[firstPlus + 2]);
+		}
+	}
+	else if (firstPlus > 2) {
+		zerostValue = abs(liquid[firstPlus - 3] + liquid[firstPlus - 2] + liquid[firstPlus - 1]);
+		setThreeLiquid(liquid[firstPlus - 3], liquid[firstPlus - 2], liquid[firstPlus - 1]);
+	}
+	else if (firstPlus <= numOfLiquid - 3) {
+		zerostValue = liquid[firstPlus] + liquid[firstPlus + 1] + liquid[firstPlus + 2];
+		setThreeLiquid(liquid[firstPlus], liquid[firstPlus + 1], liquid[firstPlus + 2]);
+	}
+
+	//-97 -6 -2 6 98
+	//음수 2개, 양수 1개
+	if (firstPlus >= 2) {
+		set<int> s;
+		for (int i = firstPlus; i < numOfLiquid; i++)
+			s.insert(liquid[i]);
+		for (int first = 0; first <= firstPlus - 2; first++) {
+			for (int second = first + 1; second <= firstPlus - 1; second++) {
+				int cur = liquid[first] + liquid[second];
+				set<int>::iterator iter = s.find(abs(cur));
+				if (iter == s.end()) {
+					iter = s.insert(abs(cur)).first;
+					iter = s.erase(iter);
+					if (iter != s.end() &&(cur + *iter < zerostValue)) {
+						zerostValue = cur + *iter;
+						setThreeLiquid(liquid[first], liquid[second], *iter);
+					}
+					if (iter != s.begin()) {
+						iter--;
+						if (abs(cur + *iter) < zerostValue) {
+							zerostValue = abs(cur + *iter);
+							setThreeLiquid(liquid[first], liquid[second], *iter);
+						}
+					}
+				}
+				else {
+					setThreeLiquid(liquid[first], liquid[second], *iter);
+					return;
+				}
+			}
+		}
+	}
+	//음수 1개, 양수 2개
+	if (firstPlus < numOfLiquid - 1) {
+		set<int> s;
+		for (int i = 0; i < firstPlus; i++)
+			s.insert(liquid[i]);
+		for (int first = 0; first <= firstPlus - 1; first++) {
+			for (int second = firstPlus; second < numOfLiquid - 1; second++) {
+				int cur = liquid[first] + liquid[second];
+				set<int>::iterator iter = s.find(0 - cur);
+				if (iter == s.end()) {
+					iter = s.insert(0 - cur).first;
+					iter = s.erase(iter);
+					if (iter != s.end() && (cur + *iter < zerostValue)) {
+						zerostValue = cur + *iter;
+						setThreeLiquid(*iter, liquid[first], liquid[second]);
+					}
+					if (iter != s.begin()) {
+						iter--;
+						if (abs(cur + *iter) < zerostValue) {
+							zerostValue = abs(cur + *iter);
+							setThreeLiquid(*iter, liquid[first], liquid[second]);
+						}
+					}
+				}
+				else {
+					setThreeLiquid(*iter, liquid[first], liquid[second]);
+					return;
+				}
+			}
+		}
+
+	}
+}
+
This post is licensed under CC BY 4.0 by the author.

20058 Samsung sw test

2623 MusicPropram

Comments powered by Disqus.

diff --git a/posts/2529/index.html b/posts/2529/index.html new file mode 100644 index 000000000..db27720af --- /dev/null +++ b/posts/2529/index.html @@ -0,0 +1,127 @@ + 2529 부등호 | 디피의 개발일지
Posts 2529 부등호
Post
Cancel

2529 부등호

2529 부등호

알고리즘(브루트포스, 백트래킹)

1
+2
+3
+
1. 모든 경우를 탐색하되, 앞에서부터 하나씩 채워가면서 재귀형태로 하면 백트래킹도 적용하기 쉽다.
+2. 조건에 맞는 경우만 탐색하면 됨.
+3. 낮은 거 먼저 탐색하도록하면 굳이 뭘 저장할 필요없이 맨처음것만 minimum에 저장하고, 제일 나중 것을 maxnimum에 저장하면 된다.
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+
#include <iostream>
+
+using namespace std;
+
+int N, arr[10];
+char str[9], mini[10], maxi[10];
+bool check[10], isMini;
+
+void brute(int deep);
+
+int main() {
+	cin >> N;
+	for (int i = 0; i < N; i++)
+		cin >> str[i];
+
+	for (int i = 0; i < 10; i++) {
+		arr[0] = i;
+		check[i] = true;
+		brute(0);
+		check[i] = false;
+	}
+
+	cout << maxi << endl;
+	cout << mini << endl;
+}
+
+void brute(int deep) {
+	if (deep == N) {
+		if (!isMini) {
+			for (int i = 0; i < N + 1; i++)
+				mini[i] = arr[i] + '0';
+			isMini = true;
+		}
+		for (int i = 0; i < N + 1; i++)
+			maxi[i] = arr[i] + '0';
+		cout << "check : " << maxi << endl;
+		return;
+	}
+
+	if (str[deep] == '>') {
+		for (int i = 0; i <= arr[deep] - 1; i++) {
+			if (!check[i]) {
+				arr[deep + 1] = i;
+				check[i] = true;
+				brute(deep + 1);
+				check[i] = false;
+			}
+		}
+	}
+	else {
+		for (int i = arr[deep] + 1; i <= 9; i++) {
+			if (!check[i]) {
+				arr[deep + 1] = i;
+				check[i] = true;
+				brute(deep + 1);
+				check[i] = false;
+			}
+		}
+	}
+}
+
This post is licensed under CC BY 4.0 by the author.

2342 Dance Dance Revolution

2568 전기줄

Comments powered by Disqus.

diff --git a/posts/2568/index.html b/posts/2568/index.html new file mode 100644 index 000000000..8d2be34e9 --- /dev/null +++ b/posts/2568/index.html @@ -0,0 +1,131 @@ + 2568 전기줄 | 디피의 개발일지
Posts 2568 전기줄
Post
Cancel

2568 전기줄

2568 전기줄

알고리즘(LIS)

1
+2
+
1. LIS를 구하면 그것들이 겹치지 않고 최대로 연결가능한 전깃줄들이다.
+2. 따라서 LIS를 구하고 전체 전깃줄에서 LIS를 빼면 빼야하는 전깃줄의 수와 전깃줄의 종류가 나오게 된다.
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+
#include <bits/stdc++.h>
+#define MAX_WIRE 100001
+
+using namespace std;
+
+struct Wire {
+	int a;
+	int b;
+	bool check;
+	bool operator <(Wire s) {
+		return a < s.a;
+	}
+};
+
+Wire wire[MAX_WIRE];
+int numOfWire, length[500001], save[500001], numOfSave;
+
+void LIS();
+
+int main() {
+	ios::sync_with_stdio(0);
+	cin.tie(0);
+	cout.tie(0);
+
+	cin >> numOfWire;
+	for (int i = 0; i < numOfWire; i++)
+		cin  >> wire[i].a >> wire[i].b;
+
+	sort(wire, wire + numOfWire);
+
+	LIS();
+}
+
+void LIS() {
+	int result = 0, index = 0;
+	for (int i = 0; i < numOfWire; i++) {
+		int *lb = lower_bound(save, save + numOfSave, wire[i].b);
+
+		length[wire[i].a] = lb - save + 1;
+		*lb = wire[i].b;
+
+		if (length[wire[i].a] ==  numOfSave + 1)
+			numOfSave++;
+
+		if (result < length[wire[i].a]) {
+			result = length[wire[i].a];
+			index = i;
+		}
+	}
+
+	cout << numOfWire - result << "\n";
+	for (int i = index; i >= 0; i--) {
+		if (result <= 0)
+			break;
+		if (length[wire[i].a] == result) {
+			wire[i].check = true;
+			result--;
+		}
+	}
+	for (int i = 0; i < numOfWire; i++)
+		if (!wire[i].check)
+			cout << wire[i].a << "\n";
+}
+
This post is licensed under CC BY 4.0 by the author.

2529 부등호

2887 planet tunnel

Comments powered by Disqus.

diff --git a/posts/2623/index.html b/posts/2623/index.html new file mode 100644 index 000000000..e2b50e1cd --- /dev/null +++ b/posts/2623/index.html @@ -0,0 +1,227 @@ + 2623 MusicPropram | 디피의 개발일지
Posts 2623 MusicPropram
Post
Cancel

2623 MusicPropram

2623 MusicPropram

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+
#include <iostream>
+#include <vector>
+#include <list>
+#include <cstring>
+#define MAX_SINGER 1000
+#define MAX_PD 100
+
+using namespace std;
+
+typedef struct Singer {
+	int beforeCount;
+	bool isCheked;
+	vector<int> next;
+} Singer;
+
+Singer singer[MAX_SINGER];
+int order[MAX_SINGER], numOfSinger;
+vector<vector<int>> set;
+int check[MAX_SINGER];
+bool checkDup[MAX_SINGER][MAX_SINGER];
+int pd[MAX_SINGER];
+
+bool unionFind(int forward, int backward);
+void makeOrder();
+
+int main() {
+	memset(check, -1, sizeof(check));
+	int numOfPd;
+	cin >> numOfSinger >> numOfPd;
+	for (int i = 0; i < numOfPd; i++) {
+		int ns;
+		cin >> ns;
+		for (int j = 0; j < ns; j++) {
+			cin >> pd[j];
+			pd[j]--;
+			if (j != 0) {
+				for (int k = 0; k < j; k++) {
+					if (!checkDup[pd[k]][pd[j]]) {
+						if (unionFind(pd[k], pd[j])) {
+							singer[pd[k]].next.push_back(pd[j]);
+							singer[pd[j]].beforeCount++;
+							checkDup[pd[k]][pd[j]] = true;
+						}
+						else {
+							cout << 0 << endl;
+							return 0;
+						}
+					}
+				}
+			}
+		}
+	}
+
+	makeOrder();
+	for (int i = 0; i < numOfSinger; i++)
+		cout << order[i] + 1 << endl;
+}
+
+bool unionFind(int forward, int backward) {
+	if (check[forward] == -1 && check[backward] == -1) {
+		vector<int> temp;
+		temp.push_back(forward);
+		temp.push_back(backward);
+		set.push_back(temp);
+		check[forward] = set.size() - 1;
+		check[backward] = set.size() - 1;
+	}
+	else if (check[forward] == -1) {
+		set[check[backward]].push_back(forward);
+		check[forward] = check[backward];
+	}
+	else if (check[backward] == -1) {
+		set[check[forward]].push_back(backward);
+		check[backward] = check[forward];
+	}
+	else if (check[forward] != check[backward]) {
+		for (int i = 0; i < set[check[backward]].size(); i++) {
+			check[set[check[backward]][i]] = check[forward];
+			set[check[forward]].push_back(set[check[backward]][i]);
+		}
+		set[check[backward]].clear();
+	}
+	else {
+		return false;
+	}
+	return true;
+}
+
+void makeOrder() {
+	list<int> q;
+	int orderIndex = 0;
+	for (int i = 0; i < numOfSinger; i++)
+		if (singer[i].beforeCount == 0) {
+			q.push_back(i);
+			singer[i].isCheked = true;
+		}
+
+	while (!q.empty()) {
+		int length = q.size();
+		for (int i = 0; i < length; i++) {
+			int temp = q.front();
+			q.pop_front();
+			order[orderIndex++] = temp;
+			for (int j = 0; j < singer[temp].next.size(); j++) {
+				singer[singer[temp].next[j]].beforeCount--;
+				if (singer[singer[temp].next[j]].beforeCount == 0 && !singer[singer[temp].next[j]].isCheked) {
+					singer[singer[temp].next[j]].isCheked = true;
+					q.push_back(singer[temp].next[j]);
+				}
+			}
+		}
+	}
+}
+
This post is licensed under CC BY 4.0 by the author.

2473 Three Liquid

3568 iSharp

Comments powered by Disqus.

diff --git "a/posts/2629-\354\226\221\355\214\224\354\240\200\354\232\270/index.html" "b/posts/2629-\354\226\221\355\214\224\354\240\200\354\232\270/index.html" new file mode 100644 index 000000000..37d280aa0 --- /dev/null +++ "b/posts/2629-\354\226\221\355\214\224\354\240\200\354\232\270/index.html" @@ -0,0 +1,87 @@ + 2629 양팔저울 | 디피의 개발일지
Posts 2629 양팔저울
Post
Cancel

2629 양팔저울

알고리즘

  • 모든 경우를 따지기 위해서 각 추가 구슬 반대쪽에 올라가는 경우, 저울에 안올라가는 경우, 구슬과 같은 쪽에 올라갈 경우를 모두 고려해야함
  • 배낭문제를 응용하여, 추를 하나씩 모든 무게에 대해 검사를 한다.

    • true이면 구슬 반대쪽 저울에 추만으로 만들 수 있는 무게라는 뜻.
    • 현재 추를 사용하여 만들 수 있는 무게면 true표시
    • 현재 추를 사용하지않고 이전 추로만으로도 만들 수 있는 무게면, 현재추를 구슬과 같은 저울에 놓고 만들 수 있는 무게도 true 표시
  • 위 알고리즘을 진행 후 마지막 줄에 가능한 모든 경우에 true가 표시되어있다.

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+
#include <iostream>
+
+using namespace std;
+
+int n, w[30];
+bool check[31][15001];
+
+int abs(int a) {
+	return a < 0 ? -a : a;
+}
+
+int main() {
+	cin >> n;
+	for (int i = 0; i < n; i++)
+		cin >> w[i];
+	for (int i = 0; i <= 30; i++)
+		check[i][0] = true;
+
+	for (int j = 1; j <= n; j++) {
+		for (int i = 1; i <= 15000; i++) {
+			if (w[j - 1] <= i && check[j - 1][i - w[j - 1]]) {
+				check[j][i] = true;
+			}
+			else if (check[j - 1][i]) {
+				check[j][i] = true;
+				check[j][abs(i - w[j - 1])] = true;
+			}
+		}
+	}
+
+	int c, t;
+	cin >> c;
+	for (int i = 0; i < c; i++) {
+		cin >> t;
+		if (t > 15000)
+			cout << "N ";
+		else if (check[n][t])
+			cout << "Y ";
+		else
+			cout << "N ";
+	}
+
+}
+
This post is licensed under CC BY 4.0 by the author.

10986 나머지합

flutter 첫 걸음

Comments powered by Disqus.

diff --git "a/posts/2631\354\244\204\354\204\270\354\232\260\352\270\260/index.html" "b/posts/2631\354\244\204\354\204\270\354\232\260\352\270\260/index.html" new file mode 100644 index 000000000..870406eb1 --- /dev/null +++ "b/posts/2631\354\244\204\354\204\270\354\232\260\352\270\260/index.html" @@ -0,0 +1,75 @@ + 2631 줄세우기 | 디피의 개발일지
Posts 2631 줄세우기
Post
Cancel

2631 줄세우기

알고리즘

  • LIS를 구하고, LIS를 구성하지 않는 요소들만 배치해주면 됨

  • LIS를 구하는 방법으론 n이 최대 200이니 O(n^2)을 써도 충분함

    • LIS 구하는 코드 O(n^2)

      • 이전 요소들 중에 자기보다 작은 것 중, 가장 큰 LIS를 가진 요소에 이어 붙임
      1
      +2
      +3
      +4
      +5
      +6
      +7
      +8
      +9
      +10
      +
      for (int i = 1; i <= n; i++) {
      +		save[i] = 1;
      +    
      +		for (int j = 1; j < n; j++) {
      +			if (nums[j] < nums[i])
      +				save[i] = max(save[i], save[j] + 1);
      +		}
      +		ans = max(ans, save[i]);
      +}
      +    
      +

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+
#include <iostream>
+
+using namespace std;
+
+
+int n, nums[220], ans, save[220];
+int max(int a, int b) {
+	return a > b ? a : b;
+}
+
+int main() {
+	cin >> n;
+	for (int i = 1; i <= n; i++)
+		cin >> nums[i];
+
+	for (int i = 1; i <= n; i++) {
+		save[i] = 1;
+
+		for (int j = 1; j < n; j++) {
+			if (nums[j] < nums[i])
+				save[i] = max(save[i], save[j] + 1);
+		}
+		ans = max(ans, save[i]);
+	}
+
+	cout << n - ans;
+}
+
This post is licensed under CC BY 4.0 by the author.

운영체제 개요

React) JS 다운로드 시간 동안의 로딩 화면

Comments powered by Disqus.

diff --git a/posts/2887/index.html b/posts/2887/index.html new file mode 100644 index 000000000..018849d88 --- /dev/null +++ b/posts/2887/index.html @@ -0,0 +1,339 @@ + 2887 planet tunnel | 디피의 개발일지
Posts 2887 planet tunnel
Post
Cancel

2887 planet tunnel

2887 planet tunnel

알고리즘(정렬, MST)

1
+2
+3
+4
+5
+6
+
1. 모든 행성들의 x좌표, y좌표, z좌표를 꺼내 따로 저장하고, 오름차순으로 정렬
+2. 정렬한 것에서 인접한 것끼리 비용을 계산, (인접한 점 두개 + 비용)을 구조체로 n-1개의 원소를 가지는 배열 3개를 선언하고 집어넣음
+3. 그 구조체배열 3개는 비용을 기준으로 오름차순으로 정렬
+4. 구조체 배열 3개를 가리키는 3개의 인덱스 변수 xi, yi, zi를 선언
+5. xi, yi, zi 중 최소 비용을 unionFind 알고리즘으로 사이클이 형성되는지 확인하면서 집어넣음. 넣으면 edge 1증가, 넣어도 안넣어도 xi, yi, zi는 최소일시 1 증가시킨다
+6. edge가 n-1개가 되면 정지 -> 총 value 출력
+

푼과정

1
+2
+3
+4
+5
+6
+7
+8
+9
+
1. MST로 풀어야하는 문제인건 바로 암. 근데 간선이 주어지지 않음 -> 간선을 직접 만들어야함
+2. 모든 행성간의 비용을 구하는건 너무 오래걸리고, 메모리 소모가 심함
+	-> 가장 가까운 것이 될 만한 것들끼리만 비교하여 간선을 만들자
+3. 모든 행성을 정렬하여 인접한 것끼리 할까?
+	-> 어떤 기준으로 정렬할 것인지 문제고, 또 정렬하더라도 인접하지만 거리는 제일 멀 수 있음 -> 비용계산공식이 좌표별로 독립적이기에
+4. 그럼 좌표별로 모든 점들을 정렬하여 인접한 것끼리 비용을 계산하고, 또 그 비용을 정렬하면 되겠구나
+	-> 그럼 비용, 두 인접한 좌표를 가지고 있는 구조체를 선언하는게 좋겠구나
+5. 이렇게 간선을 얻고, 하나씩 집어넣는데 총 edge는 N-1개니까 넣을 때마다 edge를 1개씩 증가시켜 n-1이 되면 정지시키면 되겠구나
+6. 통과
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+
#include <iostream>
+#include <vector>
+#include <cstring>
+#include <algorithm>
+#define MAX_PLANET 100000
+
+using namespace std;
+
+typedef struct Planet {
+	int x;
+	int y;
+	int z;
+} Planet;
+typedef struct Distance {
+	int first;
+	int second;
+	int distance;
+	bool operator < (Distance d) {
+		return distance < d.distance;
+	}
+} Distance;
+
+Planet planet[MAX_PLANET];
+Distance xyz[3][MAX_PLANET];
+int numOfPlanet, checkPlanet[MAX_PLANET], tempXYZ[MAX_PLANET];
+vector<vector<int>> set;
+
+int abs(int a) {
+	return a < 0 ? (-1) * a : a;
+}
+long long getMinValue();
+bool putSet(int a, int b);
+bool compX(int a, int b) {
+	return planet[a].x < planet[b].x;
+}
+bool compY(int a, int b) {
+	return planet[a].y < planet[b].y;
+}
+bool compZ(int a, int b) {
+	return planet[a].z < planet[b].z;
+}
+void initAndSortXYZ();
+
+int main() {
+	cin >> numOfPlanet;
+	for (int i = 0; i < numOfPlanet; i++)
+		cin >> planet[i].x >> planet[i].y >> planet[i].z;
+	for (int i = 0; i < MAX_PLANET; i++)
+		checkPlanet[i] = -1;
+
+	cout << getMinValue();
+}
+
+long long getMinValue() {
+	long long value = 0;
+	int Xi = 0, Yi = 0, Zi = 0, edge = 0;
+	initAndSortXYZ();
+	while (edge < numOfPlanet - 1) {
+		int xd = xyz[0][Xi].distance, yd = xyz[1][Yi].distance, zd = xyz[2][Zi].distance;
+		if (xd <= yd && xd <= zd) {
+			if (putSet(xyz[0][Xi].first, xyz[0][Xi].second)) {
+				value += xd;
+				edge++;
+			}
+			Xi++;
+		}
+		else if (yd <= xd && yd <= zd) {
+			if (putSet(xyz[1][Yi].first, xyz[1][Yi].second)) {
+				value += yd;
+				edge++;
+			}
+			Yi++;
+		}
+		else {
+			if (putSet(xyz[2][Zi].first, xyz[2][Zi].second)) {
+				value += zd;
+				edge++;
+			}
+			Zi++;
+		}
+	}
+
+	return value;
+}
+
+void initAndSortXYZ() {
+	cout << endl;
+	for (int k = 0; k < 3; k++) {
+		for (int i = 0; i < numOfPlanet; i++)
+			tempXYZ[i] = i;
+
+		if(k == 0) sort(tempXYZ, tempXYZ + numOfPlanet, compX);
+		else if(k==1)  sort(tempXYZ, tempXYZ + numOfPlanet, compY);
+		else if(k==2)  sort(tempXYZ, tempXYZ + numOfPlanet, compZ);
+
+		for (int i = 0; i < numOfPlanet - 1; i++) {
+			xyz[k][i].first = tempXYZ[i];
+			xyz[k][i].second = tempXYZ[i + 1];
+			if (k == 0)
+				xyz[k][i].distance = abs(planet[tempXYZ[i]].x - planet[tempXYZ[i + 1]].x);
+			else if (k == 1)
+				xyz[k][i].distance = abs(planet[tempXYZ[i]].y - planet[tempXYZ[i + 1]].y);
+			else if (k == 2)
+				xyz[k][i].distance = abs(planet[tempXYZ[i]].z - planet[tempXYZ[i + 1]].z);
+		}
+		for (int i = 0; i < numOfPlanet - 1; i++)
+			cout << "( " << xyz[k][i].first << ", " << xyz[k][i].second << ", " << xyz[k][i].distance << ") ";
+		sort(xyz[k], xyz[k] + numOfPlanet -1);
+		for (int i = 0; i < numOfPlanet - 1; i++)
+			cout << "( " << xyz[k][i].first << ", " << xyz[k][i].second << ", " << xyz[k][i].distance << ") ";
+		cout << endl;
+	}
+
+	cout << endl;
+
+}
+
+bool putSet(int a, int b) {
+	if (checkPlanet[a] == -1 && checkPlanet[b] == -1) {
+		vector<int> temp;
+		temp.push_back(a);
+		temp.push_back(b);
+		set.push_back(temp);
+		checkPlanet[a] = set.size() - 1;
+		checkPlanet[b] = set.size() - 1;
+	}
+	else if (checkPlanet[a] == -1) {
+		checkPlanet[a] = checkPlanet[b];
+		set[checkPlanet[b]].push_back(a);
+	}
+	else if (checkPlanet[b] == -1) {
+		checkPlanet[b] = checkPlanet[a];
+		set[checkPlanet[a]].push_back(b);
+	}
+	else if (checkPlanet[a] < checkPlanet[b]) {
+		int temp = checkPlanet[b];
+		for (int i = 0; i < set[temp].size(); i++) {
+			checkPlanet[set[temp][i]] = checkPlanet[a];
+			set[checkPlanet[a]].push_back(set[temp][i]);
+		}
+		set[temp].clear();
+	}
+	else if (checkPlanet[a] > checkPlanet[b]) {
+		int temp = checkPlanet[a];
+		for (int i = 0; i < set[temp].size(); i++) {
+			checkPlanet[set[temp][i]] = checkPlanet[b];
+			set[checkPlanet[b]].push_back(set[temp][i]);
+		}
+		set[temp].clear();
+	}
+	else
+		return false;
+	return true;
+}
+
This post is licensed under CC BY 4.0 by the author.

2568 전기줄

2931 가스관

Comments powered by Disqus.

diff --git a/posts/2931/index.html b/posts/2931/index.html new file mode 100644 index 000000000..f1167a67e --- /dev/null +++ b/posts/2931/index.html @@ -0,0 +1,289 @@ + 2931 가스관 | 디피의 개발일지
Posts 2931 가스관
Post
Cancel

2931 가스관

2931 가스관

알고리즘(구현)

1
+2
+3
+4
+5
+
1. M에서 시작해 갈 수 있는 방향으로 가다가 빈칸을 마주하면 스탑.
+  만약 빈칸이 없으면 아예 처음부터 연결이 안된 것. 이때는 Z 부터 시작해 빈칸을 찾는다.
+2. 빈칸을 찾으면 빈칸 주변을 검색. 인접한 칸에 바로 연결되는 파이프가 있으면 그 칸을 true로 표시. 표시된 것에 따라 빈칸에 넣을 수 있는 가스관을 배정.
+	ex) 빈칸 | 이면 연결이 안되므로, 오른쪽칸은 false
+	 빈칸 - 이면 연결이 되므로 오른쪽 칸은 true
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+
#include <bits/stdc++.h>
+
+using namespace std;
+
+struct Location {
+	int row;
+	int col;
+	int way;	// 1~4 위 오 아 왼
+};
+
+char eumap[25][25], answer;
+int R, C;
+Location M, Z, blank;
+list<Location> q;
+
+bool findBlank(int row, int col);		//찾으면 true
+bool goNext(Location cur);			// 빈칸 발견시 false. 나머지 true
+Location nextCell(Location c);
+void fillBlank();
+
+int main() {
+	cin >> R >> C;
+	for (int i = 0; i < R; i++)
+		for (int j = 0; j < C; j++) {
+			cin >> eumap[i][j];
+			if (eumap[i][j] == 'M')
+				M = { i, j, 0 };
+			else if (eumap[i][j] == 'Z')
+				Z = { i, j, 0 };
+		}
+
+	if (!findBlank(M.row, M.col))
+		findBlank(Z.row, Z.col);
+
+	fillBlank();
+
+	cout << blank.row + 1 << " " << blank.col + 1 << " " << answer << endl;
+}
+
+bool findBlank(int r, int c) {
+	if (r > 0 && eumap[r - 1][c] != '.')
+		q.push_back({ r, c, 1 });
+	if (c < C - 1 && eumap[r][c + 1] != '.')
+		q.push_back({ r, c, 2 });
+	if (r < R -1 && eumap[r + 1][c] != '.')
+		q.push_back({ r, c, 3 });
+	if (c > 0 && eumap[r][c - 1] != '.')
+		q.push_back({ r, c, 4 });
+
+	while (!q.empty()) {
+		if (goNext(q.front()))
+			q.pop_front();
+		else {
+			q.clear();
+			return true;
+		}
+	}
+	q.clear();
+	return false;
+}
+
+bool goNext(Location cur) {
+	cur = nextCell(cur);
+	int r = cur.row, c = cur.col, w = cur.way;
+
+	char t = eumap[r][c];
+
+	if (t == '.') {
+		blank = { r, c, w };
+		return false;
+	}
+
+	if (w == 1 || w == 3) {
+		if (t == '|' || t == '+')
+			q.push_back({ r , c, w });
+		if (t == '1' || t == '2' || t == '+')
+			q.push_back({ r , c, 2 });
+		if (t == '4' || t == '3' || t == '+')
+			q.push_back({ r , c, 4 });
+	}
+	else {
+		if (t == '-' || t == '+')
+			q.push_back({ r, c , w });
+		if (t == '3' || t == '2' || t == '+')
+			q.push_back({ r, c , 1 });
+		if (t == '4' || t == '1' || t == '+')
+			q.push_back({ r, c , 3 });
+	}
+	return true;
+}
+
+Location nextCell(Location cur) {
+	int r = cur.row, c = cur.col, w = cur.way;
+
+	if (w == 1 && r > 0)
+		return { r - 1, c, w };
+	if (w == 2 && c < C - 1)
+		return { r, c + 1, w };
+	if (w == 3 && r < R - 1)
+		return { r + 1, c, w };
+	if (w == 4 && c > 0)
+		return { r, c - 1,w};
+}
+
+void fillBlank() {
+	int r = blank.row, c = blank.col, w = blank.way;
+	bool check[4] = { 0,0,0,0 };
+	if (r > 0) {
+		char t = eumap[r - 1][c];
+		check[0] = t != '|' ? t != '1' ? t != '4' ? t != '+' ? false : true : true : true : true;
+	}
+	if (c < C - 1) {
+		char t = eumap[r][c + 1];
+		check[1] = t != '-' ? t != '4' ? t != '3' ? t != '+' ? false : true : true : true : true;
+	}
+	if (r < R - 1) {
+		char t = eumap[r + 1][c];
+		check[2] = t != '|' ? t != '2' ? t != '3' ? t != '+' ? false : true : true : true : true;
+	}
+	if (c > 0) {
+		char t = eumap[r][c-1];
+		check[3] = t != '-' ? t != '2' ? t != '1' ? t != '+' ? false : true : true : true : true;
+	}
+
+	if (check[0] && check[1] && check[2] && check[3])
+		answer = '+';
+	else if (check[0] && check[2])
+		answer = '|';
+	else if (check[1] && check[3])
+		answer = '-';
+	else if (check[1] && check[2])
+		answer = '1';
+	else if (check[0] && check[1])
+		answer = '2';
+	else if (check[0] && check[3])
+		answer = '3';
+	else if (check[2] && check[3])
+		answer = '4';
+}
+
This post is licensed under CC BY 4.0 by the author.

2887 planet tunnel

2933 미네랄

Comments powered by Disqus.

diff --git a/posts/2933/index.html b/posts/2933/index.html new file mode 100644 index 000000000..c708c7964 --- /dev/null +++ b/posts/2933/index.html @@ -0,0 +1,349 @@ + 2933 미네랄 | 디피의 개발일지
Posts 2933 미네랄
Post
Cancel

2933 미네랄

2933 미네랄

알고리즘(구현, 시뮬레이션)

1
+2
+3
+4
+5
+6
+
1. 들어온 위치의 미네랄을 지움
+2. 지운 위치에서 상하좌우에 인접한 칸들을 root로 BFS 시행 -> 바닥에 닿는 칸이 있으면 공중에 뜬 클러스터가 아님
+3. 공중에 뜬 클러스터이면 BFS 시 넣은 칸들을 열순, 열이 같으면 행이 큰 순으로 정렬
+4. 각 열별로 가장 밑에 있는 칸만 검사하여 밑 클러스트 또는 바닥과 떨어진 최소 거리를 구함
+5. 정렬한 순으로 최소거리만큼 밑으로 내림
+6. 끝날때까지 1~5 반복
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+
#include <iostream>
+#include <algorithm>
+#define MAX_WIDTH 100
+
+using namespace std;
+
+typedef struct Location {
+	int row;
+	int col;
+	bool operator < (Location l) {
+		if (col != l.col)
+			return col < l.col;
+		else
+			return row > l.row;
+	}
+} Location;
+
+int R, C;
+bool map[MAX_WIDTH][MAX_WIDTH], visit[MAX_WIDTH][MAX_WIDTH];
+Location q[MAX_WIDTH * MAX_WIDTH];
+
+void throwStick(int height, bool isLeft);
+void generateBFS(int r, int c);
+void BFS(int r, int c);
+void initVisit(int length);
+void pushDown(int length);
+
+int main() {
+	cin >> R >> C;
+	for (int i = 0; i < R; i++)
+		for (int j = 0; j < C; j++) {
+			char temp;
+			cin >> temp;
+			if (temp == '.')
+				map[i][j] = false;
+			else
+				map[i][j] = true;
+		}
+
+	int n, temp;
+	cin >> n;
+	for (int i = 0; i < n; i++) {
+		cin >> temp;
+		throwStick(R - temp, i % 2 == 0 ? true : false);
+	}
+
+	for (int i = 0; i < R; i++) {
+		for (int j = 0; j < C; j++) {
+			char c = map[i][j] ? 'x' : '.';
+			cout << c;
+		}
+		cout << endl;
+	}
+}
+
+void throwStick(int height, bool isLeft) {
+	if (isLeft) {
+		int i = 0;
+		for (; i < C; i++)
+			if (map[height][i])
+				break;
+		if (i == C)
+			return;
+
+		map[height][i] = false;
+		generateBFS(height, i);
+	}
+	else {
+		int i = C-1;
+		for (; i >=0; i--)
+			if (map[height][i])
+				break;
+		if (i == -1)
+			return;
+
+		map[height][i] = false;
+		generateBFS(height, i);
+	}
+
+	cout << endl;
+	for (int i = 0; i < R; i++) {
+		for (int j = 0; j < C; j++) {
+			char c = map[i][j] ? 'x' : '.';
+			cout << c;
+		}
+		cout << endl;
+	}
+	cout << endl;
+}
+
+void generateBFS(int r, int c) {
+	if (r > 0 && map[r - 1][c])
+		BFS(r - 1, c);
+	if (r < R - 1 && map[r + 1][c])
+		BFS(r + 1, c);
+	if (c > 0 && map[r][c - 1])
+		BFS(r, c - 1);
+	if (c < C - 1 && map[r][c + 1])
+		BFS(r, c + 1);
+}
+
+void BFS(int r, int c) {
+	int length = 0, index = 0;
+	visit[r][c] = true;
+	q[length++] = { r,c };
+
+	while (index < length) {
+		Location temp = q[index++];
+		int row = temp.row, col = temp.col;
+
+		if (row == R - 1) {
+			initVisit(length);
+			return;
+		}
+
+		if (row > 0 && !visit[row - 1][col] && map[row - 1][col]) {
+			q[length++] = { row - 1, col };
+			visit[row - 1][col] = true;
+		}
+		if (row < R - 1 && !visit[row + 1][col] && map[row + 1][col]) {
+			q[length++] = { row + 1, col };
+			visit[row + 1][col] = true;
+		}
+		if (col > 0 && !visit[row][col - 1] && map[row][col - 1]) {
+			q[length++] = { row, col - 1 };
+			visit[row][col - 1] = true;
+		}
+		if (col < C - 1 && !visit[row][col + 1] && map[row][col + 1]) {
+			q[length++] = { row, col + 1 };
+			visit[row][col + 1] = true;
+		}
+	}
+	pushDown(length);
+	initVisit(length);
+}
+
+void initVisit(int length) {
+	for (int i = 0; i < length; i++)
+		visit[q[i].row][q[i].col] = false;
+}
+
+void pushDown(int length) {
+	sort(q, q + length);
+	//밑이 빈 것들만 뽑고, 그중 밑하고 떨어진 거리가 가장 짧은 것을 따로 길이만 저장
+	//그 길이만큼 하강
+	int minHeight = 1000000, cur = -1;
+	for (int i = 0; i < length; i++) {
+		if (cur != q[i].col) {
+			cur = q[i].col;
+
+			int row = q[i].row + 1;
+			//미네랄을 만나거나 바닥이면 스탑
+			int j = row;
+			for (; j < R; j++)
+				if (map[j][cur])
+					break;
+			minHeight = minHeight < j - row ? minHeight : j - row;
+		}
+	}
+	cout << minHeight << endl;
+
+	//해당 클러스트를 minHeight만큼 내리기
+	//****임시 배열을 만들어서 표시
+	for (int i = 0; i < length; i++) {
+		map[q[i].row][q[i].col] = false;
+		map[q[i].row + minHeight][q[i].col] = true;
+	}
+}
+
This post is licensed under CC BY 4.0 by the author.

2931 가스관

3190 Samsung sw test

Comments powered by Disqus.

diff --git a/posts/3108-logo/index.html b/posts/3108-logo/index.html new file mode 100644 index 000000000..2a3dd531e --- /dev/null +++ b/posts/3108-logo/index.html @@ -0,0 +1,171 @@ + 3108 logo | 디피의 개발일지
Posts 3108 logo
Post
Cancel

3108 logo

알고리즘

  • 겹치는 사각형을 각기 다른 집합으로 분류 + (0,0) 점이 속하는 집합이 있는지 확인
  • 앞에서부터 겹치는 사각형을 확인하고, 겹치면, 큐의 맨 앞에 넣어서 DFS 방식으로 집합을 분류
  • 매번 모든 사각형을 검사하는데, 이미 분류된 사각형은 다시 검사하지 않아도 됨.
  • 이때 (0,0,0,0)인 사각형(사실은 점)을 1개 더 저장하여, 검사할때 이 점도 검사하도록 하여, 클래스가 분류되면, 원점이 속하는 집합이 있는거고, 분류되지 않으면 없는 것이니, 이것으로 최종 결과를 계산함.

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+
#include <iostream>
+#include <cstring>
+#include <list>
+using namespace std;
+
+int N, point[1001][4], classes[1001], classCount;
+bool check[1000];
+list<int> q;
+
+void classify();
+void countClass();
+
+int main() {
+	memset(classes, -1, sizeof(classes));
+
+	cin >> N;
+	for (int i = 0; i < N; i++)
+		cin >> point[i][0] >> point[i][1] >> point[i][2] >> point[i][3];
+
+	classify();
+	countClass();
+
+	for (int i = 0; i < N + 1; i++) {
+		cout << classes[i] << " ";
+	}
+	cout << endl;
+
+	if (classes[N] == -1)
+		cout << classCount;
+	else
+		cout << classCount - 1;
+}
+
+void classify() {
+	int nextClass = 0;
+
+	for (int i = 0; i < N; i++)
+		q.push_back(i);
+
+	while(!q.empty()) {
+		int i = q.front();
+		q.pop_front();
+
+		if (classes[i] == -1)
+			classes[i] = nextClass++;
+
+		int x1 = point[i][0], x2 = point[i][2], y1 = point[i][1], y2 = point[i][3];
+		int iline[4][4] = { {x1, y2, x2, y2}, {x2, y1, x2, y2},{x1, y1, x2, y1},{x1, y1, x1, y2} }; // 위 오 아 왼
+
+		for (int j = 0; j < N + 1; j++) {
+			if (classes[j] != -1)
+				continue;
+			bool same = false;
+
+			x1 = point[j][0], x2 = point[j][2], y1 = point[j][1], y2 = point[j][3];
+			int jline[4][4] = { {x1, y2, x2, y2}, {x2, y1, x2, y2},{x1, y1, x2, y1},{x1, y1, x1, y2} };
+
+			for (int k = 0; k < 4; k++) {
+				for (int p = 0; p < 4; p++) {
+					if (k % 2 == 0 && p % 2 == 0 && iline[k][1] == jline[p][1] && !(iline[k][0] > jline[p][2] || iline[k][2] < jline[p][0]))
+						same = true;
+					else if (k % 2 == 1 && p % 2 == 1 && iline[k][0] == jline[p][0] && (!(iline[k][1] > jline[p][3] || iline[k][3] < jline[p][1])))
+						same = true;
+					else if (k % 2 == 0 && !(iline[k][0] > jline[p][0] || iline[k][2] < jline[p][0] || iline[k][1] > jline[p][3] || iline[k][1] < jline[p][1]))
+						same = true;
+					else if (!(iline[k][0] > jline[p][2] || iline[k][0] < jline[p][0] || iline[k][1] > jline[p][3] || iline[k][3] < jline[p][1]))
+						same = true;
+				}
+			}
+			if (same) {
+				classes[j] = classes[i];
+				q.push_front(j);
+			}
+		}
+	}
+}
+
+void countClass() {
+	for (int i = 0; i < N; i++) {
+		if (!check[classes[i]]) {
+			check[classes[i]] = true;
+			classCount++;
+		}
+	}
+}
+
This post is licensed under CC BY 4.0 by the author.

1039 교환

Graph ql back

Comments powered by Disqus.

diff --git a/posts/3190/index.html b/posts/3190/index.html new file mode 100644 index 000000000..c318ba0ce --- /dev/null +++ b/posts/3190/index.html @@ -0,0 +1,205 @@ + 3190 Samsung sw test | 디피의 개발일지
Posts 3190 Samsung sw test
Post
Cancel

3190 Samsung sw test

3190 Samsung sw test

알고리즘

1
+2
+3
+4
+5
+
1. 그냥 대가리 옮기고 꼬리 잡히는지, 칸 밖인지 확인하고, 또 사과있는지 확인하고, 또 시간이 바꿀때가 됐는지 확인
+2. 뱀 몸은 vector로 저장 / 사과 위치는 2차원 bool 배열로 저장하여 메모리랑 참조시간 아낌. /
+  시간은 Direction 구조체에 저장하고, directionIndex를 선언하여 directionIndex에 있는 시간에 맞으면 방향 바꾸고 index++.
+
+3. 구현문제이므로 내가 생각하는 게임이랑 실제 문제 조건은 다를 수 있다. 따라서 예시를 통해 확인하라.
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+
#include <iostream>
+#include <vector>
+#include <cstring>
+#define MAX_APPLE 100
+#define MAX_DIRECTION 100
+
+using namespace std;
+
+typedef struct Location {
+	int row;
+	int col;
+} Location;
+typedef struct Direction {
+	int second;
+	char direction;
+} Direction;
+
+int width;
+int numOfApple;
+bool appleMap[MAX_APPLE][MAX_APPLE];
+int numOfChange;
+Direction direction[MAX_DIRECTION];
+
+void getProperties();
+int getSecond();
+
+int main() {
+	getProperties();
+	cout << getSecond();
+}
+
+void getProperties() {
+	cin >> width;
+	cin >> numOfApple;
+	memset(appleMap, 0, sizeof(bool) * MAX_APPLE * MAX_APPLE);
+	for (int i = 0; i < numOfApple; i++) {
+		int row, col;
+		cin >> row >> col;
+		appleMap[row - 1][col - 1] = true;
+	}
+
+	cin >> numOfChange;
+	for (int i = 0; i < numOfChange; i++) {
+		cin >> direction[i].second >> direction[i].direction;
+	}
+}
+
+int getSecond() {
+	int second = 0;
+	vector<Location> snake;
+	snake.push_back({ 0, 0 });
+	int currentDirection = 2;
+	int directionIndex = 0;
+	bool isEnd = false;
+
+	//현재 헤드 제일 뒤에, 꼬리는 제일 앞에
+	while (!isEnd) {
+		second++;
+		Location s = snake.back();
+		if (currentDirection == 1)
+			snake.push_back({ s.row - 1, s.col });
+		else if (currentDirection == 2)
+			snake.push_back({ s.row, s.col + 1 });
+		else if (currentDirection == 3)
+			snake.push_back({ s.row + 1, s.col });
+		else if (currentDirection == 4)
+			snake.push_back({ s.row, s.col - 1 });
+
+		Location t = snake.back();
+
+		if (t.row < 0 || t.row >= width || t.col < 0 || t.col >= width)
+			isEnd = true;
+		for (int i = 0; i < snake.size(); i++) {
+			if (t.row == snake[i].row && t.col == snake[i].col && i != snake.size() - 1)
+				isEnd = true;
+		}
+
+		if (appleMap[t.row][t.col])
+			appleMap[t.row][t.col] = false;
+		else
+			snake.erase(snake.begin());
+
+		if (direction[directionIndex].second == second) {
+			if (currentDirection == 1)
+				currentDirection = direction[directionIndex].direction == 'D' ? 2 : 4;
+			else if (currentDirection == 2)
+				currentDirection = direction[directionIndex].direction == 'D' ? 3 : 1;
+			else if (currentDirection == 3)
+				currentDirection = direction[directionIndex].direction == 'D' ? 4 : 2;
+			else
+				currentDirection = direction[directionIndex].direction == 'D' ? 1 : 3;
+			directionIndex++;
+		}
+	}
+
+	return second;
+}
+
This post is licensed under CC BY 4.0 by the author.

2933 미네랄

3197 백조의 호수

Comments powered by Disqus.

diff --git a/posts/3197/index.html b/posts/3197/index.html new file mode 100644 index 000000000..c24fe1057 --- /dev/null +++ b/posts/3197/index.html @@ -0,0 +1,469 @@ + 3197 백조의 호수 | 디피의 개발일지
Posts 3197 백조의 호수
Post
Cancel

3197 백조의 호수

3197 백조의 호수

알고리즘(구현)

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+
처음 방법
+	1. 한 백조에서 BFS 실시하여 다른 백조를 만날 수 있는지 검사
+	2. 전체 맵을 검사해서 녹일 수 있는 걸 녹임
+	3. day 추가하고 다시 1부터
+	-> 시간이 너무 걸림. 특히 맵 초기화하는 부분이 많이 걸림
+두번째 방법
+	1. 1번은 유지
+	2. 녹을 수 있는 얼음만 저장한 q에서 얼음을 하나씩 꺼내서 녹이고, 주변에 검사되지않은 얼음이 있으면 따로 저장
+		-> q가 끝나면 따로 저장한 얼음을 q에 복사
+	3. day추가하고 다시 1부터
+	-> 또 시간초과
+세번째 방법
+	1. 백조가 갈수있는 칸을 전부 각각 L, l 로 바꿈
+	2. 얼음과 L, l의 경계선을 q에 저장
+	3. 2방법으로 다 녹이고 나서, q에 있는 칸을 하나씩 꺼내고 주변을 검사하여 또 L, l 로 바꾼다
+	4. 바꾸는데 다른 문자를 발견하면 스탑 후 출력
+	-> 코드가 너무 복잡하여 헷갈림. 어딘가 잘못됨
+네번째 방법
+	1. 백조가 갈 수 잇는 칸을 전부 각각 L,l 로 바꿈
+	2. 얼음을 한칸 녹일 때마다 주변을 검사하여 적절한 문자로 치환
+		주변에 (L과 l) 두개 있을 때
+			-> 만난 것 -> 출력 후 종료
+		주변에 L 또는 l 만 있을 때
+			-> 만나지 않음. 그 얼음칸을 중심으로 BFS를 실시하여 갈 수 있는 모든 칸을 L 또는 l로 바꿈
+		주변에 L, l 둘다 없을 때
+			-> 그냥 . 으로 치환
+	-> 통과
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+
#include <iostream>
+#include <vector>
+#include <list>
+#define MAX_WIDTH 1500
+
+using namespace std;
+
+typedef struct Location {
+	int row;
+	int col;
+} Location;
+
+int row, col, todayLength;
+Location bird1, bird2;
+Location meltToday[MAX_WIDTH*MAX_WIDTH];
+char map[MAX_WIDTH][MAX_WIDTH];
+bool visit[MAX_WIDTH][MAX_WIDTH], meltVisit[MAX_WIDTH][MAX_WIDTH];
+list<Location> q;
+
+bool init();
+void initBird(Location bird, char c);
+bool meltIce();
+bool checkBird(int r, int c);
+
+int main() {
+	ios::sync_with_stdio(0);
+	cin.tie(0);
+	cout.tie(0);
+	cin >> row >> col;
+
+	bool check = false;
+	for (int i = 0; i < row; i++) {
+		for (int j = 0; j < col; j++) {
+			cin >> map[i][j];
+			if (map[i][j] == 'L') {
+				if (!check) {
+					bird1.row = i;
+					bird1.col = j;
+					check = true;
+				}
+				else {
+					bird2.row = i;
+					bird2.col = j;
+					map[i][j] = 'l';
+				}
+			}
+		}
+	}
+
+	if (init()) {
+		cout << 0;
+		return 0;
+	}
+
+	int day = 1;
+	while (true) {
+		cout << endl;
+		for (int i = 0; i < row; i++) {
+			for (int j = 0; j < col; j++)
+				cout << map[i][j];
+			cout << endl;
+		}
+		cout << endl;
+		if (meltIce()) {
+			cout << day;
+			break;
+		}
+		day++;
+	}
+}
+
+bool init() {
+	for (int i = 0; i < row; i++) {
+		for (int j = 0; j < col; j++) {
+			if (map[i][j] == 'X') {
+				if (i > 0 && map[i - 1][j] != 'X') {
+					meltToday[todayLength++] = { i, j };
+					meltVisit[i][j] = true;
+				}
+				else if (j < col - 1 && map[i][j + 1] != 'X') {
+					meltToday[todayLength++] = { i, j };
+					meltVisit[i][j] = true;
+				}
+				else if (i < row - 1 && map[i + 1][j] != 'X') {
+					meltToday[todayLength++] = { i, j };
+					meltVisit[i][j] = true;
+				}
+				else if (j > 0 && map[i][j - 1] != 'X') {
+					meltToday[todayLength++] = { i, j };
+					meltVisit[i][j] = true;
+				}
+			}
+		}
+	}
+
+	initBird(bird1, 'L');
+	if (map[bird2.row][bird2.col] == 'L')
+		return true;
+	initBird(bird2, 'l');
+	return false;
+}
+
+void initBird(Location bird, char c) {
+	q.push_back({ bird.row, bird.col });
+	visit[bird.row][bird.col] = true;
+
+	while (!q.empty()) {
+		Location temp = q.front();
+		q.pop_front();
+		map[temp.row][temp.col] = c;
+
+		if (temp.row > 0 && !visit[temp.row - 1][temp.col] && map[temp.row - 1][temp.col] != 'X') {
+			q.push_back({ temp.row - 1, temp.col });
+			visit[temp.row - 1][temp.col] = true;
+		}
+		if (temp.row < row - 1 && !visit[temp.row + 1][temp.col] && map[temp.row + 1][temp.col] != 'X') {
+			q.push_back({ temp.row + 1, temp.col });
+			visit[temp.row + 1][temp.col] = true;
+		}
+		if (temp.col > 0 && !visit[temp.row][temp.col - 1] && map[temp.row][temp.col - 1] != 'X') {
+			q.push_back({ temp.row, temp.col -1 });
+			visit[temp.row][temp.col - 1] = true;
+		}
+		if (temp.col < col - 1 && !visit[temp.row][temp.col + 1] && map[temp.row][temp.col + 1] != 'X') {
+			q.push_back({ temp.row, temp.col + 1});
+			visit[temp.row][temp.col + 1] = true;
+		}
+	}
+}
+
+bool meltIce() {
+	int index = 0;
+	vector<Location> q;
+
+	while (index < todayLength) {
+		if (checkBird(meltToday[index].row, meltToday[index].col))
+			return true;
+
+		if (meltToday[index].row > 0 && map[meltToday[index].row - 1][meltToday[index].col] == 'X' && !meltVisit[meltToday[index].row-1][meltToday[index].col] ) {
+			q.push_back({ meltToday[index].row - 1, meltToday[index].col });
+			meltVisit[meltToday[index].row - 1][meltToday[index].col] = true;
+		}
+
+		if (meltToday[index].col < col - 1 && map[meltToday[index].row][meltToday[index].col + 1] == 'X' && !meltVisit[meltToday[index].row][meltToday[index].col + 1]) {
+			q.push_back({ meltToday[index].row, meltToday[index].col + 1 });
+			meltVisit[meltToday[index].row][meltToday[index].col + 1] = true;
+		}
+
+		if (meltToday[index].row < row - 1 && map[meltToday[index].row + 1][meltToday[index].col] == 'X' && !meltVisit[meltToday[index].row + 1][meltToday[index].col]) {
+			q.push_back({ meltToday[index].row + 1, meltToday[index].col });
+			meltVisit[meltToday[index].row + 1][meltToday[index].col] = true;
+		}
+
+		if (meltToday[index].col > 0 && map[meltToday[index].row][meltToday[index].col - 1] == 'X' && !meltVisit[meltToday[index].row][meltToday[index].col - 1]) {
+			q.push_back({ meltToday[index].row, meltToday[index].col - 1 });
+			meltVisit[meltToday[index].row][meltToday[index].col - 1] = true;
+		}
+
+		index++;
+	}
+	todayLength = 0;
+
+	//넣기
+	for (int i = 0; i < q.size(); i++)
+		meltToday[todayLength++] = q[i];
+
+	return false;
+}
+
+bool checkBird(int r, int c) {
+	bool checkL = false, checkl = false;
+	if (r > 0 && map[r - 1][c] == 'L')
+		checkL = true;
+	else if (r < row - 1 && map[r + 1][c] == 'L')
+		checkL = true;
+	else if (c > 0 && map[r][c - 1] == 'L')
+		checkL = true;
+	else if (c < col - 1 && map[r][c + 1] == 'L')
+		checkL = true;
+
+	if (r > 0 && map[r - 1][c] == 'l')
+		checkl = true;
+	else if (r < row - 1 && map[r + 1][c] == 'l')
+		checkl = true;
+	else if (c > 0 && map[r][c - 1] == 'l')
+		checkl = true;
+	else if (c < col - 1 && map[r][c + 1] == 'l')
+		checkl = true;
+
+	if (checkL && checkl)
+		return true;
+	else if (checkL) {
+		map[r][c] = 'L';
+		initBird({ r,c }, 'L');
+		if (map[bird2.row][bird2.col] == 'L')
+			return true;
+	}
+	else if (checkl) {
+		map[r][c] = 'l';
+		initBird({ r,c }, 'l');
+		if (map[bird1.row][bird1.col] == 'l')
+			return true;
+	}
+	else
+		map[r][c] = '.';
+	return false;
+}
+
This post is licensed under CC BY 4.0 by the author.

3190 Samsung sw test

4386 make constellation

Comments powered by Disqus.

diff --git a/posts/3568/index.html b/posts/3568/index.html new file mode 100644 index 000000000..7d922ddbd --- /dev/null +++ b/posts/3568/index.html @@ -0,0 +1,99 @@ + 3568 iSharp | 디피의 개발일지
Posts 3568 iSharp
Post
Cancel

3568 iSharp

3568 iSharp

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+
#include <iostream>
+#include <string>
+
+using namespace std;
+
+string def, append;
+char input[130];
+
+void print();
+
+int main() {
+	cin.getline(input, 130);
+
+	int i = 0;
+	while (input[i] != ' ') {
+		def.push_back(input[i]);
+		i++;
+	}
+
+	for (; input[i] != ';'; i++) {
+		if (input[i] != ',')
+			append.push_back(input[i]);
+		else
+			print();
+	}
+	print();
+}
+
+void print() {
+	cout << def;
+	int j = append.size() - 1;
+	for (; j >= 0; j--) {
+		if (append[j] == '&' || append[j] == '*')
+			cout << append[j];
+		else if (append[j] == ']') {
+			cout << "[]";
+			j--;
+		}
+		else
+			break;
+	}
+	cout << " ";
+	for (int k = 1; k <= j; k++) {
+		if ((append[k] >= 'a' && append[k] <= 'z') || (append[k] >= 'A' && append[k] <= 'Z'))
+			cout << append[k];
+	}
+	cout << ";" << endl;
+	append.clear();
+}
+
This post is licensed under CC BY 4.0 by the author.

2623 MusicPropram

5373 Samsung sw test

Comments powered by Disqus.

diff --git a/posts/4386/index.html b/posts/4386/index.html new file mode 100644 index 000000000..6034ffa2a --- /dev/null +++ b/posts/4386/index.html @@ -0,0 +1,197 @@ + 4386 make constellation | 디피의 개발일지
Posts 4386 make constellation
Post
Cancel

4386 make constellation

4386 make constellation

알고리즘(MST)

1
+2
+
1. 모든 별들 사이의 거리를 구한 다음에, 두 별의 위치, 거리를 저장한 구조체 배열을 거리순으로 오름차순 정렬
+2. 구조체 배열에서 하나씩 빼가며 크루스칼 시행 -> edge == numOfStar -1 이면 중지 후 출력
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+
#include <iostream>
+#include <vector>
+#include <cmath>
+#include <cstdio>
+#include <algorithm>
+#define MAX_STAR 100
+
+using namespace std;
+
+typedef struct Star {
+	double x;
+	double y;
+} Star;
+typedef struct Distance {
+	int first;
+	int second;
+	double distance;
+	bool operator <(Distance d) {
+		return distance < d.distance;
+	}
+} Distance;
+
+Star star[MAX_STAR];
+Distance dist[(MAX_STAR * (MAX_STAR+1))/2];
+int numOfStar, checkStar[MAX_STAR], distIndex;
+vector<vector<int>> set;
+
+double getMinDistance();
+void generateDistance();
+bool unionFind(int a, int b);
+
+int main() {
+	cin >> numOfStar;
+	for (int i = 0; i < numOfStar; i++) {
+		cin >> star[i].x >> star[i].y;
+	}
+	for (int i = 0; i < numOfStar; i++)
+		checkStar[i] = -1;
+
+	cout.precision(20);
+	cout << getMinDistance();
+}
+
+double getMinDistance() {
+	generateDistance();
+	sort(dist, dist + distIndex);
+
+	double value = 0;
+	int edge = 0;
+	for (int i = 0; i < distIndex; i++) {
+		if (unionFind(dist[i].first, dist[i].second)) {
+			value += dist[i].distance;
+			edge++;
+		}
+		if (edge == numOfStar - 1)
+			break;
+	}
+	return value;
+}
+
+void generateDistance() {
+	for (int i = 0; i < numOfStar; i++) {
+		for (int j = i + 1; j < numOfStar; j++) {
+			dist[distIndex].first = i;
+			dist[distIndex].second = j;
+			dist[distIndex++].distance = sqrt(((star[i].x - star[j].x) * (star[i].x - star[j].x)) + ((star[i].y - star[j].y) * (star[i].y - star[j].y)));
+		}
+	}
+}
+
+bool unionFind(int a, int b) {
+	if (checkStar[a] == -1 && checkStar[b] == -1) {
+		set.push_back({ a, b });
+		checkStar[a] = set.size() - 1;
+		checkStar[b] = set.size() - 1;
+	}
+	else if (checkStar[a] == -1) {
+		set[checkStar[b]].push_back(a);
+		checkStar[a] = checkStar[b];
+	}
+	else if (checkStar[b] == -1) {
+		set[checkStar[a]].push_back(b);
+		checkStar[b] = checkStar[a];
+	}
+	else if (checkStar[a] != checkStar[b]) {
+		int temp = checkStar[b];
+		for (int i = 0; i < set[temp].size(); i++) {
+			set[checkStar[a]].push_back(set[temp][i]);
+			checkStar[set[temp][i]] = checkStar[a];
+		}
+		set[temp].clear();
+	}
+	else
+		return false;
+	return true;
+}
+
This post is licensed under CC BY 4.0 by the author.

3197 백조의 호수

4811 알약

Comments powered by Disqus.

diff --git "a/posts/4485-\354\240\244\353\213\244/index.html" "b/posts/4485-\354\240\244\353\213\244/index.html" new file mode 100644 index 000000000..46f042fa6 --- /dev/null +++ "b/posts/4485-\354\240\244\353\213\244/index.html" @@ -0,0 +1,119 @@ + 4485 젤다 | 디피의 개발일지
Posts 4485 젤다
Post
Cancel

4485 젤다

알고리즘

  • 문제를 해석하면, 한 점에서 다른 한 점까지 가는데 최소경로를 찾는 문제로 해석할 수 있다.
  • 따라서 다익스트라 알고리즘을 적용하면 쉽게 풀수있다.
    • 이때 한 칸에 저장된 값은, 그 칸으로 접근하는 경로의 길이로 치환하여 품.

여담

  • 문제 내에 다익스트라라는 꽤나 많은 힌트가 있었으나, dfs에 사로잡혀서 오래 걸렸다.
  • 2차원 배열을 대상으로 다익스트라를 구현한 문제는 처음이었기에 꽤나 신선했다.

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+
#include <iostream>
+#include <queue>
+#define INF 2000000000
+using namespace std;
+
+int n, map[125][125], os[4][2] = { {-1, 0}, {0, 1}, {1, 0}, {0, -1} }, save[125][125];
+bool check[125][125];
+priority_queue<pair<int, pair<int,int>>> q;
+
+int dijk();
+int min(int a, int b) {
+	return a > b ? b : a;
+}
+
+int main() {
+	ios::sync_with_stdio(0);
+	cin.tie(0);
+
+	int idx = 1;
+	while (true) {
+		cin >> n;
+		if (n == 0)
+			break;
+
+		for (int i = 0; i < n; i++)
+			for (int j = 0; j < n; j++) {
+				cin >> map[i][j];
+				check[i][j] = false;
+			}
+
+		cout << "Problem " << idx++ << ": " << dijk() << endl;
+	}
+}
+
+int dijk() {
+	q.push({ -map[0][0] , {0,0 } });
+	save[0][0] = map[0][0];
+
+	while (!q.empty()) {
+		int cur = -q.top().first, r = q.top().second.first, c = q.top().second.second;
+		q.pop();
+
+		if (check[r][c])
+			continue;
+		check[r][c] = true;
+		save[r][c] = cur;
+
+		for (int i = 0; i < 4; i++) {
+			int nr = r + os[i][0], nc = c + os[i][1];
+
+			if (nr >= 0 && nr < n && nc >= 0 && nc < n && !check[nr][nc]) {
+ 				int ncur = cur + map[nr][nc];
+				q.push({ -ncur, {nr, nc} });
+			}
+		}
+	}
+
+	return save[n - 1][n - 1];
+}
+
This post is licensed under CC BY 4.0 by the author.

1613 역사

14658 하늘에서 별똥별

Comments powered by Disqus.

diff --git a/posts/4811/index.html b/posts/4811/index.html new file mode 100644 index 000000000..634911100 --- /dev/null +++ b/posts/4811/index.html @@ -0,0 +1,69 @@ + 4811 알약 | 디피의 개발일지
Posts 4811 알약
Post
Cancel

4811 알약

4811 알약

알고리즘 (dp)

1
+2
+
1. 하나의 완전한 알약을 one, 반쪽 알약을 half 라고 하면, dp[1001][1001] 를 선언하고, one, half를 기준으로 dp를 저장함
+2. dp를 진행하면서 밑으로 내려가면서 one이 1개 이상이면 1 줄이고 half 1 증가하고 밑으로 내려갈 수 있고, half가 1개 이상이면 마찬가지로.
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+
#include <iostream>
+
+using namespace std;
+
+long long dp[1001][1001];
+
+long long DP(int one, int half);
+
+int main() {
+	int N;
+	while (true) {
+		cin >> N;
+		if (N == 0)
+			break;
+		cout << DP(N, 0) << endl;
+	}
+}
+
+long long DP(int one, int half) {
+	if (one == 0 && half == 0)
+		return 1;
+	if (dp[one][half] != 0)
+		return dp[one][half];
+
+	long long result = 0;
+	if (one > 0)
+		result += DP(one - 1, half + 1);
+	if (half > 0)
+		result += DP(one, half - 1);
+
+	return dp[one][half] = result;
+}
+
This post is licensed under CC BY 4.0 by the author.

4386 make constellation

4991 로봇청소기

Comments powered by Disqus.

diff --git a/posts/4991/index.html b/posts/4991/index.html new file mode 100644 index 000000000..6f2ea2020 --- /dev/null +++ b/posts/4991/index.html @@ -0,0 +1,249 @@ + 4991 로봇청소기 | 디피의 개발일지
Posts 4991 로봇청소기
Post
Cancel

4991 로봇청소기

4991 로봇청소기

알고리즘(BFS, 브루트포스)

1
+2
+3
+4
+
1. 구현이 더 중요한 문제.
+2. 그냥 시작 + 각 더러운 곳(DT) 에서 다른 DT로의 최소 거리를 저장한 2차원 배열을 BFS로 만들고
+3. 시작부터 모든 경로를 탐색하여 최소 거리를 뽑아내면 됨.
+4. 방문 할 수 없는지 판별은 시작에서 갈 수 없는 DT가 있는지 확인하면 됨
+

구현

1
+2
+3
+4
+5
+6
+
1.  거리 저장한 2차원 배열은, 최대 DT가 10개이므로 [11][11] 로 선언함
+ 이때, 0은 시작장소를 뜻함
+ 따라서 각 줄의 0번째 칸과 자기자신은 0이어야함
+
+2. 시작하는 곳과 DT의 위치를 배열에 저장함.
+3. 맵에서 시작하는 곳은 '0' 으로, 나머지 각 DT는 '0' + 2. 배열 상의 index 로 BFS시 바로바로 참조가능하게 한다.
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+
#include <iostream>
+#include <cstring>
+#define MAX_WIDTH 20
+
+using namespace std;
+
+typedef struct Location {
+	int row;
+	int col;
+	int length;
+} Location;
+
+int w, h, numOfD = 1, eachOther[11][11];
+char map[MAX_WIDTH][MAX_WIDTH];
+Location q[MAX_WIDTH * MAX_WIDTH], node[11];
+bool visit[MAX_WIDTH][MAX_WIDTH], visitDistance[11];
+
+void BFS(Location s);
+int minDistance(int d, int cur);
+int min(int a, int b) {
+	return a < b ? a : b;
+}
+
+int main() {
+	while (true) {
+		cin >> w >> h;
+		if (w == 0 && h == 0)
+			break;
+
+		for (int i = 0; i < h; i++)
+			for (int j = 0; j < w; j++) {
+				cin >> map[i][j];
+				if (map[i][j] == 'o') {
+					map[i][j] = '0';			// start는 0, 더티는 1~10
+					node[0] = { i, j };
+				}
+				else if (map[i][j] == '*') {
+					node[numOfD] = { i, j };
+					map[i][j] = '0' + numOfD++;
+				}
+			}
+
+		for (int i = 0; i < numOfD; i++)
+			BFS(node[i]);
+
+		int count = 0;
+		for (int i = 1; i < numOfD; i++)
+			if (eachOther[0][i] > 0)
+				count++;
+
+		if (count < numOfD - 1)
+			cout << -1 << endl;
+		else
+			cout << minDistance(0, 0) << endl;
+
+		numOfD = 1;
+		memset(eachOther, 0, sizeof(eachOther));
+	}
+}
+
+void BFS(Location s) {
+	//start면 0, 더러운 곳이면 1~10 들어감
+	int cur = map[s.row][s.col] - '0';
+
+	int length = 0, index = 0;
+	q[length++] = { s.row, s.col, 0 };
+	visit[s.row][s.col] = true;
+
+	while (index < length) {
+		int row = q[index].row, col = q[index].col, l = q[index++].length;
+
+		if (map[row][col] - '0' >= 1 && map[row][col] - '0' <= 10) {
+			eachOther[cur][map[row][col] - '0'] = l;
+		}
+
+		if (row > 0 && !visit[row - 1][col] && map[row - 1][col] != 'x') {
+			q[length++] = { row - 1,col, l + 1 };
+			visit[row - 1][col] = true;
+		}
+		if (row < h - 1 && !visit[row + 1][col] && map[row + 1][col] != 'x') {
+			q[length++] = { row + 1,col, l + 1 };
+			visit[row + 1][col] = true;
+		}
+		if (col > 0 && !visit[row][col - 1] && map[row][col - 1] != 'x') {
+			q[length++] = { row,col - 1, l + 1 };
+			visit[row][col - 1] = true;
+		}
+		if (col < w - 1 && !visit[row][col + 1] && map[row][col + 1] != 'x') {
+			q[length++] = { row, col + 1, l + 1 };
+			visit[row][col + 1] = true;
+		}
+	}
+	for (int i = 0; i < length; i++)
+		visit[q[i].row][q[i].col] = false;
+}
+
+int minDistance(int d, int cur) {
+	int result = 2000000000;
+	bool check = true;
+
+	for (int i = 1; i < numOfD; i++) {
+		if (!visitDistance[i]) {
+			visitDistance[i] = true;
+			result = min(result, minDistance(d + eachOther[cur][i], i));
+			visitDistance[i] = false;
+			check = false;
+		}
+	}
+
+	if (check)
+		return d;
+	else
+		return result;
+}
+
This post is licensed under CC BY 4.0 by the author.

4811 알약

5014 스타트링크

Comments powered by Disqus.

diff --git a/posts/5014/index.html b/posts/5014/index.html new file mode 100644 index 000000000..a96d6512b --- /dev/null +++ b/posts/5014/index.html @@ -0,0 +1,79 @@ + 5014 스타트링크 | 디피의 개발일지
Posts 5014 스타트링크
Post
Cancel

5014 스타트링크

5014 스타트링크

알고리즘 (BFS)

1
+2
+3
+
- 직선으로의 BFS 를 사용하면 됨
+- DP를 사용하면 시간초과가 발생함.
+	- 도달시 다 종료한다고 해도, 그게 최소로 간건지 몰라서 결국 더 검사해야함.
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+
#include <iostream>
+#include <list>
+
+using namespace std;
+
+list<pair<int, int>> q;
+int F, G, S, U, D;
+bool check[1000001];
+
+int main() {
+	cin >> F >> S >> G >> U >> D;
+
+	q.push_back({ S, 0 });
+	check[S] = true;
+	bool canGo = false;
+	while (!q.empty()) {
+		int c = q.front().first, deep = q.front().second;
+		q.pop_front();
+
+		if (c == G) {
+			cout << deep << endl;
+			canGo = true;
+			break;
+		}
+		if (c + U <= F && !check[c+U]) {
+			q.push_back({ c + U, deep + 1 });
+			check[c + U] = true;
+		}
+		if (c - D >= 1 && !check[c-D]) {
+			q.push_back({ c - D, deep + 1 });
+			check[c - D] = true;
+		}
+	}
+	if (!canGo)
+		cout << "use the stairs" << endl;
+}
+
This post is licensed under CC BY 4.0 by the author.

4991 로봇청소기

6064 카잉달력

Comments powered by Disqus.

diff --git "a/posts/5213-\352\263\274\354\231\270\353\247\250/index.html" "b/posts/5213-\352\263\274\354\231\270\353\247\250/index.html" new file mode 100644 index 000000000..bdf4ce610 --- /dev/null +++ "b/posts/5213-\352\263\274\354\231\270\353\247\250/index.html" @@ -0,0 +1,191 @@ + 5213 과외맨 | 디피의 개발일지
Posts 5213 과외맨
Post
Cancel

5213 과외맨

알고리즘

  • BFS로 하나씩 찾아갈 수 있지만, 나는 연결되어있는 타일을 그래프로 표현한 다음 BFS를 하였다.
  • BFS를 할땐, visit을 -1로 초기화하고, 이전에 온 곳을 기록하는 형식으로 하자.
    • 그러면 마지막에서부터 온곳을 탐색하며 가서 첫번째 타일까지 가면 된다.

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+
#include <iostream>
+#include <vector>
+#include <list>
+#include <cstring>
+using namespace std;
+
+int N, tile[250000][2], last, visit[250000];
+vector<int> graph[250000], path;
+list<int> l;
+
+void makeGraph();
+void bfs();
+
+int main() {
+	cin >> N;
+	last = N * N - N / 2;
+	for (int i = 1; i <= last; i++)
+		cin >> tile[i][0] >> tile[i][1];
+
+	makeGraph();
+
+	memset(visit, -1, sizeof(visit));
+	bfs();
+
+	cout << path.size() << endl;
+	for (int j = path.size() -1; j >=0; j--)
+		cout << path[j] << " ";
+}
+
+void makeGraph() {
+	for (int i = 1; i <= N; i++) {
+		int end, start;
+		if (i % 2 == 0) {	// 짝수줄
+			end = N - 1;
+			start = N * (i / 2) + (N - 1) * ((i / 2) - 1);
+		}
+		else {	// 홀수줄
+			end = N;
+			start = N * (i / 2) + (N - 1) * (i / 2);
+		}
+
+		// 좌우, +-N, +-(N-1), 이때 홀수번째 처음은 +N-1, -N 안되고, 마지막은 +N, -N-1 안됨
+		for (int j = start + 1; j <= start + end; j++) {
+			if (j != start + 1 || i % 2 == 0) {	// 왼쪽
+				if (tile[j - 1][1] == tile[j][0] && j != start + 1)
+					graph[j].push_back(j - 1);
+				if (j - N >= 1 && tile[j - N][1] == tile[j][0])	// -N
+					graph[j].push_back(j - N);
+				if (j + N - 1 <= last && tile[j + N - 1][1] == tile[j][0]) 	// +N-1
+					graph[j].push_back(j + N - 1);
+			}
+			if (j != start + end || i % 2 == 0) { // 오른쪽
+				if (tile[j + 1][0] == tile[j][1] && j != start + end)
+					graph[j].push_back(j + 1);
+				if (j - N + 1 >= 1 && tile[j - N + 1][0] == tile[j][1]) // -(n-1)
+					graph[j].push_back(j - N + 1);
+				if (j + N <= last && tile[j + N][0] == tile[j][1])  // + N
+					graph[j].push_back(j + N);
+			}
+		}
+	}
+}
+// 다익스트라 후 검증으로 길찾기
+void bfs() {
+	visit[1] = 0;
+	l.push_back(1);
+	while (!l.empty()) {
+		int cur = l.front();
+		l.pop_front();
+
+		for (int i = 0; i < graph[cur].size(); i++) {
+			int next = graph[cur][i];
+			if (visit[next] == -1) {
+				visit[next] = cur;
+				l.push_back(next);
+			}
+		}
+	}
+
+	int first = 0;
+	for (int i = last; i >= 1; i--) {
+		if (visit[i] != -1) {
+			first = visit[i];
+			path.push_back(i);
+			break;
+		}
+	}
+
+	int cur = first;
+	while (cur != 0) {
+		path.push_back(cur);
+		cur = visit[cur];
+	}
+	return;
+}
+
This post is licensed under CC BY 4.0 by the author.

Graph ql front

10021 watering the field

Comments powered by Disqus.

diff --git a/posts/5373/index.html b/posts/5373/index.html new file mode 100644 index 000000000..ebc4bf783 --- /dev/null +++ b/posts/5373/index.html @@ -0,0 +1,357 @@ + 5373 Samsung sw test | 디피의 개발일지
Posts 5373 Samsung sw test
Post
Cancel

5373 Samsung sw test

5373 Samsung sw test

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+
#include <iostream>
+#include <cstring>
+#define MAX_ROTATE	1000
+#define MAX_TESTCASE 100
+#define WHITE 100
+#define YELLOW 200
+#define RED 300
+#define ORANGE 400
+#define GREEN 500
+#define BLUE 600
+
+using namespace std;
+
+typedef struct Command {
+	int location;	//큐브 면
+	int way;		//0이 반시계, 1은 시계
+} Command;
+
+int cube[6][9];	//0이 위, 1이 뒤, 2가 오, 3은 앞, 4는 왼쪽, 5는 아래
+int numOfTestcase;
+Command testcase[MAX_TESTCASE][MAX_ROTATE];
+int numOfCommand[MAX_TESTCASE];
+
+void implementTestcases();
+void implementSingleTC(Command command[MAX_ROTATE], int numCommand);
+void implementSingleCommand(int index[4], int cubeIndex[4][3], int way, int location);
+void printCube();
+void initCube();
+
+int main() {
+	cin >> numOfTestcase;
+	for (int i = 0; i < numOfTestcase; i++) {
+		cin >> numOfCommand[i];
+		for (int j = 0; j < numOfCommand[i]; j++) {
+			char temp;
+			cin >> temp;
+
+			if (temp == 'U') testcase[i][j].location = 0;
+			else if (temp == 'D') testcase[i][j].location = 5;
+			else if (temp == 'F') testcase[i][j].location = 3;
+			else if (temp == 'B') testcase[i][j].location = 1;
+			else if (temp == 'L') testcase[i][j].location = 4;
+			else if (temp == 'R') testcase[i][j].location = 2;
+
+			cin >> temp;
+			if (temp == '+') testcase[i][j].way = 1;
+			else if (temp == '-') testcase[i][j].way = 0;
+		}
+	}
+	implementTestcases();
+}
+
+void initCube() {
+	int colors[6] = { WHITE, ORANGE, BLUE, RED, GREEN, YELLOW };
+	for (int i = 0; i < 6; i++)
+		for (int j = 0; j < 9; j++)
+			cube[i][j] = colors[i];
+}
+
+void implementTestcases() {
+	for (int i = 0; i < numOfTestcase; i++) {
+		initCube();
+		implementSingleTC(testcase[i], numOfCommand[i]);
+		printCube();
+	}
+}
+
+void implementSingleTC(Command command[MAX_ROTATE], int numCommand) {
+	for (int i = 0; i < numCommand; i++) {
+		if (command[i].location == 0) {	//윗
+			int index[4] = { 1,2,3,4 };
+			int cubeIndex[4][3] = { {0,1,2},{0,1,2},{0,1,2},{0,1,2} };
+			implementSingleCommand(index, cubeIndex,command[i].way, 0);
+		}
+		else if (command[i].location == 1) {	//뒤
+			int index[4] = { 0,4,5,2 };
+			int cubeIndex[4][3] = { {2,1,0}, {0,3,6}, {2,1,0}, {8,5,2} };
+			implementSingleCommand(index, cubeIndex, command[i].way, 1);
+		}
+		else if (command[i].location == 2) {	//오
+			int index[4] = { 0,1,5,3 };
+			int cubeIndex[4][3] = { {8,5,2}, {0,3,6}, {0,3,6}, {8,5,2} };
+			implementSingleCommand(index, cubeIndex, command[i].way, 2);
+		}
+		else if (command[i].location == 3) {	//앞
+			int index[4] = { 0,2,5,4 };
+			int cubeIndex[4][3] = { {6,7,8}, {0,3,6}, {6,7,8}, {8,5,2} };
+			implementSingleCommand(index, cubeIndex, command[i].way, 3);
+		}
+		else if (command[i].location == 4) {	//왼
+			int index[4] = { 0,3,5,1 };
+			int cubeIndex[4][3] = { {0,3,6}, {0,3,6}, {8,5,2}, {8,5,2} };
+			implementSingleCommand(index, cubeIndex, command[i].way, 4);
+		}
+		else if (command[i].location == 5) {	//아
+			int index[4] = { 1,4,3,2 };
+			int cubeIndex[4][3] = { {6,7,8}, {6,7,8},{6,7,8},{6,7,8} };
+			implementSingleCommand(index, cubeIndex, command[i].way, 5);
+		}
+	}
+}
+
+void implementSingleCommand(int index[4], int cubeIndex[4][3], int way, int location) {
+	if (way == 1) {
+		int first = index[0];
+		int temp[3] = { cube[first][cubeIndex[0][0]], cube[first][cubeIndex[0][1]], cube[first][cubeIndex[0][2]] };
+		for (int r = 1; r < 4; r++) {
+			int temp2[3] = { cube[index[r]][cubeIndex[r][0]], cube[index[r]][cubeIndex[r][1]], cube[index[r]][cubeIndex[r][2]] };
+			cube[index[r]][cubeIndex[r][0]] = temp[0];
+			cube[index[r]][cubeIndex[r][1]] = temp[1];
+			cube[index[r]][cubeIndex[r][2]] = temp[2];
+			memcpy(temp, temp2, sizeof(int) * 3);
+		}
+		cube[first][cubeIndex[0][0]] = temp[0];
+		cube[first][cubeIndex[0][1]] = temp[1];
+		cube[first][cubeIndex[0][2]] = temp[2];
+	}
+	else {
+		int last = index[3];
+		int temp[3] = { cube[last][cubeIndex[3][0]], cube[last][cubeIndex[3][1]], cube[last][cubeIndex[3][2]] };
+		for (int r = 2; r >= 0; r--) {
+			int temp2[3] = { cube[index[r]][cubeIndex[r][0]], cube[index[r]][cubeIndex[r][1]], cube[index[r]][cubeIndex[r][2]] };
+			cube[index[r]][cubeIndex[r][0]] = temp[0];
+			cube[index[r]][cubeIndex[r][1]] = temp[1];
+			cube[index[r]][cubeIndex[r][2]] = temp[2];
+			memcpy(temp, temp2, sizeof(int) * 3);
+		}
+		cube[last][cubeIndex[3][0]] = temp[0];
+		cube[last][cubeIndex[3][1]] = temp[1];
+		cube[last][cubeIndex[3][2]] = temp[2];
+	}
+
+
+	int tempIndex[4][3] = { {0,1,2}, {2,5,8}, {8,7,6}, {6, 3, 0} };
+	int tempValue[4][3] = { {cube[location][0], cube[location][1], cube[location][2]}, {cube[location][2], cube[location][5], cube[location][8]},
+		{cube[location][8], cube[location][7], cube[location][6] }, {cube[location][6], cube[location][3], cube[location][0]} };
+	if (way == 1) {
+		for (int i = 1; i < 4; i++) {
+			cube[location][tempIndex[i][0]] = tempValue[i - 1][0];
+			cube[location][tempIndex[i][1]] = tempValue[i - 1][1];
+			cube[location][tempIndex[i][2]] = tempValue[i - 1][2];
+		}
+		cube[location][0] = tempValue[3][0];
+		cube[location][1] = tempValue[3][1];
+		cube[location][2] = tempValue[3][2];
+	}
+	else {
+		for (int i = 2; i >= 0; i--) {
+			cube[location][tempIndex[i][0]] = tempValue[i + 1][0];
+			cube[location][tempIndex[i][1]] = tempValue[i + 1][1];
+			cube[location][tempIndex[i][2]] = tempValue[i + 1][2];
+		}
+		cube[location][6] = tempValue[0][0];
+		cube[location][3] = tempValue[0][1];
+		cube[location][0] = tempValue[0][2];
+	}
+}
+
+void printCube() {
+	for (int i = 0; i < 9; i++) {
+		char temp;
+		if (cube[0][i] == WHITE)
+			temp = 'w';
+		else if (cube[0][i] == ORANGE)
+			temp = 'o';
+		else if (cube[0][i] == RED)
+			temp = 'r';
+		else if (cube[0][i] == GREEN)
+			temp = 'g';
+		else if (cube[0][i] == BLUE)
+			temp = 'b';
+		else if (cube[0][i] == YELLOW)
+			temp = 'y';
+		cout << temp;
+		if (i % 3 == 2)
+			cout << endl;
+	}
+}
+
This post is licensed under CC BY 4.0 by the author.

3568 iSharp

9095 1,2,3 더하기

Comments powered by Disqus.

diff --git a/posts/5827-What's-Up-With-Gravity/index.html b/posts/5827-What's-Up-With-Gravity/index.html new file mode 100644 index 000000000..e6a717d8b --- /dev/null +++ b/posts/5827-What's-Up-With-Gravity/index.html @@ -0,0 +1,193 @@ + 5827 What's Up With Gravity | 디피의 개발일지
Posts 5827 What's Up With Gravity
Post
Cancel

5827 What's Up With Gravity

알고리즘

  • BFS 사용
  1. 시작할때 C는 떨구고 시작.
  2. bfs를 진행하는데 왼쪽, 오른쪽, 중력 바꿔서 검사하고 떨군다.
    1. 떨어지는 도중에 D를 만나면, 만났다고 표시 -> 끝나고 바로 ans 업데이트
  3. 이때 visit은 해당 위치에서 해당 중력일때 최소 flip을 저장함
    1. 나는 이동후 떨구기 전과 떨군 후 두가지만 visit 표시했지만, 더 할 수도 있을 거 같음.
  • 시간 더 줄이기
    • 우선순위 큐 사용하여, flip 횟수가 적은 것을 우선하기
    • visit 체크를 더 할 수 있는 부분은 더하기

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+
#include <iostream>
+#include <list>
+#define INF 2000000000
+
+using namespace std;
+
+int n, m, os[2] = { 1, -1 }, visit[500][500][2], ans = INF;
+char map[500][500];
+pair<int, int> c;
+list<pair<pair<int, int>, pair<bool, int>>> l;
+bool meetD;
+
+bool fall(bool g, int* r, int* c);
+void bfs();
+void afterCheck(int *r, int *c, bool g, int f);
+int min(int a, int b) {
+	return a < b ? a : b;
+}
+
+int main() {
+	cin >> n >> m;
+	for (int i = 0; i < n; i++) {
+		string s;
+		cin >> s;
+		for (int j = 0; j < m; j++) {
+			map[i][j] = s[j];
+			if (s[j] == 'C')
+				c = { i, j };
+			visit[i][j][0] = visit[i][j][1] = INF;
+		}
+	}
+
+	if (!fall(0, &c.first, &c.second)) {
+		cout << -1;
+		return 0;
+	}
+
+	bfs();
+	if (ans != INF)
+		cout << ans;
+	else
+		cout << -1;
+}
+
+bool fall(bool g, int* r, int* c) {
+	while (((g == 0 && *r < n - 1) || (g == 1 && *r > 0)) && map[*r + os[g]][*c] != '#') {
+		if (map[*r][*c] == 'D')
+			meetD = true;
+		*r += os[g];
+	}
+
+	if (map[*r][*c] == 'D')
+		meetD = true;
+
+	if ((g == 0 && *r == n - 1) || (g == 1 && *r == 0))
+		return false;
+	return true;
+}
+
+void bfs() {
+	l.push_back({ {c.first, c.second}, {0,0} });
+	visit[c.first][c.second][0] = 0;
+
+	while (!l.empty()) {
+		int cr = l.front().first.first, cc = l.front().first.second, g = l.front().second.first, f = l.front().second.second;
+		l.pop_front();
+
+		if (map[cr][cc] == 'D') {
+			ans = min(ans, f);
+			continue;
+		}
+
+		for (int i = 0; i < 2; i++) {
+			int nr = cr, nc = cc + os[i];
+			if (nc >= 0 && nc < m && f < visit[nr][nc][g] && map[nr][nc] != '#')
+				afterCheck(&nr, &nc, g, f);
+		}
+
+		if (++f < visit[cr][cc][!g])
+			afterCheck(&cr, &cc, !g, f);
+	}
+}
+
+void afterCheck(int* r, int* c, bool g, int f) {
+	visit[*r][*c][g] = f;
+	bool temp = fall(g, r, c);
+
+	if (meetD) {
+		meetD = false;
+		ans = min(ans, f);
+	}
+	else if (temp) {
+		visit[*r][*c][g] = f;
+		l.push_back({ {*r,*c}, {g, f} });
+	}
+}
+
This post is licensed under CC BY 4.0 by the author.

React native 기초

9874 Wormholes

Comments powered by Disqus.

diff --git a/posts/6064/index.html b/posts/6064/index.html new file mode 100644 index 000000000..eed767e6b --- /dev/null +++ b/posts/6064/index.html @@ -0,0 +1,113 @@ + 6064 카잉달력 | 디피의 개발일지
Posts 6064 카잉달력
Post
Cancel

6064 카잉달력

6064 카잉달력

알고리즘(수학)

1
+2
+3
+4
+5
+6
+7
+8
+
1. n 을 M과 N으로 나누었을때 나온 수가 x와 y 이면 된다.
+ 단, 나누었을때 나오는 수는 0~M-1 or N-1인데 문제에선 1~M, 1~N 까지이므로 이걸 고려해야한다.
+2. 하나씩 증가시켜가면서하면 너무 오래걸리니, M과 N 중 더 큰수를 더하는 수로 삼고, 그것과 연결된 수부터 시작한다(M-x, N-y)
+	ex)	10 12 3 4 이면 4부터 시작하여 12씩 더함
+		각 단계의 수를 10과 12로 나누어서 3과 4가 나오면 가능한 것.
+3. 이때 M과 N이 같을 경우 x, y이 다르게 나오면 바로 -1 을 출력하도록 해야 틀리지 않는다.
+4. 2를 최소공배수 이하일때까지만 검사하는데, 최소공배수 구하긴 귀찮으므로 M*N 이하일 때까지 검사하자.
+	최대 N 번 검사하는게 고작이고, N 은 최대 4만이다. (M < N일때)
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+
#include <iostream>
+
+using namespace std;
+
+int M, N, x, y;
+
+int main() {
+	int tc;
+	cin >> tc;
+	while (tc--) {
+		cin >> M >> N >> x >> y;
+
+		int result = 0, lap, start;
+
+		if (M > N) {
+			lap = M;
+			start = x;
+		}
+		else if(M < N) {
+			lap = N;
+			start = y;
+		}
+		else if (x != y) {
+			cout << -1 << endl;
+			continue;
+		}
+		else {
+			lap = N;
+			start = y;
+		}
+
+		while (start <= M * N) {
+			int t1 = start % M == 0 ? M : start % M;
+			int t2 = start % N == 0 ? N : start % N;
+
+			if (t1 == x && t2 == y) {
+				result = start;
+				break;
+			}
+			start += lap;
+		}
+
+		if (result == 0)
+			cout << -1 << endl;
+		else
+			cout << result << endl;
+	}
+}
+
This post is licensed under CC BY 4.0 by the author.

5014 스타트링크

6087 레이저통신

Comments powered by Disqus.

diff --git a/posts/6087/index.html b/posts/6087/index.html new file mode 100644 index 000000000..51b112df5 --- /dev/null +++ b/posts/6087/index.html @@ -0,0 +1,165 @@ + 6087 레이저통신 | 디피의 개발일지
Posts 6087 레이저통신
Post
Cancel

6087 레이저통신

6087 레이저통신

알고리즘(BFS)

1
+2
+
1. visit 배열을 int로 선언. 이제까지 온 것들의 거울 개수를 저장
+2. 갈 수 잇고, visit에 저장된 거울 개수와 같거나 작으면 이동 시킴
+

주의점

1
+2
+3
+4
+
같은 레이저에 도달하지 않기위해 설정한 row != start.row && col != start.col 은 작동하지 않음
+-> 같은 줄에만 있어도 애들이 안가기 때문
+-> 따라서 다음처럼 조건문을 짜야함.
+	row != start.row || col != start.col
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+
#include <iostream>
+#include <list>
+
+using namespace std;
+
+typedef struct Location {
+	int row;
+	int col;
+	int direction;		// 0 : 초기, 1 : 위 2 오 3 아 4 왼
+	int mirror;
+} Location;
+
+int R, C;
+Location start;
+char map[100][100];
+int visit[100][100];
+int BFS();
+
+int main() {
+	cin >> C >> R;
+	for (int i = 0; i < R; i++)
+		for (int j = 0; j < C; j++) {
+			cin >> map[i][j];
+			if (map[i][j] == 'C') {
+				start.row = i;
+				start.col = j;
+			}
+			visit[i][j] = 2000000000;
+		}
+	cout << BFS();
+}
+int BFS() {
+	list<Location> q;
+	q.push_back({ start.row, start.col, 0, 0 });
+	visit[start.row][start.col] = 0;
+	int minMirror = 2000000000;
+
+	while (!q.empty()) {
+		int row = q.front().row, col = q.front().col, d = q.front().direction, m = q.front().mirror;
+		q.pop_front();
+
+
+		if ( map[row][col] == 'C') {
+			minMirror = m;
+		}
+		if (row > 0 && map[row - 1][col] != '*' && d != 3) {
+			int nextMirror = d != 4 ? d != 2 ? m : m + 1 : m + 1;
+			if (nextMirror <= visit[row - 1][col]) {
+				q.push_back({ row - 1, col, 1, nextMirror });
+				visit[row - 1][col] = nextMirror;
+			}
+		}
+		if (row < R - 1 && map[row + 1][col] != '*' && d != 1) {
+			int nextMirror = d != 4 ? d != 2 ? m : m + 1 : m + 1;
+			if (nextMirror <= visit[row + 1][col]) {
+				q.push_back({ row + 1, col, 3, nextMirror });
+				visit[row + 1][col] = nextMirror;
+			}
+		}
+		if (col > 0 && map[row][col - 1] != '*' && d != 2) {
+			int nextMirror = d != 3 ? d != 1 ? m : m + 1 : m + 1;
+			if (nextMirror <= visit[row][col - 1]) {
+				q.push_back({ row, col - 1, 4, nextMirror });
+				visit[row][col - 1] = nextMirror;
+			}
+		}
+		if (col < C - 1 && map[row][col + 1] != '*'&& d != 4) {
+			int nextMirror = d != 3 ? d != 1 ? m : m + 1 : m + 1;
+			if (nextMirror <= visit[row][col + 1]) {
+				q.push_back({ row, col + 1, 2, nextMirror });
+				visit[row][col + 1] = nextMirror;
+			}
+		}
+	}
+	return minMirror;
+}
+
This post is licensed under CC BY 4.0 by the author.

6064 카잉달력

7453 four Integer's sum should be zero

Comments powered by Disqus.

diff --git a/posts/7453/index.html b/posts/7453/index.html new file mode 100644 index 000000000..99360dbb8 --- /dev/null +++ b/posts/7453/index.html @@ -0,0 +1,133 @@ + 7453 four Integer's sum should be zero | 디피의 개발일지
Posts 7453 four Integer's sum should be zero
Post
Cancel

7453 four Integer's sum should be zero

7453 four Integer’s sum should be zero

알고리즘(정렬, 이분탐색)

1
+2
+3
+4
+5
+
1. 앞 두개 정수의 합 배열, 뒤 두개 정수의 합 배열을 만듬.(배열 개수^2 가 합 배열의 길이)
+2. 두 배열을 정렬
+3. 앞 합 배열의 원소를 하나씩 빼며 검사 -> 뒤 합 배열에서, upper_bound - lower_bound 값이, 현재 원소에서의 count 값.
+4. count 를 더하고 다음 원소 검사
+5. count 값을 long long 으로 선언
+

구현

1
+
1. lower_bound, upper_bound 로 몇개있는지 구하고, 그걸 count 에 더하는 식으로 구현
+

더 빠른 방법

1
+2
+3
+4
+5
+6
+7
+8
+
1. 합 배열에 중복된 값이 들어가는 게 문제.
+2. 구조체를 선언하고, 합과, 그 합이 몇번 나왔는지 저장하는 방법
+	-> 먼저 합 배열을 만들고, 같은 값이 나올 때마다, 같은 값의 가장 앞에 있는 곳에 count를 하나씩 증가하고 뒤에있는 같은 값은 sum을 -1로 만들어 버린다.
+	-> 그리고 값이 달라질때마다 count를 1씩 증가하여, 압축한 길이가 몇인지 구하기.
+	-> 값을 찾을 때는 이분탐색으로 찾는다.
+3. 앞보단 느리지만, 앞 합배열의 원소를 꺼낼때, 같은 원소면 바로 통과하도록 하기
+	-> 앞에서 연산한 결과를 이용하여 바로 통과시키자.
+	-> 130ms 줄임
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+
#include <iostream>
+#include <algorithm>
+#define MAX_ARRAY 4000
+
+using namespace std;
+
+int intArray[MAX_ARRAY][4];
+int firstTwo[MAX_ARRAY*MAX_ARRAY], lastTwo[MAX_ARRAY * MAX_ARRAY];
+int numOfArray;
+
+void generateSum();
+long long getCount();
+
+int main() {
+	cin >> numOfArray;
+	for (int i = 0; i < numOfArray; i++) {
+		for (int j = 0; j < 4; j++)
+			cin >> intArray[i][j];
+	}
+
+	generateSum();
+	sort(firstTwo, firstTwo + numOfArray*numOfArray);
+	sort(lastTwo, lastTwo + numOfArray*numOfArray);
+	cout << getCount();
+}
+
+void generateSum() {
+	int index = 0;
+	for (int i = 0; i < numOfArray; i++) {
+		for (int j = 0; j < numOfArray; j++) {
+			firstTwo[index] = intArray[i][0] + intArray[j][1];
+			lastTwo[index++] = intArray[i][2] + intArray[j][3];
+		}
+	}
+}
+
+long long getCount() {
+	long long count = 0;
+	int save[2] = { -1,-1 };
+	for (int i = 0; i < numOfArray*numOfArray; i++) {
+		int temp;
+		if (save[0] == firstTwo[i])
+			temp = save[1];
+		else
+			temp = (upper_bound(lastTwo, lastTwo + numOfArray * numOfArray, 0 - firstTwo[i])
+				- lower_bound(lastTwo, lastTwo + numOfArray * numOfArray, 0 - firstTwo[i]));
+		save[0] = firstTwo[i];
+		save[1] = temp;
+		count += temp;
+	}
+	return count;
+}
+
This post is licensed under CC BY 4.0 by the author.

6087 레이저통신

7579 app

Comments powered by Disqus.

diff --git a/posts/7579/index.html b/posts/7579/index.html new file mode 100644 index 000000000..252fec2bd --- /dev/null +++ b/posts/7579/index.html @@ -0,0 +1,99 @@ + 7579 app | 디피의 개발일지
Posts 7579 app
Post
Cancel

7579 app

7579 app

알고리즘(배낭정리, DP)

1
+2
+
1. 배낭정리 응용. 최대 비용을 가방에 담을 수 있는 최대 무게로 보고, 메모리를 가치로 본다
+2. 이렇게 2차원 배열을 완성하고, 필요한 메모리 이상이며 비용이 최소인 칸을 판별하고 출력하면 된다.
+

구현법

1
+2
+
1. 최대 앱 개수는 100개이므로, 101행, 앱 당 최대 비용은 100 이므로 10001열을 선언하여 구현하면 됨.
+2. 2차원 배열 완성하고나서는 첫번째 원소부터 검사하여, 메모리가 필요이상으로 있는데, 비용이 최소인 것을 찾으면 됨.
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+
#include <iostream>
+#define MAX_N 100
+#define MAX_MEMORY 10000000
+
+using namespace std;
+
+int numOfApp, a[MAX_N], c[MAX_N], m, maxValue;
+int DP[105][10020];
+
+int getMin();
+int max(int a, int b) {
+	return a > b ? a : b;
+}
+
+int main() {
+	cin >> numOfApp >> m;
+	for (int i = 0; i < numOfApp; i++)
+		cin >> a[i];
+	for (int i = 0; i < numOfApp; i++) {
+		cin >> c[i];
+		maxValue += c[i];
+	}
+
+	cout << getMin();
+}
+
+int getMin(){
+	for (int i = 1; i <= numOfApp; i++) {
+		for (int j = 1; j <= maxValue; j++) {
+			if (c[i-1] > j)
+				DP[i][j] = DP[i - 1][j];
+			else
+				DP[i][j] = max(a[i-1] + DP[i - 1][j - c[i-1]], DP[i - 1][j]);
+		}
+	}
+
+	int minValue = 2000000000;
+	for (int i = 0; i <= numOfApp; i++) {
+		for (int j = 0; j <= maxValue; j++) {
+			if (DP[i][j] >= m && minValue > j)
+				minValue = j;
+		}
+	}
+	return minValue;
+}
+
This post is licensed under CC BY 4.0 by the author.

7453 four Integer's sum should be zero

9252 LCS2

Comments powered by Disqus.

diff --git a/posts/8980/index.html b/posts/8980/index.html new file mode 100644 index 000000000..ae7bd55eb --- /dev/null +++ b/posts/8980/index.html @@ -0,0 +1,97 @@ + 8980 택배 | 디피의 개발일지
Posts 8980 택배
Post
Cancel

8980 택배

8980 택배

풀이(그리디)

1
+2
+3
+4
+5
+
1. 각 도착지별로 정보를 따로 관리함.(같이해도 되긴함)
+2. 도착지 1~N번까지 입력받은 C로 트럭 용량을 초기화함.
+3. 도착지 2번부터 정보를 하나씩 뽑아가며 검사. 시작지점~현재 도착지-1 까지에서의 최소값을 뽑고, 그만큼을 2의 배열에서 빼줌.(물론 그 최솟값이 현재 정보에서의 택배량보다 많으면 택배량으로 제한.)
+
+* 최솟값으로 뽑을 경우, 최대한 많이 담는 것이므로, 그것이 결국 최댓값이 됨.
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+
#include <iostream>
+#include <algorithm>
+#include <vector>
+using namespace std;
+
+vector<pair<int, int>> list[2021];
+int N, C, M, capacity[2021];
+
+int min(int a, int b) {
+	return a < b ? a : b;
+}
+
+int main() {
+	cin >> N >> C >> M;
+	for (int i = 0; i < M; i++) {
+		int a, b, c;
+		cin >> a >> b >> c;
+		list[b].push_back({ a,c });
+	}
+	for (int i = 1; i <= N; i++) {
+		capacity[i] = C;
+		sort(list[i].begin(), list[i].end());
+	}
+
+	int shipped = 0;
+
+	for (int i = 2; i <= N; i++) {
+		for (int j = list[i].size() - 1; j >= 0; j--) {
+			int s = list[i][j].first, box = list[i][j].second, m = 2000000000;
+			for (int k = s; k < i; k++) {
+				m = min(m, capacity[k]);
+			}
+			int howMany = min(box, m);
+			for (int k = s; k < i; k++)
+				capacity[k] -= howMany;
+			shipped += howMany;
+		}
+	}
+
+
+	cout<< shipped;
+}
+
+
This post is licensed under CC BY 4.0 by the author.

노드 스터디 5장

노드 스터디 4장

Comments powered by Disqus.

diff --git a/posts/9095/index.html b/posts/9095/index.html new file mode 100644 index 000000000..c758fa423 --- /dev/null +++ b/posts/9095/index.html @@ -0,0 +1,63 @@ + 9095 1,2,3 더하기 | 디피의 개발일지
Posts 9095 1,2,3 더하기
Post
Cancel

9095 1,2,3 더하기

9095 1,2,3 더하기

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+
#include <iostream>
+
+using namespace std;
+
+int tc;
+long long save[12];
+
+long long dp(int n);
+
+int main() {
+	cin >> tc;
+
+	while (tc--) {
+		int n;
+		cin >> n;
+		cout << dp(n) << endl;
+	}
+}
+
+long long dp(int n) {
+	if (n < 0)
+		return 0;
+	if (n == 0) {
+		return 1;
+	}
+	if (save[n] != 0)
+		return save[n];
+
+	return save[n] = dp(n - 3) + dp(n-2) + dp(n-1);
+
+}
+
This post is licensed under CC BY 4.0 by the author.

5373 Samsung sw test

13303 장애물 경기

Comments powered by Disqus.

diff --git a/posts/9251-LCS/index.html b/posts/9251-LCS/index.html new file mode 100644 index 000000000..1dda56f12 --- /dev/null +++ b/posts/9251-LCS/index.html @@ -0,0 +1,49 @@ + 9251 LCS | 디피의 개발일지
Posts 9251 LCS
Post
Cancel

9251 LCS

알고리즘

https://velog.io/@emplam27/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EA%B7%B8%EB%A6%BC%EC%9C%BC%EB%A1%9C-%EC%95%8C%EC%95%84%EB%B3%B4%EB%8A%94-LCS-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-Longest-Common-Substring%EC%99%80-Longest-Common-Subsequence

LCS 알고리즘을 굉장히 잘 설명한 글이 있어 링크로 대체

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+
#include <iostream>
+using namespace std;
+
+string s[2];
+int map[1011][1011];
+
+int max(int a, int b) {
+	return a < b ? b : a;
+}
+
+int main() {
+	cin >> s[0] >> s[1];
+
+	for (int i = 1; i <= s[1].size(); i++) {
+		for (int j = 1; j <= s[0].size(); j++) {
+			if (s[1][i - 1] == s[0][j - 1])
+				map[i][j] = map[i - 1][j - 1] + 1;
+			else
+				map[i][j] = max(map[i - 1][j], map[i][j - 1]);
+		}
+ 	}
+
+	cout << map[s[1].size()][s[0].size() ];
+}
+
This post is licensed under CC BY 4.0 by the author.

2096 내려가기

React-boilterplate 설명

Comments powered by Disqus.

diff --git a/posts/9252/index.html b/posts/9252/index.html new file mode 100644 index 000000000..7da56493d --- /dev/null +++ b/posts/9252/index.html @@ -0,0 +1,131 @@ + 9252 LCS2 | 디피의 개발일지
Posts 9252 LCS2
Post
Cancel

9252 LCS2

9252 LCS2

알고리즘(최장 공통 부분 수열)

1
+
1. LCS 알고리즘 사용하면 됨
+

구현법

1
+
1. 문자열 입력 받을 땐. cin.getline(char *c, size) 넣으면 된다.
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+
#include <iostream>
+#include <cstring>
+#define MAX_LENGTH 1000
+using namespace std;
+
+char a[MAX_LENGTH], b[MAX_LENGTH], lcs[MAX_LENGTH];
+int map[MAX_LENGTH + 1][MAX_LENGTH + 1];
+int al, bl, numOfLCS;
+
+void getLCS();
+int max(int a, int b) {
+	return a > b ? a : b;
+}
+
+int main() {
+	cin.getline(a, MAX_LENGTH+1);
+	cin.getline(b, MAX_LENGTH+1);
+	for (int i = 0; i < MAX_LENGTH; i++)
+		if (a[i] != 0)
+			al++;
+		else
+			break;
+	for (int i = 0; i < MAX_LENGTH; i++)
+		if (b[i] != 0)
+			bl++;
+		else
+			break;
+
+	getLCS();
+	cout << numOfLCS << endl;
+	for (int i = numOfLCS-1; i >= 0; i--)
+		cout << lcs[i];
+	cout << endl;
+}
+
+void getLCS() {
+	for (int i = 1; i <= bl; i++) {
+		for (int j = 1; j <= al; j++) {
+			if (b[i-1] == a[j-1])
+				map[i][j] = map[i-1][j-1]+ 1;
+			else
+				map[i][j] = max(map[i - 1][j], map[i][j - 1]);
+		}
+	}
+	int count = 0,start = al, before = map[bl][al]+1;
+	for (int i = bl; i >= 1; i--) {
+		for (int j = start; j >= 1; j--) {
+			if (before - 1 != map[i][j])
+				break;
+			if (map[i][j - 1] != map[i][j] && b[i - 1] == a[j - 1]) {
+				lcs[count++] = a[j - 1];
+				start = j - 1;
+				before = map[i][j];
+				break;
+			}
+		}
+		if (start == 0)
+			break;
+	}
+	numOfLCS = count;
+}
+
+
+
This post is licensed under CC BY 4.0 by the author.

7579 app

9328 key

Comments powered by Disqus.

diff --git a/posts/9328/index.html b/posts/9328/index.html new file mode 100644 index 000000000..6f3e4528b --- /dev/null +++ b/posts/9328/index.html @@ -0,0 +1,353 @@ + 9328 key | 디피의 개발일지
Posts 9328 key
Post
Cancel

9328 key

9328 key

알고리즘(BFS, 구현)

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+
1. 방문한 곳을 체크하는 check 이차원 배열, 키를 저장하는 key 배열, 닫힌문을 저장하는 closedDoor 벡터가 필요
+2. 먼저 키 배열은 26개의 원소로 이루어진 bool 자료형 배열로 선언. 각 알파벳에 해당하는 키가 들어오면 1을 저장한다.
+3. 방문가능한지 체크하는 checkCell 함수 선언
+	-> 빈공간이면 true반환, 문서면 빈공간으로 전환 후 document 1증가하고 true반환
+	   소문자면 열쇠 추가하고, 빈공간으로 전환 후 truw반환
+	   대문자고, 열쇠가 있으면 빈공간으로 전환 후 true 반환
+	   대문자고 열쇠가 없으면 closedDoor 벡터에 해당위치 추가하고 false 반환
+4. 처음에 테두리 먼저 검사
+	-> 체크 안됐고, 벽이 아니고, checkCell 통과하면 해당 위치에서 BFS 시행
+	-> 모든 테두리 검사
+5. 테두리 검사 후. cloesedDoor 검사
+	-> 체크 안됐고, 빈공간 전환 안됐고, 열쇠있으면 빈공간으로 전환 후 BFS 시행
+	-> 한번이라도 BFS가 시행되면 다시 처음부터 closedDoor검사를 함.
+	-> BFS가 시행안되면 루프 중단 후 document 수 출력
+

키 포인트

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+
1. 한번 검사한 cell은 다시 검사하지 않는다. -> 검사안된 곳은 열쇠가 없어서 못들어간 곳이므로, closedDoor만 검사하면 된다.
+2. 입력잘받자 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+문자열 입력
+	1. 길이를 정해줬을 때
+		: 띄어쓰기를 안했다해도, 그냥 map[i][j] 로 받으면 된다.
+	2. 길이를 안정해줬을 떄
+		: 	string temp;
+			cin >> tmep;
+			for(int i = 0; i<temp.length(); i++)
+				key[i] = temp[i];
+			로 받자
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+
#include <iostream>
+#include <cstring>
+#include <list>
+#include <vector>
+#include <string>
+#define MAX_WIDTH 102
+
+using namespace std;
+
+typedef struct Location {
+	int row;
+	int col;
+} Location;
+
+char map[MAX_WIDTH][MAX_WIDTH];
+bool key[27], check[MAX_WIDTH][MAX_WIDTH];
+int width, height, document;
+vector<Location> closedDoor;
+
+void getMaxDocument();
+void BFSfrom(int row, int col);
+bool checkCell(int row, int col);
+
+int main() {
+	int numOfTC;
+	cin >> numOfTC;
+	for (int i = 0; i < numOfTC; i++) {
+		memset(key, false, sizeof(key));
+		cin >> height >> width;
+		cin.ignore();
+		for (int j = 0; j < height; j++) {
+			for (int k = 0; k < width; k++)
+				cin >> map[j][k];
+		}
+		string tempkey;
+		cin >> tempkey;
+
+		for (int j = 0; j < tempkey.length(); j++) {
+			if (tempkey[j] == '0')
+				break;
+			key[tempkey[j] - 'a'] = true;
+		}
+
+		document = 0;
+		getMaxDocument();
+		cout << document << endl;
+	}
+}
+//모든 게이트로  들어감 -> 열수 있는 문은 열고 . 로 바꾸고 안으로 들어감. 열쇠든 문서든 주으면 .으로 바꿈
+	//열수 없는 문은 closedDoor에 추가
+	//들어갈 수 있는 곳 BFS가 끝나면, closedDoor 검사 수행. 새로 열린 곳을 기준으로 다시 BFS 시행 열린 곳이 없으면 그때의 count 반환
+void getMaxDocument() {
+	//모든 게이트 검사. 게이트가 이어지는 경우 고려
+	for (int i = 0; i < width; i++) {
+		if (!check[0][i] && map[0][i] != '*' && checkCell(0, i))
+			BFSfrom(0, i);
+	}
+	for (int i = 0; i < width; i++) {
+		if (!check[height - 1][i] && map[height - 1][i] != '*' && checkCell(height - 1, i))
+			BFSfrom(height - 1, i);
+	}
+	for (int i = 1; i < height - 1; i++) {
+		if (!check[i][0] && map[i][0] != '*' && checkCell(i, 0))
+			BFSfrom(i, 0);
+	}
+	for (int i = 1; i < height - 1; i++) {
+		if (!check[i][width - 1] && map[i][width - 1] != '*' && checkCell(i, width - 1))
+			BFSfrom(i, width - 1);
+	}
+
+	//closedDoor 검사
+	bool loopCheck = true;
+	while (loopCheck) {
+		loopCheck = false;
+		int length = closedDoor.size();
+		for (int i = 0; i < length; i++) {
+			if (!check[closedDoor[i].row][closedDoor[i].col] && map[closedDoor[i].row][closedDoor[i].col] != '.' && key[map[closedDoor[i].row][closedDoor[i].col] - 'A']) {
+				map[closedDoor[i].row][closedDoor[i].col] = '.';
+				BFSfrom(closedDoor[i].row, closedDoor[i].col);
+				loopCheck = true;
+			}
+		}
+	}
+
+	memset(check, 0, sizeof(check));
+	memset(key, 0, sizeof(key));
+	closedDoor.clear();
+	memset(map, 0, sizeof(map));
+}
+
+void BFSfrom(int row, int col) {
+	list<Location> q;
+	q.push_back({ row, col });
+	check[row][col] = true;
+
+	while (!q.empty()) {
+		Location temp = q.front();
+		q.pop_front();
+
+		if (temp.row > 0 && !check[temp.row - 1][temp.col] && map[temp.row - 1][temp.col] != '*') {
+			if (checkCell(temp.row - 1, temp.col)) {
+				check[temp.row - 1][temp.col] = true;
+				q.push_back({ temp.row - 1, temp.col });
+			}
+		}
+		if (temp.col < width - 1 && !check[temp.row][temp.col + 1] && map[temp.row][temp.col + 1] != '*') {
+			if (checkCell(temp.row, temp.col + 1)) {
+				check[temp.row][temp.col + 1] = true;
+				q.push_back({ temp.row, temp.col + 1 });
+			}
+		}
+		if (temp.row < height - 1 && !check[temp.row + 1][temp.col] && map[temp.row + 1][temp.col] != '*') {
+			if (checkCell(temp.row + 1, temp.col)) {
+				check[temp.row + 1][temp.col] = true;
+				q.push_back({ temp.row + 1, temp.col });
+			}
+		}
+		if (temp.col > 0 && !check[temp.row][temp.col - 1] && map[temp.row][temp.col - 1] != '*') {
+			if (checkCell(temp.row, temp.col - 1)) {
+				check[temp.row][temp.col - 1] = true;
+				q.push_back({ temp.row, temp.col - 1 });
+			}
+		}
+	}
+}
+
+bool checkCell(int row, int col) {
+	if (map[row][col] == '.')
+		return true;
+	else if (map[row][col] == '$') {
+		map[row][col] = '.';
+		document++;
+		return true;
+	}
+	else if (map[row][col] >= 'a' && map[row][col] <= 'z') {
+		key[map[row][col] - 'a'] = true;
+		map[row][col] = '.';
+		return true;
+	}
+	else if(map[row][col] >= 'A' && map[row][col] <='Z'){
+		if (key[map[row][col] - 'A']) {
+			map[row][col] = '.';
+			return true;
+		}
+		else {
+			closedDoor.push_back({ row, col });
+			return false;
+		}
+	}
+}
+
This post is licensed under CC BY 4.0 by the author.

9252 LCS2

9466 Term project

Comments powered by Disqus.

diff --git a/posts/9466/index.html b/posts/9466/index.html new file mode 100644 index 000000000..5236378da --- /dev/null +++ b/posts/9466/index.html @@ -0,0 +1,163 @@ + 9466 Term project | 디피의 개발일지
Posts 9466 Term project
Post
Cancel

9466 Term project

9466 Term project

알고리즘 (DFS, 그래프)

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
1. 각 학생들이 가리키는 학생을 저장한 student 배열, cycle에 몇명이 있는지 체크하는 cycleCcount 변수, 사이클이 형성됐는지 확인하는 cycleCheck bool형 배열.
+   검사중인 학생들을 저장하는 q배열
+2. 첫번째 학생부터 검사
+	-student가 -1 이면 검사안함. (이미 검사된 것)
+	-검사가 시작되면, q에 넣고, cycleCheck 배열에 표시하고, start엔 자기, end엔 다음걸 가리키며, 그래프따라 dfs 시작
+	-그래프 따라가면서 cycleCheck에 표시, end는 항상 다음 걸 가리키게 함.
+	-end가 -1이거나, 이미 검사한 거면 종료
+	-start와 end가 같으면 시작부터 사이클 형성된 것.
+		- q에 넣어진 곳을 전부 -1로 바꾸고, count를 q크기만큼 증가시킴
+	-다르면 end가 -1이 아닌 경우엔 중간에 사이클만 넣어주고, 전부 -1로 변경
+		-1이면 그냥 전부 -1로 변경
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+
#include <iostream>
+#include <cstring>
+#define MAX_STUDENT 100000
+
+using namespace std;
+
+int numOfStudent, student[MAX_STUDENT+1];
+bool checkCycle[MAX_STUDENT+1];
+int queue[MAX_STUDENT + 1], cycleCount;
+
+void generateCheck();
+
+int main() {
+	int tc;
+	cin >> tc;
+	for (int i = 0; i < tc; i++) {
+		cin >> numOfStudent;
+		for (int j = 1; j <= numOfStudent; j++)
+			cin >> student[j];
+
+		cycleCount = 0;
+		generateCheck();
+		cout << numOfStudent - cycleCount << endl;
+	}
+}
+void generateCheck() {
+	for (int i = 1; i <= numOfStudent; i++) {
+		if (student[i] != -1) {
+			int start = i;
+			int end = student[i];
+			int index = 0;
+			queue[index++] = i;
+			checkCycle[start] = true;
+			while (!checkCycle[end]) {
+				queue[index++] = end;
+				checkCycle[end] = true;
+				end = student[end];
+				if (end == -1)
+					break;
+			}
+
+			if (start == end) {
+				for(int j = 0; j<index; j++) {
+					cycleCount++;
+					student[queue[j]] = -1;
+					checkCycle[queue[j]] = false;
+				}
+			}
+			else {
+				int j = index - 1;
+				if (end != -1) {
+					for (; j >= 0; j--) {
+						if (queue[j] != end) {
+							cycleCount++;
+							student[queue[j]] = -1;
+							checkCycle[queue[j]] = false;
+						}
+						else
+							break;
+					}
+					cycleCount++;
+				}
+				for(; j>= 0; j--) {
+					checkCycle[queue[j]] = false;
+					student[queue[j]] = -1;
+				}
+			}
+		}
+	}
+}
+
This post is licensed under CC BY 4.0 by the author.

9328 key

9527 1의 개수세기

Comments powered by Disqus.

diff --git a/posts/9527/index.html b/posts/9527/index.html new file mode 100644 index 000000000..ae1d099e0 --- /dev/null +++ b/posts/9527/index.html @@ -0,0 +1,151 @@ + 9527 1의 개수세기 | 디피의 개발일지
Posts 9527 1의 개수세기
Post
Cancel

9527 1의 개수세기

9527 1의 개수세기

알고리즘(수학)

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+
1. 2^n 마다 규칙이 있음
+2. f(n)을 2^n ~ 2^(n+1)-1 에서의 1의 개수라고 한다면, f(n) = 2^n + f(i) ( 0 <= i <= n-1 ) 이 성립한다.
+3. 그리고 들어오는 수 A,B에 대해
+	(1~B 까지의 1의 개수) - (1~A-1 까지의 1의 개수) 를 하면 값이 나온다.
+4. 2에서의 규칙에따라 end가 주어지면 0에서 end까지의 1의 개수를 구하는 함수를 선언한다.
+	-> 이때 end 미만의 최대 2^n의 n를 구한다.
+	-> 2에서의 규칙에 따라 0~n까지의 f(n)을 구한다.
+	-> 그럼 나머지 2^(n+1) ~ end 에서의 1의 개수를 구하면 된다.
+
+5. 나머지를 구하는 함수를 선언한다. 2^(n+1)에서 end까지 구하면 된다.
+	-> 이때 2^(n+1) ~ end 사이의 값들의 맨앞은 전부 1이고, 개수는 end - 2^(n+1) + 1이다.
+	-> 그리고 그 맨앞의 1을 떼고 보면, 0부터 end - 2^(n+1) 까지의 이진수 분포와 동일하다
+	-> 따라서 다시 end - 2^(n+1)을 위 4번 함수에 넣으면 된다.
+

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+
#include <iostream>
+#include <cmath>
+
+using namespace std;
+
+long long a, b;
+long long DP[55];
+
+long long getN(int N);
+long long countOne(long long end);
+long long getRest(long long start, long long end);
+
+int main() {
+	cin >> a >> b;
+	DP[0] = 1;
+
+	cout << countOne(b) - countOne(a-1) << endl;
+}
+
+long long countOne(long long end) {
+	if (end <= 2)
+		return end;
+
+	int n = 0;
+	long long temp = 1;
+	while (temp < end) {
+		temp *= 2;
+		n++;
+	}
+	if (temp - end <=1)
+		n -= 1;
+	else
+		n -= 2;
+
+	long long result = 0;
+	for (int i = 0; i <= n; i++)
+		result += getN(i);
+
+	result += getRest(pow(2, n+1), end);
+
+	return result;
+}
+
+long long getN(int N) {
+	if (N == 0)
+		return 1;
+	if (DP[N] != 0)
+		return DP[N];
+
+	long long result = pow(2, N);
+	for (int i = N - 1; i >= 0; i--)
+		result += getN(i);
+
+	return DP[N] = result;
+}
+
+long long getRest(long long start, long long end) {
+	if (start > end)
+		return 0;
+
+	return end - start + 1 + countOne(end - start);
+}
+
This post is licensed under CC BY 4.0 by the author.

9466 Term project

12865 평범한 배낭

Comments powered by Disqus.

diff --git "a/posts/9576-\354\261\205-\353\202\230\353\210\240\354\243\274\352\270\260/index.html" "b/posts/9576-\354\261\205-\353\202\230\353\210\240\354\243\274\352\270\260/index.html" new file mode 100644 index 000000000..9120660fa --- /dev/null +++ "b/posts/9576-\354\261\205-\353\202\230\353\210\240\354\243\274\352\270\260/index.html" @@ -0,0 +1,127 @@ + 9576 책 나눠주기 | 디피의 개발일지
Posts 9576 책 나눠주기
Post
Cancel

9576 책 나눠주기

알고리즘

  • a, b에서 b의 순서대로 정렬

    1 2

    1 1

    이런식으로 입력이 들어오면, 1 2에서 1에 넣고 1 1에선 넣을 수가 없기 때문에.

  • 정렬된 (a, b)를 앞에서부터 검사하는데, a에서 b까지 1씩 증가하면서 가능하면 책을 배정하고, b까지 검사했는데도 남아있는 책이없으면 패스한다.

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+
#include <iostream>
+#include <list>
+#include <cstring>
+#include <algorithm>
+#include <vector>
+
+using namespace std;
+
+list<pair<int, int>> l;
+vector<pair<int, int>> v;
+bool card[1001];
+
+
+int main() {
+	int tc;
+	cin >> tc;
+	while (tc--) {
+		int n, m, ans = 0, a, b;
+		cin >> n >> m;
+		for (int i = 0; i < m; i++) {
+			cin >> a >> b;
+			v.push_back({ b, a });
+		}
+		sort(v.begin(), v.end());
+		for (int i = 0; i < v.size(); i++) {
+			l.push_back({ v[i].second, v[i].first });
+		}
+		v.clear();
+
+		auto iter = l.begin();
+		while (iter != l.end()) {
+			int f = iter->first, s = iter->second;
+			if (!card[f]) {
+				card[f] = true;
+				ans++;
+				iter = l.erase(iter);
+			}
+			else {
+				if (f + 1 > s)
+					iter = l.erase(iter);
+				else {
+					bool check = true;
+					for (int i = f + 1; i <= s; i++) {
+						if (!card[i]) {
+							card[i] = true;
+							ans++;
+							iter = l.erase(iter);
+							check = false;
+							break;
+						}
+					}
+					if (check) {
+						iter = l.erase(iter);
+					}
+				}
+			}
+		}
+
+		cout << ans << endl;
+
+		memset(card, 0, sizeof(card));
+	}
+}
+
This post is licensed under CC BY 4.0 by the author.

expo push notification

12906 새로운 하노이탑

Comments powered by Disqus.

diff --git a/posts/9874-Wormholes/index.html b/posts/9874-Wormholes/index.html new file mode 100644 index 000000000..0b33bacab --- /dev/null +++ b/posts/9874-Wormholes/index.html @@ -0,0 +1,167 @@ + 9874 Wormholes | 디피의 개발일지
Posts 9874 Wormholes
Post
Cancel

9874 Wormholes

알고리즘

  • 페어링 쌍을 만들고, 사이클이 있는지 확인하는 작업을 모든 페어링 쌍을 대상으로 하면 됨.
  • 헤멘 부분
    1. 사이클을 찾을때 같은 페어링쌍을 다시 방문하는게 아니라, 같은 점을 다시 방문하는지 확인해야함.
    2. check 를 언제 초기화해야하는지 헷갈림 -> 확실히하려면 매 시작점마다하면 됨.

여담

  • 출척 Olympiad> USA Computing Olympiad 인데, 요즘 이 출처의 문제들을 풀면서 느낀 게, 이 출처의 문제들은 내용자체는 간단한데, 예외 또는 특수한 경우 처리가 매우 어렵다.
  • 그리고 내겐 한번 ‘틀렸습니다.’가 뜨면 기존 알고리즘에 집착해서 새로운 생각을 못하는 단점이 있는거 같다.
  • 앞으로는 문제풀때 배열 범위문제는 일어나지 않도록 크게 두고, 틀렸습니다가 뜨면 기존 알고리즘 재점검하고, 문제 없을경우 지체없이 특수한 경우를 생각해봐야겠다.

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+
#include <iostream>
+#include <cstring>
+#define INF 2000000000
+
+using namespace std;
+
+int n, holes[15][2], ans, pairing[7][2];
+bool visit[15], check[8][2];
+
+void makePair(int cur, int deep);
+bool findCycle(int s, int o);
+
+int main() {
+	cin >> n;
+	for (int i = 0; i < n; i++)
+		cin >> holes[i][0] >> holes[i][1];
+
+	visit[0] = true;
+	makePair(0, 0);
+
+	cout << ans;
+}
+
+void makePair(int cur, int deep) {
+	if (deep == n / 2) {
+		for (int i = 0; i < 2; i++) {
+			for (int j = 0; j < n / 2; j++)
+				if (findCycle(j, i)) {
+					ans++;
+					return;
+				}
+		}
+		return;
+	}
+	pairing[deep][0] = cur;
+	for (int i = cur + 1; i < n; i++) {
+		if (!visit[i]) {
+			visit[i] = true;
+			pairing[deep][1] = i;
+			int j = 0;
+			for (; j < n; j++)
+				if (!visit[j])
+					break;
+			visit[j] = true;
+			makePair(j, deep + 1);
+			visit[i] = false;
+			visit[j] = false;
+		}
+	}
+}
+
+
+bool findCycle(int s, int o) {
+	int next = pairing[s][!o];
+	check[s][o] = true;
+
+	while (next != -1) {
+		int cur = next;
+		next = -1;
+
+		int cr = holes[cur][1], cc = holes[cur][0], ni, nj, close = INF;
+		for (int i = 0; i < n / 2; i++) {
+			for (int j = 0; j < 2; j++) {
+				if (cr == holes[pairing[i][j]][1] && cc < holes[pairing[i][j]][0] && holes[pairing[i][j]][0] < close) {
+					close = holes[pairing[i][j]][0];
+					ni = i;
+					nj = j;
+				}
+			}
+		}
+
+		if (close != INF) {
+			if (check[ni][nj]) {
+				memset(check, 0, sizeof(check));
+				return true;
+			}
+			check[ni][nj] = true;
+			next = pairing[ni][!nj];
+		}
+	}
+	memset(check, 0, sizeof(check));
+	return false;
+}
+
This post is licensed under CC BY 4.0 by the author.

5827 What's Up With Gravity

10875 뱀

Comments powered by Disqus.

diff --git "a/posts/9935-\353\254\270\354\236\220\354\227\264-\355\217\255\353\260\234/index.html" "b/posts/9935-\353\254\270\354\236\220\354\227\264-\355\217\255\353\260\234/index.html" new file mode 100644 index 000000000..be4c0a636 --- /dev/null +++ "b/posts/9935-\353\254\270\354\236\220\354\227\264-\355\217\255\353\260\234/index.html" @@ -0,0 +1,185 @@ + 9935 문자열 폭발 | 디피의 개발일지
Posts 9935 문자열 폭발
Post
Cancel

9935 문자열 폭발

매우 어렵게 구현하여 풀긴하였으나, 스택을 사용하면 매우 직관적으로 편하게 구현할 수 있었다.

스택을 사용하여 문제를 푼 경험이 적어서 스택을 떠오르지 못한 것 같다

스택을 사용한 문자열 문제를 몇문제 풀어봐야할 것 같음

내 풀이 : 빡센 구현

알고리즘

  • 앞에서부터 검사하다가 폭발문자열과 일치하는 문자열이 발견되면, 그 문자열을 폭파시키고, 없어진 다음 문자열을 참조하도록 함.
  • 예를들어, 문자열 인덱스 3~6이 폭파되었다면, to[3] = 7 을 저장하여, 검사하는 위치를 앞으로 당겨서 다시 검사할때, 폭파된 문자열을 점프하도록 한다.

평가

  • 구현이 매우 복잡함. to뿐만이 아니라 뒤로갈때 필요한 from 배열도 필요하여 메모리 낭비가 심함

코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+
#include <iostream>
+#include <cstring>
+using namespace std;
+
+int to[1000010], from[1000010];
+string f, s;
+
+int main() {
+	ios::sync_with_stdio(0);
+	cin.tie(0);
+	cout.tie(0);
+
+	cin >> f >> s;
+	memset(to, -1, sizeof(to));
+	memset(from, -1, sizeof(from));
+
+	int start = 0;
+	for (int i = 0; i <= f.size() - s.size(); ) {
+		if (to[i] != -1) {
+			i = to[i];
+			continue;
+		}
+
+		bool check = true;
+
+		int j = i;
+		for (int cnt = 0; cnt < s.size();) {
+			if (j >= f.size()) {
+				check = false;
+				break;
+			}
+			if (to[j] != -1) {
+				j = to[j];
+				continue;
+			}
+			if (f[j] != s[cnt]) {
+				check = false;
+				break;
+			}
+			cnt++;
+			j++;
+		}
+
+		if (check) {
+			to[i] = j;
+			from[j - 1] = i - 1;
+			if (j == f.size()) 
+				i = j;
+			else if (start == i) 
+				start = i = j;
+			else {
+				int t = i - 1;
+				for (int cnt = 0; cnt < s.size();) {
+					if (t < start)
+						break;
+					if (from[t] != -1) {
+						t = from[t];
+						continue;
+					}
+					cnt++;
+					t--;
+				}
+
+				if (t < start)
+					i = start;
+				else 
+					i = t;
+			}
+		}
+		else
+			i++;
+	}
+
+	for (int i = 0; i < f.size(); i++)
+		cout << to[i] << " ";
+	cout << endl;
+
+	string ans = "";
+	for (int i = 0; i < f.size(); i++) {
+		if (to[i] != -1) {
+			i = to[i] -1;
+			continue;
+		}
+
+		ans.push_back(f[i]);
+	}
+
+	if (ans.size() == 0)
+		cout << "FRULA";
+	else
+		cout << ans;
+}
+

스택을 이용한 풀이

알고리즘

  • 문자열을 앞에서부터 스택에 집어넣다가, 현재 넣은 문자가 폭발문자열의 맨 끝과 같으면, 스택에 넣었던 문자열을 다시 하나씩 꺼내어 폭발문자열과 일치하는지 확인
    • 일치하면 없애서 다시 넣기 시작함
    • 일치하지 않으면 뺐던 문자들을 다시 하나씩 넣음

평가

  • 구현이 매우 단순하고 알고리즘이 직관적임
This post is licensed under CC BY 4.0 by the author.

함수형 프로그래밍

RESTful API

Comments powered by Disqus.

diff --git a/posts/CSS_position/index.html b/posts/CSS_position/index.html new file mode 100644 index 000000000..9cf058e10 --- /dev/null +++ b/posts/CSS_position/index.html @@ -0,0 +1 @@ + CSS position 에 관하여 | 디피의 개발일지
Posts CSS position 에 관하여
Post
Cancel

CSS position 에 관하여

static

  • 기본 속성
  • 원래 있어야할 위치에 있다.
  • top, left, right, bottom 으로 위치 조절 불가능

relative

  • static 일 때의 위치를 기준으로 조절 가능.
  • 겹치는 element 가 있으면, z-index로 결정한다.

absolute

  • position:static 을 가지고 있지 않은 부모를 기준으로 위치가 조절 된다.

fixed

  • 브라우저 기준으로 위치 조절 가능
This post is licensed under CC BY 4.0 by the author.

JunctionXSeoul 2021 후기

CSS 단위

Comments powered by Disqus.

diff --git a/posts/CSS_unit/index.html b/posts/CSS_unit/index.html new file mode 100644 index 000000000..d60d3abfe --- /dev/null +++ b/posts/CSS_unit/index.html @@ -0,0 +1,23 @@ + CSS 단위 | 디피의 개발일지
Posts CSS 단위
Post
Cancel

CSS 단위

em : 부모의 단위에 배수를 더하는 것.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
body {
+
+ font-size:14px;
+
+}
+
+div {
+
+ font-size:1.2em;
+
+}
+

면 div엔 16.8px로 들어간다.

이때 부모의 크기에서 배수를 더하는 거라서, 자식마다 em을 써서 내려가면 크기는 계속 배수로 증가하게 된다.

rem

root의 단위에 배수를 더하는 것. 최상위 태그에 대해서 배수로만 구하는 거라, 자식의 깊이가 깊어져도 크기가 기하급수적으로 커지지 않는다.

vh : viewport height, vw : viewport width

뷰포트의 높이값과 너비값에 맞추어 사용한다.

뷰포트의 높이, 너비를 100으로 나누어 1vh, 1vw로 표현한다. 100vh, 100vw는 뷰포트와 크기가 동일해진다.

vmin, vmax

뷰포트의 높이, 너비 값 중 작은 건 vmin에 큰건 vmax에 넣어 100분의 1 단위로 사용한다.

ex) 높이가 700px, 너비가 1100px 이면, 1vmin엔 7px, 1vmax엔 11px가 들어감

뷰포트의 크기에 맞추어, 정사각형을 만들 때 유용하다

ex, ch

둘다 현재 폰트와 폰트사이즈에 의존한다는 점에서 em, rem과 비슷.

하지만 이 두단위는 font-family에 의존함.

ch : 제로문자인 0의 너비값의 고급척도로 정의됨.

ex : 현재폰트의 x높이값 또는 em의 절반값.

This post is licensed under CC BY 4.0 by the author.

CSS position 에 관하여

로티(lottie) 애니메이션 적용

Comments powered by Disqus.

diff --git a/posts/DB-ElasticSearch/index.html b/posts/DB-ElasticSearch/index.html new file mode 100644 index 000000000..ead4e3299 --- /dev/null +++ b/posts/DB-ElasticSearch/index.html @@ -0,0 +1 @@ + ElasticSearch | 디피의 개발일지
Posts ElasticSearch
Post
Cancel

ElasticSearch

Elasticsearch는 시간이 갈수록 증가하는 문제를 처리하는 분산형 RESTful 검색 및 분석 엔진입니다. Elastic Stack의 핵심 제품인 Elasticsearch는 데이터를 중앙에 저장하여 손쉽게 확장되는 광속에 가까운 빠른 검색, 정교하게 조정된 정확도, 강력한 분석을 제공합니다.

(ElasticSearch 공식 문구)

ElasticSearch는 Apache Lucene(아파치 루씬) 기반의 Java 오픈소스 분산 검색엔진이다. ElasticSearch를 통해 루씬 라이브러리를 단독으로 사용할 수 있게 되었으며, 방대한 양의 데이터를 신속하게 거의 실시간으로 저장, 검색, 분석할 수 있다.

ElasticSearch은 검색을 위해 단독으로 사용되기도 하고, ELK(ElasticSearch / logstash / kibana) 스택으로 사용되기도 한다

ELK 스택

  • Logstash : 다양한 소스의 로그 또는 트랜잭션 데이터를 수집, 집계, 파싱하여 ElasticSearch로 전달
  • ElasticSearch : Logstash로부터 받은 데이터를 검색 및 집계하여 필요한 관심있는 정보를 획득
  • Kibana : ElasticSearch의 빠른 검색을 통해 데이터를 시각화 및 모니터링

ELK 스택


ElasticSearch와 RDBMS 용어 비교

RDBMSElasticSearch
Databaseindex
TableType
RowDocument
ColumnField
IndexAnalyze
Primary key_id
schemaMapping
Physical partitionShared
Logical PartitionRoute
RelationalParent/Child, Nested
SQLQuery DSL


ElasticSearch 아키텍처 / 용어 정리

ElasticSearch아키텍처

  • 클러스터
    • ElasticSearch에서 가장 큰 시스템 단위이며, 최소 하나 이상의 노드로 이루어진 노드들의 집합이다
    • 서로 다른 클러스터는 데이터의 접근, 교환을 할 수 없는 독립적인 시스템으로 유지되며, 여러 대의 서버가 하나의 클러스터를 구성할 수 있고, 한 서버에 여러 개의 클러스터가 존재할 수도 있다
  • 노드
    • ElasticSearch를 구성하는 하나의 단위 프로세스
    • 역할에 따라 다음과 같이 구분된다
      • Master-eligible node : 클러스터를 제어하는 마스터로 선택할 수 있는 노드.
        • 인덱스 생성, 삭제
        • 클러스터 노드들의 추적, 관리
        • 데이터 입력 시 어느 샤드에 할당할 건지 선택
      • Data node : 데이터와 관련된 CRUD 작업을 하는 노드. CPU, 메모리 등 자원을 많이 소비하므로 모니터링이 필요하며 master 노드와 분리되는 것이 좋다
      • ingest node : 데이터를 변환하는 등 사전 처리 파이프라인을 실행
      • coordination only node : 로드밸런서와 같이 data node와 master-eligible node의 일을 대신한다
  • 인덱스 : RDBMS의 데이터베이스와 대응하는 개념.
  • 샤드(Shard)
    • 데이터를 분산해서 저장하는 방법. 스케일 아웃을 위해 인덱스를 여러 샤드로 쪼갠다.
    • 기본적으로는 한개가 존재하며, 검색 성능 향상을 위해 클러스터의 샤드 갯수를 조정하는 튜닝읗 하기도 한다.
    • 샤드의 종류
      • 프라이머리 샤드 : 데이터의 원본. ElasticSearch에서 데이터 업데이트 요청을 날리면 반드시 프라이머리 샤드에 요청하게 되고, 해당 내용은 레플리카 샤드에 복제된다.
      • 레플리카(Replica) 샤드
        • 프라이머리 샤드의 복제본. 노드를 손실했을 경우 데이터의 신뢰성을 위해 샤드들을 복제한다.
        • 따라서 레플리카는 서로 다른 노드에 존재할 것을 권장한다.

replica

  • 세그먼트
    • ElasticSearch에서 문서의 빠른 검색을 위해 설계된 자료구조.
    • 샤드의 데이터를 가지고 있는 물리적인 파일이다. 각 샤드는 다수의 세그먼트로 구성되어있으므로 검색 요청을 분산처리하여 훨씬 효율적인 검색이 가능하다.
    • 샤드에서 검색 시, 먼저 각 세그먼트를 검색하여 결과를 조합한 후 최종 결과를 해당 샤드의 결과로 반환하게 된다. 이때 세그먼트는 내부에 색인된 데이터가 역색인 구조로 저장되어 있으므로 검색 속도가 매우 빠르다.
    • 인메모리 버퍼
      • 매 요청마다 새로운 세그먼트를 만들면 비효율적이므로, 요청을 먼저 저장하는 버퍼이다. 인메모리 버퍼가 가득차거나 일정 시간이 지나면 flush 작업이 일어나고, 시스템 캐시에 세그먼트가 생성된다. 이 시점에서 데이터 검색이 가능해진다.
      • 시스템 캐시에 저장된 세그먼트는 일정 시간이 지나면 commit을 통해 물리적인 디스크에 저장된다.
      • 저장된 세그먼트는 시간이 지날 수록 하나로 병합하는 과정을 수행하고, 병합을 통해 세그먼트를 하나로 줄여 주면, 검색할 세그먼트의 개수가 줄어들기 때문에 검색 성능이 향상된다.


ElasticSearch의 특징

  • Scale out : 샤드를 통해 규모가 수평적으로 늘어날 수 있음

  • 고가용성 : Replica를 통해 데이터의 안정성을 보장

  • Schema Free : Json 문서를 통해 데이터 검색을 수행하므로 스키마 개념이 없음

  • Restful : 데이터 CRUD 작업은 HTTP Restful API를 통해 수행하며 각각 다음과 같이 대응한다

    CRUDElasticsearch Restful
    SELECTGET
    INSERTPUT
    UPDATEPOST
    DELETEDELETE


ElasticSearch의 역색인

아파치 루씬은 기본적으로 역색인이라는 구조로 데이터를 저장한다. ElasticSearch도 루씬 기반이므로 역색인을 사용한다.

역색인(inverted index)이란, 책의 맨 뒤에 키워드가 어떤 페이지에 있는지 알려주는 페이지처럼, 값을 통해 위치를 알아내는 방식을 통해 RDBMS보다 전문 검색(full text search)에 빠른 성능을 보인다.

예를 들어 “Lorem Ipsum is simply dummy text of the printing and typesetting industry” 란 문장이 있으면 이 문장을 모두 파싱해서 각 단어들을 저장하고, 대문자는 소문자 처리하고, 유사어도 체크하여 텍스트를 저장한다. 그리고 키워드와 함께 검색 요청이 오면, 키워드를 통해 문서를 찾아내어 검색 성능이 매우 빠르다.


ElasticSearch 단점

  • 실시간 처리가 불가능하다
    • ElasticSearch은 인메모리 버퍼를 사용하므로, 쓰기가 발생해도 세그먼트 생성 전까지는 해당 데이터를 검색할 수 없다
  • 트랜잭션을 지원하지 않는다
    • 분산 시스템 구성의 특징때문에 시스템적으로 비용 소모가 큰 트랜잭션 및 롤백을 지원하지 않는다
  • 진정한 의미의 업데이트를 지원하지 않는다.
    • 세그먼트에서 데이터가 삭제되면 soft delete를 한다
    • 세그먼트에서 데이터가 수정되면 soft delete를 하고 수정된 데이터를 새로운 세그먼트로 생성한다
    • RDBMS의 index와 유사한 동작


NoSql과의 차이

 ElasticSearchNoSQL
실시간 처리불가능가능
역색인 자료구조있음없음
구조검색 엔진데이터 저장소


기타 참고할 문서


출처

https://steady-coding.tistory.com/573

https://victorydntmd.tistory.com/308

This post is licensed under CC BY 4.0 by the author.

Apache Kafka

서블릿 컨테이너

Comments powered by Disqus.

diff --git a/posts/Esbuild-to-reduce-build-times-by-90/index.html b/posts/Esbuild-to-reduce-build-times-by-90/index.html new file mode 100644 index 000000000..78a25906b --- /dev/null +++ b/posts/Esbuild-to-reduce-build-times-by-90/index.html @@ -0,0 +1 @@ + Esbuild to reduce build times by 90% | 디피의 개발일지
Posts Esbuild to reduce build times by 90%
Post
Cancel

Esbuild to reduce build times by 90%

원본

기존에 빌드가 오래 걸렸던 원인들

  • 웹팩, 롤업 작업 시간이 대부분이었음
  • 작은 의존성들이 하나씩 빌드됨. 이것들은 동시에 진행될 여지가 있음
  • 몇몇 의존성은 필요한 것보다 오래 빌드됨.
  • 변경되지 않은 파일에 대해서 typeshare 를 재실행하지 않기위해 find를 실행했으나, 그냥 typeshare를 실행하는 것보다 find 실행시간이 더 길었음

해커톤을 통해 몇몇 시도한 해결책

  • esbuild를 webpack, rollup의 로더로 사용하여 성능 개선을 이루어냄.
    • rollup 에서는 80%의 성능개선을 이루어냄
  • esbuild로 webpack, rollup을 완전히 대체함 -> 90%의 성능개선

실제 서비스 적용

  • esbuild, with typechecking
    • tsc가 너무 느린 문제를 해결하기위해, esbuild-plugin-typecheck를 사용함.
    • 이를 통해 논블록킹으로 tsc를 실행가능하게 한다
    • 여기에 더해 에러메세지를 포맷팅하여 가독성을 높였고, 모든 빌드 입력 파일이 제대로 타입 체킹되었는지 확인하는 로직을 추가함
  • production bundle size improvements
    • esbuild의 bundle size analyzer를 사용하여 불필요하게 번들 사이즈를 늘리는 원인을 찾아내어 크기를 줄일 수 있었다
This post is licensed under CC BY 4.0 by the author.

실용주의프로그래머 - 전체 후기

How I Use "AI"

Comments powered by Disqus.

diff --git a/posts/Firebase_cloudfunction_puppeteer/index.html b/posts/Firebase_cloudfunction_puppeteer/index.html new file mode 100644 index 000000000..b0c2486aa --- /dev/null +++ b/posts/Firebase_cloudfunction_puppeteer/index.html @@ -0,0 +1,75 @@ + Firebase/Function Puppeteer 사용하기 | 디피의 개발일지
Posts Firebase/Function Puppeteer 사용하기
Post
Cancel

Firebase/Function Puppeteer 사용하기

일반 노드에서의 환경과 같이 사용하면 된다.

  • 홈페이지에 접속 후 스크린샷을 찍어 스토리지에 업로드하는 예시
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+
const puppeteer = require("puppeteer");
+const { Storage } = require("@google-cloud/storage");
+
+const storage = new Storage();
+
+router.post("/", (req, res) => {
+  (async () => {
+    const browser = await puppeteer.launch({
+      args: ["--no-sandbox", "--disable-setuid-sandbox"],
+    });
+    const page = await browser.newPage();
+
+    await page.goto(req.body.url);
+
+    await page.waitForTimeout(5000);
+    const screenshotBinary = await page.screenshot({
+      fullPage: true,
+      encoding: "binary",
+    });
+
+    const bucket = storage.bucket("스토리지 버켓 토큰");
+
+    const file = bucket.file(`${req.body.uid}/${req.body.uuid}.png`);
+
+    await file.save(screenshotBinary, {
+      metadata: { contentType: "image/png" },
+    });
+    await browser.close();
+
+    res.send({ data: "uploaded" });
+  })();
+});
+

주의점

firebase cloud function은 연결된 함수가 실행되고, 종료되면 그 함수를 돌리기위한 환경또한 같이 종료되게 된다.

따라서, cloud function에서 puppeteer를 사용하려면, 작업이 전부 완료될때까지 함수가 종료되지 않도록 해줘야한다.

1
+2
+3
+4
+5
+
...
+return Promise.all(promises).then(async() => {
+    await browser.close();
+})
+...
+

위처럼 puppeteer를 부르는 모든 작업을 promises에 집어넣어, 이 promise 함수들이 전부 끝나기 전까지 종료되지 않도록 하거나, await문을 적절히 사용하여 종료하지 않도록 하면 된다.

This post is licensed under CC BY 4.0 by the author.

REACT/CORS 개발 환경에서, 외부 API 와 연결할때 쿠키가 생성되지 않는 문제

Firebase/Function firestore, storage 쓰기, 읽기

Comments powered by Disqus.

diff --git a/posts/Gof/index.html b/posts/Gof/index.html new file mode 100644 index 000000000..f534af05e --- /dev/null +++ b/posts/Gof/index.html @@ -0,0 +1 @@ + GoF | 디피의 개발일지
Posts GoF
Post
Cancel

GoF

GoF(Gang of Four)

GoF에서는 23가지 디자인패턴을 3가지 유형으로 분류함.

  1. 생성 패턴(Creation pattern)
    • 객체를 생성하는데 관련된 패턴들
    • 객체가 생성되는 과정의 유연성을 높이고, 코드의 유지를 쉽게함
  2. 구조 패턴(Structural Pattern)
    • 프로그램 구조에 관련된 패턴들
    • 프로그램 내의 자료구조나 인터페이스 구조 등 프로그램의 구조를 설계하는데 활용할 수 있는 패턴들
  3. 행동 패턴(Behavioral Pattern)
    • 반복적으로 사용되는 객체들의 상호작용을 패턴화 해놓은 것들

img

주요 패턴

  • 생성 패턴
    • 클래스 : 팩토리 메서드 패턴
    • 객체 : 프로토타입 패턴, 싱글톤 패턴, 빌더 패턴
  • 구조 패턴
    • 객체 : 어댑터 패턴, 컴포지트 패턴, 데코레이터 패턴, 퍼사드패턴, 프록시 패턴
  • 행동 패턴
    • 객체 : 옵저버 패턴, 커맨드 패턴, 책임 연쇄 패턴, 중재자 패턴, 방문자 패턴
This post is licensed under CC BY 4.0 by the author.

운영체제 소개

CASE

Comments powered by Disqus.

diff --git a/posts/GraphQL-back/index.html b/posts/GraphQL-back/index.html new file mode 100644 index 000000000..3b122900c --- /dev/null +++ b/posts/GraphQL-back/index.html @@ -0,0 +1,139 @@ + Graph ql back | 디피의 개발일지
Posts Graph ql back
Post
Cancel

Graph ql back

Qraph QL 이 해결할 수 있는 문제

  • over-fetching : 요청한 것보다 많이 보내줌. ex) username만 필요한데, 프로필 사진도 보냄
  • under-fetching : 하나의 작업을 위해 여러 요청을 보내게 하는 것.

  • QraphQL에는 URL이 존재하지 않음.
    • 하나의 엔드포인트만 있고, 그곳으로 쿼리를 보내면, 쿼리에 맞춰서 알아서 데이터를 보내준다.
    • 즉, 정확히 요청한 데이터만 보내준다.

시작

https://www.npmjs.com/package/graphql-yoga

  • yarn add graphql-yoga

    • 시작할때, 타입스크립트로 시작을 하든, 바벨 설정을 해줘야한다.
  • 기본 구조

    1
    +2
    +3
    +4
    +5
    +
    import { GraphQLServer } from "graphql-yoga";
    +  
    +const server = new GraphQLServer({});
    +  
    +server.start(() => console.log("graph Ql server running"));
    +
    • 위 코드는 스키마가 정의되어있지 않기에 오류가 발생함.

Scheme

  • 무엇을 받고, 무엇을 줄지에 대한 설명.
  • Query : 정보를 받을 때 사용
  • Mutation : 데이터베이스 등에서 데이터를 바꿀때 사용.

구조

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+
// /graphql/resolvers.js
+const resolvers = {
+   Query: {
+      name: () => "seongil",
+   },
+};
+export default resolvers;
+
+
+// /graphql/schema.graphql
+type Query {
+   name: String!
+}
+
+// index.js
+...
+import resolvers from "./graphql/resolvers";
+
+const server = new GraphQLServer({
+   typeDefs: "graphql/schema.graphql",
+   resolvers: resolvers,
+});
+...
+

위와 같이 resolver와, Query를 선언해주고, index.js에서 server와 연동시켜주면 된다.

위와 같이 할 경우, 아래와 같이 Query를 보내면

1
+2
+3
+
Query {
+    name
+}
+

아래와 같이 오게 된다.

1
+2
+3
+4
+5
+
{
+  "data": {
+    "name": "seongil"
+  }
+}
+

구조 : 좀 더 자세히

schema를 다음과 같이 했다.

1
+2
+3
+
type Query {
+   name: String!
+}
+

이 의미는 name에 String 데이터를 반환하는데, !를 주어 required라고 표시하는 것이다.

resolver는 데이터를 어떻게 보내줄 것인지 자바스크립트로 작성하는 것이다.

그럼 객체를 보내고 싶을때는 어떻게 할까

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+
// /graphql/resolvers.js
+const seongil = {
+   name: "seongil",
+   age: 24,
+   gender: "male",
+};
+const resolvers = {
+   Query: {
+      name: () => seongil,
+   },
+};
+export default resolvers;
+
+
+// /graphql/schema.graphql
+type Person {
+   name: String!
+   age: Int!
+   gender: String!
+}
+
+type Query {
+   person: Person!
+}
+

이렇게 해주면 된다.

요청은 다음과 같이 보낸다. 객체 안의 요소를 하나하나 선택하여 받아올 수 있다.

1
+2
+3
+4
+5
+6
+
Query {
+    person {
+        name
+        age
+    }
+}
+
This post is licensed under CC BY 4.0 by the author.

3108 logo

Graph ql front

Comments powered by Disqus.

diff --git a/posts/GraphQL-front/index.html b/posts/GraphQL-front/index.html new file mode 100644 index 000000000..8ee6910f7 --- /dev/null +++ b/posts/GraphQL-front/index.html @@ -0,0 +1,197 @@ + Graph ql front | 디피의 개발일지
Posts Graph ql front
Post
Cancel

Graph ql front

설치

yarn add @apollo/react-hooks apollo-boost graphql

사용법

  • GraphQL API로 요청을 보낼때는, 요청문을 axios나 fetch를 사용하여 POST 메소드로 보내야함.

  • 하지만 Apollo를 사용하면, 위와같이 하지 않아도 된다.

    • 먼저, Apollo client를 만들어야함.

Apollo client 셋업

다음과 같이, apollo.js 파일을 생성하고, client를 만들어주자. uri에는 graphQL 서버의 주소가 들어가면 된다.

1
+2
+3
+4
+5
+6
+7
+8
+
// src/apollo.js
+import ApolloClient from "apollo-boost";
+
+const client = new ApolloClient({
+  uri: "http://localhost:4000/",
+});
+
+export default client;
+

그리고 다음과 같이 index.js에서 연결하자

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
// src/index.js
+...
+import client from "./apollo";
+import { ApolloProvider } from "@apollo/react-hooks";
+
+ReactDOM.render(
+   <ApolloProvider client={client}>
+      <App />
+   </ApolloProvider>,
+   document.getElementById("root")
+);
+

쿼리 작성 및 사용법

1
+2
+
import { useQuery } from "@apollo/client";
+import { gql } from "apollo-boost";
+

아래와 같이 쿼리문 작성

1
+2
+3
+4
+5
+6
+7
+8
+
const GET_MOVIES = gql`
+  {
+    movies {
+      id
+      medium_cover_image
+    }
+  }
+`;
+

아래와 같이 사용. useQuery에 쿼리문을 넣고, loading, error, data를 얻을 수 있다.

1
+2
+3
+4
+5
+6
+7
+
const { loading, error, data } = useQuery(GET_MOVIES);
+
+if (loading) {
+  return "loading...";
+} else if (data && data.movies) {
+  return data.movies.map((m) => <h1>{m.id}</h1>);
+}
+
  • paremeter가 있는 get

    아래와 같이 쿼리문 작성. paremeter가 없을 때는 그냥해도 되지만, 있을 때는 다음과 같이 query getMovie($id:Int!) 라는 이름으로 불러주자.

    이때, 꼭 graphQL 서버에 있는 이름으로 호출할 필요는 없다. 즉, 다음코드에서 getMovie 대신 get만 써도 된다는 것이다.

    타입만 잘 적어주자.

    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +
    const GET_MOVIE = gql`
    +  query getMovie($id: Int!) {
    +    movie(id: $id) {
    +      id
    +      title
    +      medium_cover_image
    +      description_intro
    +    }
    +  }
    +`;
    +

    호출 : 나머지는 똑같되, useQuery의 두번째 인자에 객체를 넣고, variable 필드에 파라미터를 넣어주자

    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +
    const Detail = () => {
    +  const { id } = useParams();
    +  const { loading, data } = useQuery(GET_MOVIE, {
    +    variables: { id: parseInt(id) },
    +  });
    +  
    +  if (loading) {
    +    return "loading";
    +  }
    +  if (data && data.movie) {
    +    return data.movie.title;
    +  }
    +};
    +

Apollo Cache

  • react apollo가 뭔가를 얻으면 그것을 저장하여 같은 데이터를 요청하면, 그대로 캐시에 있는 것을 보여준다.

  • 모든 걸 apollo client에서 자동으로 해준다.

Local State

  • Apollo cache에 있는 데이터를 client 에서 변경하여 사용하기

  • client에서 만든 데이터 가져오기

    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +
    // src/routes/Home.js
    +const GET_MOVIES = gql`
    +  {
    +    movies {
    +      id
    +      medium_cover_image
    +      isLiked @client
    +    }
    +  }
    +`;
    +
    • 이렇게 @client를 붙여줘야 apollo가 백엔드로 보내지 않고 client의 데이터임을 안다.
  • 데이터 넣기

    • 아래와 같이, resolvers 안에 넣고자하는 데이터 타입에 넣고자하는 필드를 추가하면 된다.
    1
    +2
    +3
    +4
    +5
    +
     resolvers: {
    +      Movie: {
    +         isLiked: () => false, // 디폴트로 false 값 지니도록.
    +      },
    +   },
    +
  • 데이터 수정하기

    • 아래와 같이, resolver 안에 Mutation 을 추가하고, 쿼리에서와 똑같이 작성하면 된다.
    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +
     resolvers: {
    +     ...
    +      Mutation: {
    +         likeMovie: (_, { id }, { cache }) => {
    +            cache.modify({
    +               id: `Movie:${id}`,
    +               fields: {
    +                  isLiked: (isLiked) => !isLiked,
    +               },
    +            });
    +         },
    +      },
    +   },
    +
    • 이때, cache.modify를 통해 변경한다.
    • cache.modify
      • id : 어느 데이터를 변경할 것인지 명시. apollo dev tool로 형식을 확인할 수 있다.
        • 보통 데이터 타입 + id 로 구성
      • fields : 변경하고자하는 필드와 변경 방식을 정의한다.
  • 데이터 변경 요청

    • 아래와 같이 @client만 붙이고 똑같이 하면 된다.
    • useMutation을 사용한다.
    • 이 hook으로 만들어진 함수를 호출하면, 요청이 날아간다.
    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +
    const LIKE_MOVIE = gql`
    +   mutation likeMovie($id: Int!) {
    +      likeMovie(id: $id) @client
    +   }
    +`;
    +...
    +const [likeMovie] = useMutation(LIKE_MOVIE, {
    +      variables: { id: parseInt(id) },
    +   });
    +...
    +<button onClick={likeMovie}>{isLiked ? "Unlike" : "Like"}</button>
    +
This post is licensed under CC BY 4.0 by the author.

Graph ql back

5213 과외맨

Comments powered by Disqus.

diff --git a/posts/How-I-Use-AI/index.html b/posts/How-I-Use-AI/index.html new file mode 100644 index 000000000..c21e0672a --- /dev/null +++ b/posts/How-I-Use-AI/index.html @@ -0,0 +1 @@ + How I Use "AI" | 디피의 개발일지
Posts How I Use "AI"
Post
Cancel

How I Use "AI"

원본

작성자가 언제 어떻게 LLM 을 활용하는지 공유하는 포스트

  • 애플리케이션을 만드는 초기 단계에서 전체적인 베이스를 만드는데 도움을 받았음
  • 새로운 기술의 튜터로써
  • 코드 단순화를 위해
  • 단조로운 작업을 빠르게 처리하기 위해
    • 불규칙적인 작업이더라도 LLM이 패턴을 파악하여 결과물을 내게할 수 있음
  • 자동화작업에 사용가능
This post is licensed under CC BY 4.0 by the author.

Esbuild to reduce build times by 90%

JS bundler, package manager, compiler

Comments powered by Disqus.

diff --git a/posts/JS-bundler,-package-manager,-compiler/index.html b/posts/JS-bundler,-package-manager,-compiler/index.html new file mode 100644 index 000000000..0c5a52b8b --- /dev/null +++ b/posts/JS-bundler,-package-manager,-compiler/index.html @@ -0,0 +1 @@ + JS bundler, package manager, compiler | 디피의 개발일지
Posts JS bundler, package manager, compiler
Post
Cancel

JS bundler, package manager, compiler

프론트엔드 관련 아티클을 읽다보면 다양한 bundler, package manger, compiler 도구에 대한 얘기가 나온다. 그때마다 익숙지않은 도구의 이름(pnpm, swc, vite 등)이 나오는데 뭐가 뭔지 잘 이해가 안간다. 따라서 각각의 역할이 무엇인지, 어떤 종류가 있고 어떤 장단이 있는지 간단히 정리해보려고 한다.

Bundler

여러개의 파일을 하나의 파일로 묶어주는 도구이다.

초기에 브라우저는 모듈 시스템을 지원하지 않았다. <script> 간 모듈을 내보낼 수 없었고, 전역 객체를 통해서만 서로를 참조할 수 있었다. 하지만 웹 생태계가 커지면서 점점 더 다양한 요구조건이 생기기 시작했다. (최적화, 모듈 시스템, 코드 호환성(polyfill), 전처리 등). 번들러는 이러한 요구사항에 대응하기 위해 태어났다.

현재 대표적인 번들러는 webpack, rollup, esbuild, vite 이다

webpack

  • 초창기에 나타난 번들러로, 지속적인 업데이트를 거쳐가며 많이 사용되어왔다
  • “통합”에 집중하여, 번들을 통합하여 관리하도록 하였다
  • 장점
    • 풍부한 plugin과 loader
    • 강력한 개발서버 (Hot module replacement 등)
    • Code splitting (파일을 여러 번들로 분리하여 병렬로 스크립트를 로드할 수 있도록 함)

rollup

  • module bundler
  • “확장”에 집중하여, 작은 코드조각들을 거대하고 복잡한 어플리케이션 혹은 라이브러리로 만들어준다고 소개한다.
  • 같은 소스코드를 다양한 환경에 맞춰 빌드할 수 있기에, 라이브러리를 만들때 큰 도움이 된다
  • vs webpack
    • webpack은 내부적으로 commonJS를 사용하고, rollup은 es6(+typescript)를 사용한다
    • 초기에 webpack은 commonJS로만 빌드가 가능했다. rollup은 ES6 모듈형태로 빌드할 수 있기에 이는 라이브러리를 만들때 큰 도움이 되었다.
    • webpack에 비해 좀 더 빠르다.
      • webpack은 모듈을 함수로 감싸서 평가하지만, rollup은 모듈들을 호이스팅하여 한번에 평가한다
    • 조금 더 가볍다
      • tree shaking은 기본적으로 ES6 코드에서 제대로 동작하기에
    • CommonJS 코드를 ES6 코드에서 사용할 수 있다

esbuild

  • Go를 기반으로 만들어진 번들러로 성능상 큰 강점이 있다. (JS 기반 번들러보다 10 ~ 100x)
  • 아직 메이저 버전이 릴리스 되진 않았지만, 번들러로서 필수적인 기능은 갖췄다
  • 하지만 설정이 webpack, rollup 만큼 유연하지 못하고 안정성 이슈가 있다

vite

  • esbuild의 단점을 보완한 라이브러리
  • vue.js 창시자가 만든 frontend build tool로, 두가지 포인트가 있다
    • native ES modules 기반의 강력한 개발서버
    • esbuild로 파일들을 통합하고 rollup을 통해 번들링
  • esbuild 로 성능을 높이고, rollup으로 번들링의 유연성을 챙겼다
  • 자세한 내용은 일전에 작성한 글이 있다
  • 키포인트
    • 개발 서버 구동시간이 0에 가까울 정도로 매우 빠름
    • 모든 CommonJS 및 UMD 파일을 ESM으로 불러올 수 있도록 변환함
    • 별도의 설정없이 다양한 리소스 import 가능
    • CSS 빌드 최적화
  • 주의할 점
    • 기본적으로 ES6 타겟으로 번들한다. 따라서 ES5 이하를 타겟으로 잡으려면 polyfill 추가가 필요하다. (또는 @vitejs/plugin-legacy 플러그인 사용)
    • 기본적으로 index.html이 빌드 시작점이고, 순수 JS 번들 생성을 위해서는 라이브러리 모드가 필요하다

Package manager

프로젝트에서 사용하는 패키지를 쉽게 설치, 업데이트해주는 도구이다. 대표적으로는 npm, yarn, pnpm이 있다

npm

  • JS 패키지 매니저의 시초이며 node.js에 내장되어 있기 때문에 별도의 설치가 필요없다
  • 장점 : 오랜 기간 사용되어왔기에 생태계가 풍부하다
  • 단점 : 과거에는 속도, 보안 측면에서 떨어진다는 평가가 있었으나 업데이트를 거치면서 개선되었다

yarn

  • npm과 큰 차이가 없고 프로세스도 거의 동일한 패키지매니저
  • 장점
    • npm 에 존재하던 보안문제를 해결하여 안전성을 보장하고 있다.
    • 패키지 설치를 병렬로 진행하여 속도가 빠르다

pnpm

  • “performant npm”의 약자로, 효율적인 npm을 표방한다
  • 장점
    • npm 에서 node_modules에 매번 패키지를 설치했던 것과 달리 global 저장소에 한번만 저장하여 여러 프로젝트에서 같은 패키지를 참조하는 식으로 저장공간을 절약할 수 있다
    • Ghost Dependency 문제를 해결한다

npx 는 뭘까?

npm을 좀 더 편리하게 사용할 수 있도록 도와주는 도구로, npm 레지스트리에서 원하는 패키지를 실행할 수 있는 도구이다.

패키지를 설치하지 않더라도 npm 레지스트리에 올라가있는 패키지의 최신버전을 실행 시키고, 자동으로 설치도 된다. 다음과 같은 경우 사용한다.

  • npm run-script 를 사용하지 않고 로컬에 설치된 패키지를 사용할 경우
  • 일회성 명령으로 실행할 경우
  • gist-based scripts를 실행할 경우
  • 특정 노드 버전의 스크립트를 실행할 경우

참고

  • https://bepyan.github.io/blog/2023/bundlers
  • https://velog.io/@sebinn/%ED%8C%A8%ED%82%A4%EC%A7%80-%EB%A7%A4%EB%8B%88%EC%A0%80-%EB%B9%84%EA%B5%90-npm-yarn-pnpm
  • https://engineering.ab180.co/stories/yarn-to-pnpm
This post is licensed under CC BY 4.0 by the author.

How I Use "AI"

-

Comments powered by Disqus.

diff --git "a/posts/MVC-\355\214\250\355\204\264/index.html" "b/posts/MVC-\355\214\250\355\204\264/index.html" new file mode 100644 index 000000000..663a35628 --- /dev/null +++ "b/posts/MVC-\355\214\250\355\204\264/index.html" @@ -0,0 +1 @@ + MVC 패턴 | 디피의 개발일지
Posts MVC 패턴
Post
Cancel

MVC 패턴

MVC 컴포넌트의 역할

Model

  • 컨트롤러가 호출했을때, 요청에 맞는 역할을 수행한다. 비즈니스 로직을 구현하는 영역으로 응용프로그램에서 데이터를 처리하는 부분이다.
  • DB에 연결하고 데이터를 추출하거나 CRUD 등의 작업을 수행한다.
  • 상태의 변화가 있을때, 컨트롤러와 뷰에 통보해 후속 조치 명령을 받을 수 있게 된다.
  • 뷰나 컨트롤러에 대해서 어떤 정보도 알지 말아야한다.

Controller

  • 클라이언트의 요청을 받았을 때, 그 요청에 대해 실제 업무를 수행하는 모델컴포넌트를 호출한다. 또한 클라이언트가 보낸 데이터가 있다면, 모델에 전달하기 쉽게 데이터를 가공한다.
  • 모델이 업무를 마치면 그 결과를 뷰에게 전달한다.
  • 모델이나 뷰에 대해 알고 있어야 하며, 둘의 변경을 모니터링해야한다.

View

  • 컨트롤러로부터 받은 모델의 결과값을 가지고, 사용자에게 출력할 화면을 만드는 일을 한다.
  • 만들어진 화면을 컨트롤러로 다시 보내고, 컨트롤러는 이를 웹 브라우저에 전송하여 웹 브라우저가 출력하게 한다.
  • 모델과 컨트롤러에 대해 몰라야한다.

MVC 구동 원리

image-20220117005529402

Client-Server 구조로, 요청을 하면 그에 맞는 응답을 하는 구조를 기본으로 하고 있다.

  1. 웹 브라우저가 웹 서버에 웹 어플리케이션 실행을 요청한다.
  2. 웹 서버는 들어온 요청을 처리할 수 있는 컨트롤러를 찾아 요청을 전달한다.
  3. 컨틀롤러는 모델의 메서드를 호출한다.
  4. 모델은 데이터를 가공하여 값 객체를 생성하거나, DB와의 인터랙션을 통해 값 객체를 생성한다.
  5. 업무 수행을 마친 결과 값을 컨트롤러에게 반환한다.
  6. 컨트롤러는 모델로부터 받은 결과값을 View에게 전달한다.
  7. View는 적절한 화면을 생성하고, 컨트롤러에게 전달한다.
  8. 컨트롤러는 뷰로부터 받은 화면을 웹서버에 전달한다.
  9. 웹 브라우저는 웹 서버의 응답을 받고 화면을 출력한다.
This post is licensed under CC BY 4.0 by the author.

TDD

Git

Comments powered by Disqus.

diff --git a/posts/MVVM-MVP/index.html b/posts/MVVM-MVP/index.html new file mode 100644 index 000000000..fd9c2b93d --- /dev/null +++ b/posts/MVVM-MVP/index.html @@ -0,0 +1,143 @@ + MVVM, MVP 패턴 | 디피의 개발일지
Posts MVVM, MVP 패턴
Post
Cancel

MVVM, MVP 패턴

MVVM

Model - View - View Model 의 약자로, 프로그램의 비지니스 로직과 프레젠테이션 로직을 UI로 명확하게 분리하는 패턴

img1.daumcdn

구성요소

  • Model : 데이터를 보관하고 있는 부분으로, 데이터를 불러오거나 업데이트하는 로직이 있음.
  • View Model : Model에 데이터를 요청하고 가공함. 비지니스 로직을 처리. View로부터 입력을 받아 적절한 처리를 함
  • View : UI 담당. UI에 연관된 로직만 수행한다. 필요한 데이터는 View Model과 data binding을 통해 얻는다. 사용자의 입력을 VM으로 전달.

특징

  • View와 ViewModel이 n : 1 관계이다. 하나의 View Model에 여러 View가 결합이 가능하여 View Model의 재사용성이 높다.

장점

  • 데이터를 관리하는 로직과 UI로직을 깔끔하게 분리하여 유지보수에 용이해짐.

  • 코드의 재사용성을 개선
  • UI 디자이너와 개발자가 쉽게 협업할 수 있는 디자인 패턴

단점

  • View Model의 설계가 쉽지 않다.

React와 MVVM 패턴

Model

  • Redux를 통해 구현할 수 있음
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+
class Model {
+    constructor() {
+      books = [
+        {id: 'RCB-123',name: "React Cook Book", isFavorite: false},
+        {id: 'VCB-123',name: "Vue Cook Book", isFavorite: false},
+        {id: 'ACB-123',name: "Angular Cook Book", isFavorite: false}
+      ];
+    }
+
+    getBooks() {
+        return this.books
+    }
+
+    toggleFavorite(bookId) {
+      const target = this.books.filter(item => item.id === bookId)[0];
+      target.isFavorite = !target.isFavorite
+    }
+}
+

View Model

  • React-hooks와 dispatch를 통해 구현할 수 있음
  • Model과 직접 상호 작용하며, View Model이 모델을 업데이트할 때마다, 모든 변경사항이 자동으로 View에 반영된다.
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+
class ViewModel {
+    constructor(bookStore) {
+        this.store = bookStore
+    }
+
+    getBooks() {
+        return this.store.getBooks()
+    }
+
+    toggleFavorite(bookId) {
+       this.store.toggleFavorite(bookId)
+    }
+}
+
+export default ViewModel
+

View

  • 비지니스 로직은 최대한 피하고, UI에 관련된 로직만 나타내도록 해야함.

    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +
    const ViewComponent = ({books, handleToggleFavorite, handleClickNeverView}) => {
    +  return (
    +    <div>
    +      <BooksList
    +        books={books}
    +        handleToggleFavorite={handleToggleFavorite}
    +        handleClickNeverView={handleClickNeverView}
    +       />
    +    </div>
    +  )
    +}
    +
  • View Controller

    • View Model에 대한 참조를 소유하고, View는 View Model을 인식하지 못하며 View controller에 의존하여 필요한 데이터 및 이벤트를 전달한다.
    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +14
    +15
    +16
    +17
    +18
    +19
    +
    const ViewControllerComponent = ({viewModel}) => {
    +    const [isNeverView, setIsNeverView] = useState(false);
    +
    +    const handleToggleFavorite = useCallback((bookId) => {
    +      viewModel.toggleFavorite(bookId)
    +    }, [viewModel]);
    +
    +    const handleClickNeverView = useCallback(() => {
    +      setIsNeverView(!isNeverView);
    +    }, [isNeverView]);
    +
    +    return (
    +      <ViewComponent
    +        books={viewModel.getBooks()}
    +        handleToggleFavorite={handleToggleFavorite}
    +        handleClickNeverView={handleClickNeverView}
    +      />
    +    )
    +}
    +
  • Provider

    • MVVM의 일부는 아니지만, 모든 것을 이어 붙이기 위한 요소이다.
    • View Controller로 View Model을 인스턴스화하고, 필요한 모든 종속성을 전달해주기 위한 요소
    • ViewModel의 인스턴스는 props를 통해 View Controller 구성요소로 전달 된다.
    • 공급자는 모든 것을 연결하는 것이 목적이기에 논리없이 깨끗해야한다.
    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +
    const BooksProvider = () => {
    +    const model = new Model()
    +    const [viewModel] = useState(new ViewModel(model))
    +  
    +    return (
    +    	<ViewControllerComponent viewModel={viewModel} />
    +    )
    +}
    +

MVP

Model - View - Prensenter

img1.daumcdn

구조

  • Model : MVVM과 동일
  • View : MVVM과 동일
  • Prensenter : View에서 요청한 정보로 Model을 가공하여 View에 전달해주는 부분. View와 Model을 붙여주는 접착제 같은 역할이다.

동작

  1. 사용자의 Action 들은 View를 통해 들어온다.
  2. View는 데이터를 presenter로 요청한다.
  3. presenter는 model에게 데이터를 요청한다.
  4. model은 prensenter에게 데이터를 응답한다.
  5. presenter는 view에게 데이터를 응답한다.
  6. view는 presenter가 응답한 데이터로 화면을 꾸린다.

특징

Presenter는 View와 Model의 인스턴스를 가지고 있어 둘을 연결하는 접착제 같은 역할을 한다.

또, Prensenter와 View는 1:1 관계이다.

장점

  • View와 Model 사이의 의존성이 없다.

단점

  • View와 Prensenter 사이의 의존성이 높다.

나는 어떻게 하고 있지?

MVC, MVVM, MVP 등의 디자인 패턴을 공부하면서 그동한 무지성으로 개발했던 내 과거 프로젝트가 과연 어떤 디자인패턴에 가까운지 궁금해져 한번 과거 프로젝트를 살펴보기로 했다.

이글을 작성하고 있는 시점에서의 나의 가장 최근 프로젝트인 yourlist-web-renewal 프로젝트의 디렉터리 구조는 다음과 같다.


  • components : 컴포넌트
    • elements : 공통으로 사용하는 작은 컴포넌트들
    • [컴포넌트명] - 주로 페이지 단위로 나뉨
      • index.tsx : 컴포넌트 최상단
      • container : 컴포넌트를 구성하는 작은 단위 컴포넌트들의 컨테이너
      • view : 컴포넌트를 구성하는 작은 단위 컴포넌트들의 뷰
      • elements : 컴포넌트 내에서 공통으로 자주 사용되는 작은 컴포넌트
  • modules
    • index.ts : 모듈 최상단
    • moduleTypes.ts : 모듈에서 공통으로 사용되는 타입들
    • [모듈명]
      • index.ts : 모듈 최상단. 리듀서 포함
      • saga.ts : 모듈의 리덕스 사가 관련 코드 모음
      • action.ts : 액션 타입, 액션 생성 함수 등 포함
      • types.ts : 모듈에서 사용되는 타입들

간단히 디자인 패턴과 관련이 있는 부분만 적었다.

먼저 modules를 살펴보면, 앱에 필요한 데이터를 가지고 있고, 각종 api와 직접 소통하여 데이터를 받아들이고, 간단한 가공을 담당하는 부분이니 Model 에 해당한다고 볼 수 있다.

그리고 각 컴포넌트의 최상단에 있는 index.tsx 는 모델과 소통하여 데이터를 받아들이고, 그 컴포넌트에서 사용되는 비지니스 로직을 담당한다. 따라서 이는 MVP의 Prensenter나 MVVM의 View Model에 해당한다고 볼 수 있다.

container 는 Model과 대화하지 않고, 자신이 담당하는 view의 이벤트를 처리해주고, 자신이 담당하는 부분에서의 Model과 연관이 없는 비지니스 로직을 처리한다. 따라서 이는 MVVM 구조의 View controller에 가깝다고 보인다.

이때 내 프로젝트에서 view는 container나 index로 데이터를 요청하지 않는다. 상태를 통해 container 또는 index로부터 데이터를 받아서 그것을 활용하여 UI를 구성하고, UI에 관련된 로직만을 포함하고 있다. 이는 MVVM 구조에서의 View에 좀더 가깝다.

따라서 내 프로젝트는 굳이 따지자면, MVVM 구조를 따르고 있다고 볼 수 있다.

하지만 MVVM의 패턴에서 벗어난 부분도 존재한다.

  1. index.tsx는 View Model과 Provieder를 동시에 겸하고 있다.
    • Model과 대화하며 각종 비지니스 로직을 처리하며(View Model), View Controller이나 View로 데이터를 보내준다(Provider)
    • 이는 각 컴포넌트 별로 하나의 거대한 View Model + Provider를 만들게 하여, View Model과 View과 1:n으로 연결되어, View Model을 재사용할 수 있다는 MVVM의 장점을 없앤다.
    • 또 view controller의 작업을 담당하고 있는 부분도 보인다.
  2. 부분부분적으로 index.tsx, container에서 ui를 담당한 부분이 존재한다
    • 예를 들어, index.tsx에서 간단한 wrapper를 둘러싸서 view를 조합하는 등의 View 작업을 조금 맡아 하고 있다.
    • 이는 view의 작업을 분산시키는 행위로, 디자인 변경사항이 있을 시 수정을 까다롭게 하고 있다.

개선 사항

무의식적으로 했던 패턴이 MVVM에 가장 가깝다는 것을 알고, 나에게 MVVM이 가장 잘 맞는다는 것을 알았으나, MVVM 패턴을 지키지 않은 부분도 존재하기에 아래와 같이 개선할 점을 뽑아 보았다.

  1. View Model을 분리
    • 재사용이 가능하도록 View Model을 index.tsx에서 분리시켜야한다.
  2. index.tsx는 Provider만 담당하도록
  3. View의 조합은 index.tsx에서 하더라도, wrapper는 왠만하면 view에서 담당하도록 하기

위와 같이 한다면 좀 더 구조적으로 안정된 프로젝트를 할 수 있을 것이라 생각된다.

하지만 아직 구체적으로 MVVM의 패턴이 실제 프로젝트에 어떻게 적용되는지는 잘 모르기에, 리팩토링을 할때 한번 MVVM 패턴이 지켜진 프로젝트를 참고해보는 것이 좋아보인다.

출처

https://velog.io/@dlrmsghks7/whatismvvmpattern

https://beomy.tistory.com/43

This post is licensed under CC BY 4.0 by the author.

Git

13144 List of Unique Numbers

Comments powered by Disqus.

diff --git "a/posts/Mysql-Count\354\235\230-\354\206\215\353\217\204/index.html" "b/posts/Mysql-Count\354\235\230-\354\206\215\353\217\204/index.html" new file mode 100644 index 000000000..e5844cf72 --- /dev/null +++ "b/posts/Mysql-Count\354\235\230-\354\206\215\353\217\204/index.html" @@ -0,0 +1 @@ + Mysql Count 속도 | 디피의 개발일지
Posts Mysql Count 속도
Post
Cancel

Mysql Count 속도

Mysql의 Select Count(*) 얼마나 빠를까?

사용하는 데이터베이스 엔진에 따라 다르다.

  • InnoDB : O(n)
  • MyISAM : O(1)

이유는 MyISAM의 경우 row가 몇 개 있는지 저장하고 있기에 Count(*) 쿼리가 들어오면 그 저장한 값을 바로 리턴하여 O(1)이 된다.


Count(*) vs Count(column) vs Count(*) Where

Count(*)이 가장 빠르다.

이유는 Count(*) 의 경우 내부적으로 값을 확인하지 않고, row의 개수만 확인한다.

하지만 column을 주면 내부적으로 값을 확인하여 null이 아닌 row만 리턴하기에 시간이 좀 더 걸리게 된다.

where 조건도 마찬가지다. 값을 확인한다음 조건에 맞는 것만 세기에 Count(*)보다 느리다.

이는 MyISAM 엔진에서도 마찬가지다. MyISAM 엔진에서 빠른 것은 SELECT COUNT(*)이고 Count(column)이나 Count(*) Where 은 InnoDB와 마찬가지로 값을 읽고 리턴하기에 시간이 오래 걸린다.


정리

 MyISAMInnoDB
Count(*)빠름보통
Count(col)느림느림
Count(*) Where느림느림

절대적으로 빠름, 느림을 의미하는게 아닌 상대적인 속도를 의미.


출처

https://stackoverflow.com/questions/5257973/mysql-complexity-of-select-count-from-mytable

https://m.blog.naver.com/pjt3591oo/221030483713

https://stackoverflow.com/questions/22352073/improve-innodb-count-performance

https://imnkj.tistory.com/49

This post is licensed under CC BY 4.0 by the author.

리액트 리팩토링

Open Graph(OG) 프로토콜

Comments powered by Disqus.

diff --git a/posts/RESTful-API/index.html b/posts/RESTful-API/index.html new file mode 100644 index 000000000..c59f61929 --- /dev/null +++ b/posts/RESTful-API/index.html @@ -0,0 +1 @@ + RESTful API | 디피의 개발일지
Posts RESTful API
Post
Cancel

RESTful API

REST란, REprensentational State Transfer의 약자이다. 따라서, RESTful API는 REST의 기본 원칙을 잘 지킨 API를 의미한다.

REST는 하나의 아키텍쳐로, Resource Oriented Architecture이다. API 설계의 중심에 자원(resource)가 있고, HTTP Method를 통해 자원을 처리하도록 설계하는 것이다.

www와 같은 분산 하이퍼 미디어 시스템을 위한 소프트웨어 아키텍처의 한 형식으로, 자원을 정의하고 자원에 대한 주소를 지정하는 방법 전반에 대한 패턴

REST 특징

  1. uniform interface
    • URI로 지정한 리소스에 대한 조작을 통일되고 한정적인 인터페이스로 수행하는 아키텍쳐 스타일
  2. Stateless
    • API 서버는 작업을 위한 상태정보를 따로 저장하지 않고, 들어온 요청을 단순히 처리하면 된다.
    • 때문에 서비스의 자유도가 높아지고 서버에서 불필요한 정보를 관리하지 않아도 된다.
  3. caching
    • HTTP라는 기존 웹표준을 그대로 사용하기에, 웹에서 사용하는 기존 인프라를 그대로 사용할 수 있다.
    • 따라서 HTTP가 가진 캐싱 기능을 적용할 수 있다.
    • HTTP 프로토콜 표준에서 사용하는 Last-Modified 태그나 E-Tag를 이용하면 캐싱 구현이 가능하다/
  4. client-server
    • REST 서버는 API를 제공하고, 클라이언트는 사용자 인증이나 컨텍스트(세선, 로그인정보) 등을 직접 관리하는 구조로, 각각의 역할이 확실히 구분되어있다.
    • 따라서 클라이언트와 서버에서 개발해야할 내용이 명확해지고, 서로간의 의존성이 줄어든다.
  5. hierarchical system
    • REST 서버는 다중 계층으로 구성될 수 있으며, 보안/로드밸런싱/암호화 계층을 추가해 구조상의 유연성을 둘 수 있고, Proxy, 게이트웨이 같은 네트워크 기반의 중간 매체를 사용할 수 있다.
  6. Self-descriptiveness
    • REST API 메시지만 보고도 이를 쉽게 이해할 수 있는 자체 표현구조로 되어있다.

RESTfull 하게 API를 디자인 한다는 것의 의미

  1. 리소스행위를 명시적이고 직관적으로 분리한다.

    • 리소스는 URI로 표현되는데, 리소스가 가리키는 것은 명사로 표현되어야한다.
    • 행위는 HTTP Method로 표현하고, GET, POST, PUT(전체 수정), PATCH(일부 수정), DELETE 가 있다.
  2. Message는 Header와 Body를 명확하게 분리해서 사용한다

    • entity에 대한 내용은 body에 담는다
    • 애플리케이션 서버가 행동할 판단의 근거가 되는 컨트롤 정보인 API 버전 정보, 응답받고자 하는 MIME 타입등은 header에 담는다.
    • header와 body는 http header와 http body로 나눌 수 있고, http body에 들어가는 json 구조로 분리할 수 있다.
  3. API 버전을 관리한다.

    • 환경은 항상 변하기때문에 API의 signature가 변경될 수도 있음에 유의하자.
    • 특정 API를 변경할 때는 반드시 하위호환성을 보장해야한다.
  4. 서버와 클라이언트가 같은 방식을 사용해서 요청하도록 한다.

    • 브라우저와 서버 둘 다 json으로 보내든, form-data 형식으로 보내든 하나로 통일한다.

    • 다른 말로 표현하자면, URI가 플랫폼 중립적이어야한다는 말이다.

장단점

장점

  • Open API를 제공하기 쉽다.
  • 멀티플랫폼 지원 및 연동이 용이하다.
  • 원하는 타입으로 데이터를 주고 받을 수 있다.
  • 기존 웹 인프라(HTTP)를 그대로 사용할 수 있다.

단점

  • 사용할 수 있는 메서드가 적다.
  • 분산환경에는 부적합하다.
  • HTTP 통신 모델에 대해서만 지원한다.

출처

https://meetup.toast.com/posts/92

This post is licensed under CC BY 4.0 by the author.
diff --git a/posts/React_cors_problem_under_dev_mod/index.html b/posts/React_cors_problem_under_dev_mod/index.html new file mode 100644 index 000000000..29e598d28 --- /dev/null +++ b/posts/React_cors_problem_under_dev_mod/index.html @@ -0,0 +1,35 @@ + REACT/CORS 개발 환경에서, 외부 API 와 연결할때 쿠키가 생성되지 않는 문제 | 디피의 개발일지
Posts REACT/CORS 개발 환경에서, 외부 API 와 연결할때 쿠키가 생성되지 않는 문제
Post
Cancel

REACT/CORS 개발 환경에서, 외부 API 와 연결할때 쿠키가 생성되지 않는 문제

이미 서버에 올라간 백엔드 API 와 로그인 작업을 하던 도중에, 백엔드에서 보내는 쿠키를 브라우저가 저장하지 않는 문제를 발견했다.

찾아보니 CORS 위반으로 생기지 않는 것이라고 하였다.

그럴때 해결방법은 두가지가 있다.

  1. package.json에 proxy 설정 추가
1
+2
+3
+4
+5
+6
+
//package.json
+{
+    ...
+    "proxy" : "백엔드 주소"
+    ...
+}
+

위처럼 하면, proxy에서 통신을 하는 것이라고 하여 CORS 를 위반하지 않고 제대로 쿠키를 저장할 수 있다.

  1. setupProxy.js

    npm install http-proxy-middleware

    설치후 src 밑에 setupProxy.js 파일을 생성하고 다음과 같이 코드 작성

    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +
    const { createProxyMiddleware } = require("http-proxy-middleware");
    +
    +module.exports = function (app) {
    +  app.use(
    +    createProxyMiddleware("/", {
    +      target: "백엔드 주소",
    +      changeOrigin: true,
    +    })
    +  );
    +};
    +
  • 주위점

    위처럼 한다음에 axios 를 호출할땐 다음과 같이 도메인을 제외한 주소를 적어야한다.

    1
    +
    await axios.get("/api/product");
    +

    모든 주소를 적으면, 다른 origin으로 인식하여 다시 쿠키가 생기지 않게 된다

This post is licensed under CC BY 4.0 by the author.

노드 스터디 7장

Firebase/Function Puppeteer 사용하기

Comments powered by Disqus.

diff --git a/posts/TDD/index.html b/posts/TDD/index.html new file mode 100644 index 000000000..c1795a8e8 --- /dev/null +++ b/posts/TDD/index.html @@ -0,0 +1 @@ + TDD | 디피의 개발일지
Posts TDD
Post
Cancel

TDD

TDD 란?

Test-Driven Development의 약자로, 테스트가 코드 작성을 주도하는 개발방식이며, 매우 짧은 개발 사이클의 반복에 의존하는 소프트웨어 개발 프로세스이다.

우선 개발자는 요구되는 새로운 기능에 대한 자동화된 테스트케이스를 작성하고, 해당 테스트를 통과하는 가장 간단한 코드를 작성한다. 일단 테스트를 통과하는 코드를 작성하고, 리팩토링하는 과정을 거치는 것이다.

장점

코드를 작성하기 전에 보다 요구사항에 집중

테스트를 작성하기 전에 개발자는 해당 기능의 요구사항과 명세를 분명히 이해하고 있어야한다. 이는 user-case나 user-story 등으로 이해할 수 있으며, 이는 개발자가 코드를 작성하기 전에 보다 요구사항에 집중할 수 있도록 도와준다. 이는 TDD가 주는 이점 중 하나이다.

기존의 기능이 잘 작동하는지 확인

새로운 기능을 추가하면, 잘 작동하던 기능이 제대로 동작하지 않을 때가 많다. 또 개발자가 미처 인지하지 못하는 경우도 있다. 이러한 경우를 방지하기 위해 테스트 코드를 작성하여, 새로운 기능을 추가할때 기존의 기능들이 잘 작동하는지 확인해 볼 수 있다.

더 빠른 리팩토링

TDD를 해왔다면 방대한 코드량을 안심하고 리팩토링할 수 있다. 리팩토링을 하면서 해당 기능이 오작동을 일으킨다면 테스트가 잡아줄 것이기때문이다. 결과적으로 리팩토링이 더 빨라지고, 코드의 퀄리티도 그만큼 향상하게 된다.

단점

코드의 생산성

요구되는 코드량은 분명히 늘어난다. 코드 퀄리티보다 빠른 생산성이 요구되는 시점에서 TDD는 큰 걸림돌이 될 수 있다.

테스트 코드 작성의 어려움

어떤 부분을 테스트해야할지, 어떻게 테스트해야할지, 어떤 테스트 프레임워크가 적합한지에 대한 학습이 필요하고, 익숙해지는데에도 시간이 걸린다. 개발팀 전원이 이에 익숙해져야하기에 이에 대한 코스트는 무시할 수 없다.

테스트를 작성할 수 없는 상황

다양한 요구사항과 예외 케이스를 맞추다보면, 테스트를 작성하는 것이 불가능하거나 매우 어려울 수 있다. 이때 실제 코드를 변경하여 테스트에 적합한 코드를 만드는 것은 주객전도인 상황이다.

따라서 모든 코드에 대해 테스트를 작성할 수는 없으며, 할 필요도 없다. 테스트코드를 작성한다고 버그가 발생하지 않는 것도 아니다. 또 TDD는 100% coverage와 100% 무결성을 주장하지 않는다.

출처

https://github.com/JaeYeopHan/Interview_Question_for_Beginner/tree/master/Development_common_sense#restful-api

This post is licensed under CC BY 4.0 by the author.

RESTful API

MVC 패턴

Comments powered by Disqus.

diff --git a/posts/UML/index.html b/posts/UML/index.html new file mode 100644 index 000000000..e94540c2b --- /dev/null +++ b/posts/UML/index.html @@ -0,0 +1 @@ + UML | 디피의 개발일지
Posts UML
Post
Cancel

UML

UML(Unified Modeling Language)

통합 모델링 언어를 사용하여 시스템 상호작용, 업무 흐름, 시스템 구조, 컴포넌트 관계 등을 그린 도면.

프로그래밍을 단순화 시켜 표현하여 의사소통 하기 좋고, 대규모 프로젝트에서는 로드맵을 만들거나 개발을 위한 시스템 구축의 기본을 마련한다.

요구사항 모델링에 사용되는 기법 중 하나


구성요소

  • 사물 (things)
    • 구조사물 : 시스템의 개념적, 물리적 요소를 표현. (ex : 클래스, usecase, 컴포넌트)
    • 행동사물 : 시간과 공간에 따른 요소들의 행위를 표현 (ex : 상호작용, 상태머신)
    • 그룹사물 : 요소들을 그룹으로 묶어서 표현 (ex : 패키지)
    • 주해사물 : 부가적인 설명이나 제약조건 등을 표현 (ex : 노트)
  • 관계 (relationship)
    • 연관관계
    • 집합관계
    • 포함관계
    • 일반화 관계
    • 의존관계
    • 실체화 단계
  • 다이어그램 (diagram)


다이어그램 종류

  1. Use Case 다이어그램 : 요구 분석 과정에서 시스템과 외부와의 상호작용을 묘사함.
  2. Activity 다이어그램 : 업무의 흐름을 모델링하거나 객체의 생명주기를 표현함
  3. Sequence 다이어그램 : 객체간의 메시지 전달을 시간적 흐름에서 분석함
  4. Collaboration(or communication) 다이어그램 : 객체와 객체가 주고받는 메세지 중심의 작성
  5. Class 다이어그램 : 시스템 내 클래스의 정적 구조를 표현하고 클래스와 클래스, 클래스의 속성 사이의 관계를 나타냄
  6. Component 다이어그램 : 소프트웨어 구조를 그림
  7. Deployment 다이어그램 : 기업 환경의 구성과 컴포넌트들 간의 관계를 그림


모델 종류

기능적 모델

사용자 측면에서 본 시스템 기능, UML에서는 Use Case 다이어그램을 사용한다.

정적 모델

객체, 속성, 연관관계, 오퍼레이션의 시스템 구조를 나타내며, UML에서는 Class 다이어그램을 사용한다.

동적 모델

시스템의 내부 동작을 말하며, UML에서는 Sequence, state, activity 다이어그램을 사용한다.


Use case 다이어그램

시스템과 외부와의 상호작용을 묘사. 프로젝트에 대한 요구사항을 정의하고, 세부기능을 분석하며 개발 범위를 정할때 작성함.

구성 요소

  • 시스템 : 만들고자하는 프로그램을 나타냄
  • 액터 : 시스템의 외부에 있고, 시스템과 상호작용하는 사람
  • use case : 사용자 입장에서 바라본 시스템의 기능.
  • 관계
    • 연관 : use case와 액터간의 상호작용이 있음을 표현
    • 포함 : 하나의 use case가 다른 use case의 실행을 전제로 할때 형성되는 관계
    • 확장 : 확장 대상 use case를 수행할 때, 특정 조건에 따라 확장 기능 use case를 수행하는 경우에 적용
    • 일반화 : 유사한 use case 또는 액터를 모아 추상화한 use case 또는 액터와 연결시켜 그룹을 만들어 이해도를 높이기 위한 관계


클래스 다이어그램

시스템에서 사용되는 객체타입을 정의함. 그들 간에 존재하는 정적인 관꼐를 다양한 방식으로 표현한 다이어그램.

구조

image-20220222012714142

이름, 속성, 연산으로 구성되어있음.

관계

  • 의존(dependancy) : 하나의 모델 요소가 다른 모델 요소를 사용하는 관계
  • 일반화(generalization) : 여러 클래스가 가진 공통적인 특징을 추출하여 공통적인 클래스를 일반화하는 관계
  • 연관(association) : 클래스로부터 생성된 인스턴스들 간의 관계
  • 집합 연관(aggregation) : 전체와 부분을 나타내는 모델 요소. 단, 전체와 부분은 서로 독립적
  • 복합 연관(composition) : 전체와 부분을 타나내는 모델 요소. 연관관계를 맺고 있는 클래스의 생명주기가 같음.


인터랙션 다이어그램

시스템이 수행하는 과정을 시각화하기 위한 다이어그램.

Use case 다이어그램의 actor와 개념 클래스 다이어그램의 객체 사이의 상호작용을 표현

종류

  • 시퀀스 다이어그램
  • communication 다이어그램


패키지 다이어그램

패키지는 클래스와 같은 여러 모델 요소들을 그룹화하여 표현하기 위한 수단이며, 패키지 다이어그램은 시스템의 서로 다른 패키지들 사이의 의존 관계를 표현하기 위한 다이어그램

구성 요소

  • 패키지 : 여러 클래스의 묶음
  • 의존관계 : 하나의 패키지가 다른 패키지를 사용하는 관계


상태 다이어그램

객체가 가질 수 있는 모든 가능한 상태를 표현하며, 특정 객체에 대하여 사건 발생에 따른 상태 천이 과정을 묘사한 다이어그램. 진입 및 탈출 조건, 상태 전이에 필요한 사건에 대한 표현이 가능하며, 설계 단계에서 객체의 동적인 행동을 표현하는데 주로 사용된다.

구성 요소

  • 상태
  • 시작상태
  • 종료상태
  • 전이


액티비티 다이어그램

객체의 로직이나 조건에 따른 처리 흐름을 순서에 따라 정의한 다이어그램. use case의 실체화, 알고리즘의 표현, 비지니스 프로세스의 정의 등에 사용이 가능함.

구성요소

  • 활동 : 행위나 작업 등 무언가를 하고 있는 상태
  • 시작 상태 : 여러 클래스가 가진 공통적인 특징을 추출하여 공통적인 클래스를 일반화하는 관계
  • 종료 상태 : 클래스로부터 생성된 인스턴스들 간의 관계
  • 선택점 : 전체와 부분을 나타내는 모델 요소, 전체와 부분은 서로 독립적
  • 전이 : 전체와 부분을 나타내는 모델요소, 연관 관계를 맺고 있는 클래스의 생명주기가 같음
  • 구분선 : 모듈의 수정없이 소프트웨어 확장을 위한 인터페이스의 실체화



출처

https://m.blog.naver.com/icbanq/221781238065

https://googry.tistory.com/2

This post is licensed under CC BY 4.0 by the author.

객체지향 분석론

럼바우 분석기법

Comments powered by Disqus.

diff --git a/posts/appdir/index.html b/posts/appdir/index.html new file mode 100644 index 000000000..d9bfa0da0 --- /dev/null +++ b/posts/appdir/index.html @@ -0,0 +1,251 @@ + next.js appDir | 디피의 개발일지
Posts next.js appDir
Post
Cancel

next.js appDir

AppDir

next.js에서 layout과 routing 경험을 개선시키고 React 최신 기술을 지원하기위해, next.js 13부터 지원하는 디렉터리 방식. 기존의 pages 디렉터리를 대체한다.

23년 04월 기준으로 아직은 베타버전으로 프로덕션에서 사용하는 건 추천하지 않는다고 한다. 하지만, 좋은 기능들이 많기에 학습해봐도 괜찮을 거 같다.

file conventions

app directory에서 파일 컨벤션은 다음과 같다.

  • page.js : 해당 path의 UI를 담당하며, public하게 접근할 수 있도록 함.
    • route.js : 서버 사이드 API endpoint
  • layout.js : route segment 간에 공유 가능한 UI. 자식 layout, page를 감쌈
    • template.js : layout 아래에서 자식 layout, page를 감쌈.
  • loading.js : 페이지를 로딩 중일 때 보여주는 UI. 페이지나 child segment를 React Suspense로 감싼다.
  • error.js : page나 child segment에서 에러가 발생했을 경우 보여줌. React Error Boundary 사용.
    • global-error.js : root layout에서 에러를 잡았을 때 보여주는 UI
  • not-found.js : 해당 route segment 아래에서 매치되는 route가 없을 때 보여주는 UI


Layout

next.js 13부터는 path가 바뀌더라도 리렌더링 없이 이전 레이아웃을 유지할 수 있다. 이때 상태도 유지된다.

사용법은 다음과 같다.

  1. app directory를 사용한다.
  2. 특정 페이지 아래에서 어떤 파일을 layout.js로 생성하면, 그것이 레이아웃 컴포넌트이다.
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+
// app/dashboard/layout.ts
+export default function DashboardLayout({
+  children, // will be a page or nested layout
+}: {
+  children: React.ReactNode,
+}) {
+  return (
+    <section>
+      {/* Include shared UI here e.g. a header or sidebar */}
+      <nav></nav>
+
+      {children}
+    </section>
+  );
+}
+

참고

  • 모든 route segment는 자신만의 layout을 가질 수 있다. 이 layout은 그 segment 아래의 모든 페이지에 적용된다.
  • layout은 디폴트로 nested된다. 각 부모 layout은 자식 layout을 감싼다.
  • Route Groups를 지정하여 특정한 route segment에만 layout을 공유할 수도 있다.
  • Layout은 디폴트로 Server Component이다. 다만 Client Component로 설정할 수 있다.
  • 부모 자식 layout 간에 데이터를 주고 받는건 불가능하다. 하지만, 같은 데이터를 fetch 하면 리액트에서 자동으로 요청을 중복제거한다.
  • layout.jspage.js를 같은 폴더에 생성할 수 있다. layout.jspage.js를 감싼다.

Root Layout (Required)

app directory의 최상단에 반드시 있어야함. 이 root layout을 통해 클라이언트에서 최초에 받는 HTML을 수정할 수 있다. (pages 디렉터리에서의 _document.js 같은 느낌)

참고

  • <html>, <body>를 반드시 가지고 있어야한다.
  • <head>를 수정하여 SEO를 지원할 수 있음.
  • Route Group을 사용하여 여러 root layout을 가질 수 있음.
  • root layout은 서버컴포넌트로, 클라이언트 컴포넌트로 설정할 수 없다.

template

layout과 같이 child layout, page를 감싸지만, routing에 거쳐 상태가 유지되지않는다. path가 변경되면 리렌더링된다. 사용하려면, route segment에 template.js로 생성하면 된다. 그럼 다음과 같이 layout 아래에서 자식 컴포넌트를 감싸도록 적용된다.

1
+2
+3
+4
+
<Layout>
+  {/* Note that the template is given a unique key. */}
+  <Template key={routeParam}>{children}</Template>
+</Layout>
+


Server component

app directory를 사용하면, 서버컴포넌트를 간편하게 사용할 수 있다. 서버컴포넌트를 사용하면, 클라이언트에게 최초로 보내지는 JS 코드를 줄일 수 있어 빠른 페이지로딩에 도움을 준다.

app directory 아래에 있는 모든 컴포넌트는 기본적으로 서버컴포넌트이다. 따라서 서버컴포넌트를 사용하기 위해 별다른 작업이 필요없다.

route가 next.js에서 로딩되면, 최초의 HTML은 서버에서 렌더링 된다. 그리고 그 HTML는 점진적으로 브라우저에 전달된다. 이때 next.js와 react client-side runtime을 비동기적으로 로딩한다. 이로써 클라이언트가 어플리케이션을 다루고, 인터랙션할 수 있게 된다.

서버컴포넌트를 사용하면 최초 페이지 로딩은 빨라지고, client-side runtime은 캐시될 수 있으며 사이즈를 예측할 수 있다. 그리고 이 최초로 내려지는 JS 번들 사이즈는 어플리케이션 크기가 커지는만큼 따라 커지지 않는다. 클라이언트 컴포넌트를 통해 어플리케이션에 추가된 client-side interactivity만큼만 증가하게 된다.

app directory에서 network boundary는 서버 컴포넌트냐, 클라이언트 컴포넌트냐에 달려있다. pages directory에서는 getStaticProps, getServerSideProps에 달려있다.

Client Component

next.js에서 클라이언트 컴포넌트는 서버에서 prerender 되고 클라이언트에서 hydration 된다. pages/ 디렉터리에서 동작하는 방식이다. app 디렉터리에서는 다음과 같이 파일 상단에 "use client";를 작성하면 클라이언트 컴포넌트가 된다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+
'use client';
+import { useState } from 'react';
+
+export default function Counter() {
+  const [count, setCount] = useState(0);
+
+  return (
+    <div>
+      <p>You clicked {count} times</p>
+      <button onClick={() => setCount(count + 1)}>Click me</button>
+    </div>
+  );
+}
+

이때 클라이언트 번들로 포함되는 컴포넌트들은 다음과 같다.

  • "use client";가 작성된 컴포넌트
  • "use client";가 작성된 컴포넌트에 import된 모든 컴포넌트들
  • 자식컴포넌트

따라서 클라이언트 컴포넌트를 사용할 때는 최대한 말단 컴포넌트에서 사용할 것을 추천하고 있다.

when to use server component vs client component?

next.js 팀에서는 서버 컴포넌트를 기본으로 사용하다가 클라이언트 컴포넌트는 필요할 때만 사용할 것을 추천한다. 그럼 언제 클라이언트 컴포넌트가 필요할까?

  • interactivity나 event listener(onClick(), onChange() 등)가 필요할 때.
  • lifecycle effect나 state가 필요할 때
  • browser-only API를 사용할 때
  • state, effect, browser-only API에 의존하는 custom hook을 사용할 때
  • React Class Component를 사용할 때

클라이언트 컴포넌트에서 서버컴포넌트 사용하기

리액트에서 클라이언트 컴포넌트안에 서버컴포넌트를 importing 하는 건 제약이 있다. 서버 컴포넌트는 server-only code 이기 때문이다. 따라서 다음과 같이 클라이언트 컴포넌트에선 서버 컴포넌트를 import할 수 없다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+
'use client';
+
+// ❌ This pattern will not work. You cannot import a Server
+// Component into a Client Component
+import ServerComponent from './ServerComponent';
+
+export default function ClientComponent() {
+  return (
+    <>
+      <ServerComponent />
+    </>
+  );
+}
+

하지만 클라이언트 컴포넌트에 props로 넘기는 것은 가능하다

1
+2
+3
+4
+5
+6
+7
+8
+9
+
'use client';
+
+export default function ClientComponent({children}) {
+  return (
+    <>
+      {children}
+    </>
+  );
+}
+
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+
// ✅ This pattern works. You can pass a Server Component
+// as a child or prop of a Client Component.
+import ClientComponent from "./ClientComponent";
+import ServerComponent from "./ServerComponent";
+
+// Pages are Server Components by default
+export default function Page() {
+  return (
+    <ClientComponent>
+      <ServerComponent />
+    </ClientComponent>
+  );
+}
+

이렇게 사용하면 리액트는 클라이언트 컴포넌트로 child를 넘기기 전에 <ServerComponent/>를 서버에서 렌더링해야한다는 것을 안다. 클라이언트 컴포넌트의 입장에서는 child 가 이미 렌더링 되어있다. layout이 이 방식으로 구현되어있다.

passing props from server to client component

서버 컴포넌트에서 클라이언트 컴포넌트로 props를 넘길 땐 serialization 될 필요가 있다. function, Date 등은 직접 클라이언트 컴포넌트로 넘길 수 없다는 것이다.

server-only code가 클라이언트 컴포넌트에서 실행되는 것을 막는 방법

server-only 패키지를 사용하여, 서버에서만 실행되어야하는 코드가 클라이언트 컴포넌트에서 사용되면 build-time error를 내도록 할 수 있다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
import "server-only";
+
+export async function getData() {
+  let resp = await fetch("https://external-service.com/data", {
+    headers: {
+      authorization: process.env.API_KEY,
+    },
+  });
+
+  return resp.json();
+}
+

마찬가지로 client-only 패키지도 제공한다. 이 패키지는 window 객체를 사용하는 함수와 같이 클라이언트에서만 사용되어야하는 함수에서 사용할 수 있다.

third party packages

현재 많은 서드파티 패키지들은 useState 등을 사용함에도 use client를 붙이지 않은 경우가 많다. 이러한 서드파티 패키지를 클라이언트 컴포넌트에서 사용하는 것은 큰 문제가 없다. 클라이언트 컴포넌트에 이미 use client가 붙어있기 때문이다. 하지만 서버 컴포넌트에서 사용할 땐 오류가 뜰 것이다. 따라서 서버 컴포넌트에서 사용할 경우엔 다음과 같이 서드파티 패키지를 감싸고 사용하면 된다.

1
+2
+3
+4
+5
+
'use client';
+
+import { AcmeCarousel } from 'acme-carousel';
+
+export default AcmeCarousel;
+
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+
import Carousel from './carousel';
+
+export default function Page() {
+  return (
+    <div>
+      <p>View pictures</p>
+
+      {/* 🟢 Works, since Carousel is a Client Component */}
+      <Carousel />
+    </div>
+  );
+}
+

data fetching

클라이언트 컴포넌트에서도 data feching은 할 수 있지만, 가능한 서버 컴포넌트에서 하는 것이 성능상 좋다.

context

클라이언트 컴포넌트에서는 context나 provider를 사용하여 context를 공유할 수 있다. 하지만, 서버 컴포넌트에서는 react state를 가지고 있지 않고, context는 보통 리렌더링할 때 필요하기 때문이다.

따라서 next.js 팀은 서버 컴포넌트 간에 데이터 교환을 위해 native javascript 패턴을 제안한다. 예를들어 만약 여러 서버 컴포넌트 사이에서 DB connection을 공유해야한다면 다음과 같이 사용할 수 있다.

1
+2
+
// utils/database.js
+export const db = new DatabaseConnection(...);
+
1
+2
+3
+4
+5
+6
+7
+
// app/users/layout.js
+import { db } from "@utils/database";
+
+export async function UsersLayout() {
+  let users = await db.query(...);
+  // ...
+}
+
1
+2
+3
+4
+5
+6
+7
+
// app/users/[id]/pages.js
+import { db } from "@utils/database";
+
+export async function DashboardPage() {
+  let user = await db.query(...);
+  // ...
+}
+

한편, 서버 컴포넌트에서 fetch 요청을 자동으로 중복제거된다. 따라서 fetch request result를 어떻게 공유할지는 걱정하지 않아도 된다. revalidate 등은 어떻게 하는지는 밑에 data fetching 섹션에서 다룬다.


Support for data fetching

fetch Web API는 React와 next.js에서 확장되었다.

  • React에서는 automatic request deduping을 위해 fetch를 확장하였다.
  • next.js에서는 options를 확장하여 caching/revalidating 규칙을 설정하였다.

이를 통해 next.js에서는 하나의 API로 SSG, SSR, ISR을 사용가능하다. (fetch에 대해서)

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+
// This request should be cached until manually invalidated.
+// Similar to `getStaticProps`.
+// `force-cache` is the default and can be omitted.
+fetch(URL, { cache: 'force-cache' });
+
+// This request should be refetched on every request.
+// Similar to `getServerSideProps`.
+fetch(URL, { cache: 'no-store' });
+
+// This request should be cached with a lifetime of 10 seconds.
+// Similar to `getStaticProps` with the `revalidate` option.
+fetch(URL, { next: { revalidate: 10 } });
+

next.js에서는 웬만하면 서버 컴포넌트에서 data fetching 수행을 권장한다. 다음과 같은 이점이 있기 때문이다.

  • 데이터 리소스로의 직접 접근
  • API key와 같은 민감한 정보의 노출 예방
  • data fetching과 render가 같은 환경에서 이루어짐. 따라서 클라이언트와 서버 사이의 통신이 줄어든다.
  • multiple data fetching을 하나의 round-trip으로 실행한다. 클라이언트에서 개별적으로 이루어지는 것과 다르다.
  • 클라이언트-서버 waterfall을 줄임
  • 서버가 데이터 리소스에 가까이 있다면 성능 향상의 효과가 있음.

클라이언트 컴포넌트에서도 data fetching은 가능하지만, 그때는 SWR나 React Query 사용을 추천하고 있다. 향후 React의 use() hook을 사용할 수도 있다고 한다.

component-level data fetching

컴포넌트에서 data fetching을 수행할때 두가지 모델이 있다.

  • parallel : route 내 request가 동시에 이루어진다. 이는 client-server waterfall을 줄이며 데이터 로딩에 걸리는 총 시간을 줄인다.
  • sequential : route 내 request가 순차적으로 이루어진다. 하나의 요청이 다른 요청에 의존적일때 사용할 수 있다.

sequential-parallel-data-fetching

또한 위에 언급했듯이 fetch()은 자동으로 deduping 된다. 만약 트리내 여러 컴포넌트에서 같은 데이터를 원한다면, next.js는 자동으로 fetch request(GET)를 캐싱하고 같은 요청이 발생하면 저장했던 것을 돌려준다.

  • 서버에서는 렌더링 프로세스가 종료될때까지 캐시가 유지된다.
    • 이는 Layout, Page, Server Component, generateMetadata, generateStaticParams에서 작성된 fetch 요청에 적용된다.
    • static generation 동안에도 적용된다.
  • 클라이언트에서는 세션 동안 캐시가 유지된다.

만약 fetch() 를 사용할 수 없는 상황이라면 cache function을 사용해도 된다.

static and dynamic data fetches

두가지 타입의 데이터가 존재한다.

  • static data : 자주 변경되지 않는 데이터
  • dynamic data : 자주 변경되거나 유저 별로 다른 데이터

기본적으로 next.js에서는 static fetch를 한다. 이는 빌드타임에 fetch되어 캐시되고 각 요청마다 사용한다는 말이다. 하지만 dynamic fetch를 해야할 때도 있는데, next.js는 캐시를 무효화하거나 revalidating 하는 방법을 제공한다.

  • 캐시 무효화
1
+
fetch(URL, { cache: 'no-store' });
+
  • revalidating

    • background : time interval을 주어 주기적으로 다시 fetch

      1
      +
      fetch(URL, { next: { revalidate: 10 } });
      +
    • on-demand : update가 있을 때마다 refetch (예제)

next.js에서는 StreamingSuspense에 대한 지원도 잘 되어있다. Layout 섹션에서 보았듯이 화면의 일부는 즉시 내려보내고, 일부는 비동기적으로 loading 화면을 보여주었다가 data fetching이 완료되면 내려보낸다. 이를 통해 유저는 페이지의 모든 부분을 로드할 필요가 없다.

caching data

The Next.js Cache is a persistent HTTP cache that can be globally distributed. This means the cache can scale automatically and be shared across multiple regions depending on your platform (e.g. Vercel).

next.js는 fetch()의 options 객체를 확장하여 서버에서 이루어지는 각 요청이 persistent caching 동작이 이루어지도록 설정할 수 있다. 이를 통해 데이터를 사용하는 어플리케이션 코드에서 캐싱 동작을 설정할 수 있다.


출처

https://beta.nextjs.org/docs/routing/fundamentals

This post is licensed under CC BY 4.0 by the author.

성능 최적화를 위한 next.js case study

Typescript 5.0

Comments powered by Disqus.

diff --git a/posts/architecture/index.html b/posts/architecture/index.html new file mode 100644 index 000000000..f30d562df --- /dev/null +++ b/posts/architecture/index.html @@ -0,0 +1,99 @@ + singleton | 디피의 개발일지
Posts singleton
Post
Cancel

singleton

Singleton 패턴

애플리케이션에서 인스턴스를 하나만 만들어 사용하기 위한 패턴이다.

커넥션 풀, 스레드 풀, 디바이스 설정 객체 등의 경우, 인스턴스를 여러 개 만들게하면 자원을 낭비하게 되거나 버그를 발생시킬 수 있으므로 오직 하나만 생성하고 그 인스턴스를 사용하도록 하는 것이 목적이다.


구현

하나의 인스턴스만을 유지하기 위해 인스턴스 생성에 특별한 제약을 걸어둬야한다.

  1. new를 실행할 수 없도록 생성자에 private 접근 제어자를 지정
  2. 유일한 단일 객체를 반환할 수 있도록 정적 메소드를 지원.
  3. 유일한 단일 객체를 참조할 정적 참조변수 필요
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+
public class Singleton {
+    private static Singleton singletonObject;
+
+    private Singleton() {}
+
+    public static Singleton getInstance() {
+        if (singletonObject == null) {
+            singletonObject = new Singleton();
+        }
+        return singletonObject;
+    }
+}
+

위 코드는 멀티 스레딩환경에서 싱글턴 패턴을 적용할떄 문제가 발생한다. 동시에 접근하다가 하나만 생성되어야하는 인스턴스가 두개 생성될 수 있기때문이다. 따라서 getInstance() 메소드를 동기화 시켜야 한다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+
public class Singleton {
+    private static Singleton singletonObject;
+
+    private Singleton() {}
+
+    public static synchronized Singleton getInstance() {
+        if (singletonObject == null) {
+            singletonObject = new Singleton();
+        }
+        return singletonObject;
+    }
+}
+

좀 더 효율적으로 하려면, DCL(double checking locking)을 써서 getInstance() 에서 동기화 되는 영역을 줄일 수 있다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+
public class Singleton {
+    private static volatile Singleton singletonObject;
+
+    private Singleton() {}
+
+    public static Singleton getInstance() {
+        if (singletonObject == null) {
+            synchronized (Singleton.class) {
+                if(singletonObject == null) {
+                    singletonObject = new Singleton();
+                }
+            }
+        }
+        return singletonObject;
+    }
+}
+

이때 위 코드는, 멀티코어 환경에서 하나의 CPU를 제외하고는 다른 CPU가 lock이 걸리게 된다. 따라서 다른 방법이 필요하다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+
public class Singleton {
+    private static volatile Singleton singletonObject = new Singleton();
+
+    private Singleton() {}
+
+    public static Singleton getSingletonObject() {
+        return singletonObject;
+    }
+}
+

volatile : 컴파일러가 특정변수에 대해 옵티마이져가 캐싱을 적용하지 못하도록 하는 키워드이다.

This post is licensed under CC BY 4.0 by the author.

데이터베이스 개요

알고리즘 개요

Comments powered by Disqus.

diff --git a/posts/arrowfunction/index.html b/posts/arrowfunction/index.html new file mode 100644 index 000000000..417b22a4f --- /dev/null +++ b/posts/arrowfunction/index.html @@ -0,0 +1,79 @@ + Arrow function | 디피의 개발일지
Posts Arrow function
Post
Cancel

Arrow function

화살표함수

기존의 function 표현방식보다 간결하게 함수를 표현할 수 있다. 화살표함수는 항상 익명이며, 자신의 this, arguments, super, new.target을 바인딩하지 않는다. 따라서 생성자로는 사용할 수 없다.

  • 화살표함수 도입 영향 : 짧은 함수, 상위 스코프 this


짧은 함수

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
var materials = ["Hydrogen", "Helium", "Lithium", "Beryllium"];
+
+materials.map(function (material) {
+  return material.length;
+}); // [8, 6, 7, 9]
+
+materials.map((material) => {
+  return material.length;
+}); // [8, 6, 7, 9]
+
+materials.map(({ length }) => length); // [8, 6, 7, 9]
+

상위스코프 this

1
+2
+3
+4
+5
+6
+7
+8
+9
+
function Person() {
+  this.age = 0;
+
+  setInterval(() => {
+    this.age++; // |this|는 person 객체를 참조
+  }, 1000);
+}
+
+var p = new Person();
+

일반 함수에서 this는 전역객체를 this로 정의한다. 하지만 화살표 함수 this는 상위 스코프의 this와 동일한 값을 갖는다. 따라서 위 예제에서 setInterval로 전달된 this는 Person의 this를 가리키며, Person 객체의 age에 접근한다.


화살표함수를 사용하면 안되는 순간

메소드

1
+2
+3
+4
+5
+6
+
const cat = {
+  name: 'meow';
+  callName: () => console.log(this.name);
+}
+
+cat.callName();	// undefined
+

이 경우 화살표함수의 상위 스코프는 전역 this가 된다. 따라서 메소드로 화살표함수를 사용하면 안된다.

메소드의 경우, 호출한 객체의 this로 바인딩되니 일반 함수를 사용하자

생성자

1
+2
+
const Foo = () => {};
+const foo = new Foo(); // TypeError: Foo is not a constructor
+

화살표함수에는 prototype이 없기에 생성자로 사용할 수는 없다.

addEventListener()의 콜백함수

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
const button = document.getElementById("myButton");
+
+button.addEventListener("click", () => {
+  console.log(this); // Window
+  this.innerHTML = "clicked";
+});
+
+button.addEventListener("click", function () {
+  console.log(this); // button 엘리먼트
+  this.innerHTML = "clicked";
+});
+

addEventListener의 콜백함수에서 this 는 이벤트리스너가 호출된 엘리먼트로 바인딩된다.

하지만 화살표함수를 사용할 경우 상위스코프로 바인딩되므로, 전역 this가 사용되게 된다.



출처

https://github.com/JaeYeopHan/Interview_Question_for_Beginner/tree/master/JavaScript#promise

https://velog.io/@padoling/JavaScript-%ED%99%94%EC%82%B4%ED%91%9C-%ED%95%A8%EC%88%98%EC%99%80-this-%EB%B0%94%EC%9D%B8%EB%94%A9

This post is licensed under CC BY 4.0 by the author.

Promise, async/await

운영체제 소개

Comments powered by Disqus.

diff --git a/posts/babel/index.html b/posts/babel/index.html new file mode 100644 index 000000000..1707b8711 --- /dev/null +++ b/posts/babel/index.html @@ -0,0 +1,61 @@ + 바벨 | 디피의 개발일지
Posts 바벨
Post
Cancel

바벨

바벨

바벨이란?

자바스크립트 트랜스파일러.

보통 모던 자바스크립트를 호환성을 위해 예전 문법으로 변환할때 사용한다.

Typescript와 JSX 코드를 변환할 때도 사용한다.


바벨 설정 파일 예시

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+
{
+  "presets": [
+    [
+      "@babel/env",
+      {
+        "targets": {
+          "chrome": "79",  // 예시) 크롬 79까지 지원하는 코드를 만든다.
+          "edge": "17",
+          "firefox": "60",
+          "safari": "11.1",
+        },
+        "useBuiltIns": "usage",
+      }
+    ]
+  ]
+}
+

Plugin/Preset

  • Plugin : 규칙 하나하나를 적용할 때 사용
  • Preset : 여러 개의 규칙을 한번에 적용할 때 사용
    • @babel/preset-env : ES6 이상 문법을 ES5 문법의 코드로 변환해주는 규칙을 정의함.
    • @babel/preset-react : 리액트 코드 변환


Polyfill

babel을 사용해도 변환할 수 없는 모던자바스크립트 코드들이 있다. 바로 Promise와 같은 것들인데, 이를 변환하기 위해선 Polyfill을 사용하여야한다.

바벨에서 설정하여도 되고, 웹팩에서 설정해도 된다.

바벨에서 사용할때는 @babel/plugin-transform-runtime 사용(@babel/polyfill은 deprecated)

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+
{
+  "plugins": [
+    [
+      "@babel/plugin-transform-runtime",
+      {
+        "absoluteRuntime": false,
+        "corejs": 3,
+        "helpers": true,
+        "regenerator": true,
+        "useESModules": false
+      }
+    ]
+  ]
+}
+


출처

https://www.daleseo.com/js-babel/

https://ingg.dev/babel/

https://okchangwon.tistory.com/3

This post is licensed under CC BY 4.0 by the author.

선언형프로그래밍

useEffect 올바른 사용

Comments powered by Disqus.

diff --git a/posts/bfcache/index.html b/posts/bfcache/index.html new file mode 100644 index 000000000..ac94a5a27 --- /dev/null +++ b/posts/bfcache/index.html @@ -0,0 +1,3 @@ + bfcache | 디피의 개발일지
Posts bfcache
Post
Cancel

bfcache

BFCache

  • 사용자가 브라우저 내에서 뒤로가기/앞으로가기를 할 때 이전 페이지를 자바스크립트 heap 영역까지 전체 캐싱하여 메모리에 저장하는 것
  • HTTP 캐시는 리소스만을 캐시하고 JS 작업까지 캐시하지 않기에 BFCache보다 빠르지 않다.


BFCache 작동을 판단할 수 있는 API

pageshow, pagehide

pageshow

  • 페이지가 처음 로드 될 때, bfcache에서 페이지가 복원될 때마다 load 이벤트 직후 트리거 된다.
  • event.persisted === true이면 BFCache에서 복구된 것이다.

pagehide

  • 페이지에서 나가거나, 브라우저가 페이지를 bfcache에 저장하려고 할 때 트리거 된다.
  • event.persisted === false이면 bfcache에 저장되지 않았다는 뜻이다.
  • true라고 항상 bfcache에 저장되지 않는다. (브라우저가 저장하려고 했다는 알수 있음.)

freeze / resume

크롬에서 지원되는 이벤트

freeze

  • pagehide 이벤트의 persisted 가 true일 경우, pagehide 이벤트 다음에 호출됨.
  • bfcache에 저장하려고 한 의도를 알 수 있음.

resume

  • BFCache로 들어가고 나올 때 호출되는 이벤트 (이외 상황에서도 호출됨 ex : CPU 사용량 최소화를 위해 백그라운드 탭이 동결된 경우)
  • bfcache에서 페이지가 복원 될 때, 정지된 배경 탭을 다시 방문할 때도 실행


BFCache를 위한 페이지 최적화 방법

  1. unload 이벤트 사용하지 않기
    • unload 이벤트는 페이지가 더이상 존재하지 않을 거라는 가정하여 운영됨.
    • 따라서 unload 이벤트 사용시 BFCache는 사용되지 않음. 대신 pagehide 이벤트 사용하기
  2. window.opener 참조를 피하라
    • rel="noopener"를 사용하지 않고 열면 새로운 페이지는 window.opener를 가지고 있고, 이 opener를 가지고 있는 페이지는 bfcache에 넣지 않는다.
    • Tabnabbing 보안 취약점 공격과도 연관된 부분이기 때문이다. (Tabnabbing 공격)
  3. navigate 전에 항상 open된 connection 닫기.
    • setTimeout, promise 함수가 진행중일때 bfcache에 삽입하면, 작업을 일시정지하고 다시 페이지에 돌아왔을 때 진행중인 작업을 재개한다.
    • 스케쥴된 작업이 단지 DOM APIdㅔ 접근하는 등 해당 페이지에만 영향을 주는 API라면 일시 정지 후 재개가 문제되지 않는다.
    • 하지만 same-origin의 다른 페이지에서도 액세스할 수 있는 API의 경우 작업이 일시 중지되었을 때, 다른 탭의 코드가 실행되지 않을 수도 있기 때문에 문제가 발생한다.
    • 따라서 많은 브라우저에서는 connection이 open되어있을 경우 bfcache에 해당 페이지를 넣지 않는다.
      • IndexedDB 트랜잭션 중일때
      • fetch(), XMLHttpRequest 중일때
      • WebSocket, WebRTC 연결이 열려있을 때.
    • 최적화를 위해선, pagehide나 freeze 이벤트에서 항상 connection을 닫고, observer를 제거하거나 연결을 끊는게 좋다. 물론 다시 돌아왔을 때 연결을 맺어줘야한다.


bfcache 비활성화하기

아래와 같이 Cache-Control 속성을 설정하면 된다.

1
+
Cache-Control: no-store
+

물론 다른 캐시들도 동작하지 않는다.


BFCache 문제점

  • 성능측정을 불분명하게 함 : bfcache에서 페이지가 로드되면 매우 빠르게 로드되기에 성능 측정 시 잘못된 결과값을 내게할 수 있다.
  • 집계를 내는 것이 힘듬 : bfcache로 다시 방문했을때 페이지 접속 집계가 안될 수도 있음.

두 경우 모두 pageshow 이벤트를 사용하여 정상적으로 수행하도록 해야한다.


출처

https://blog.naver.com/PostView.nhn?blogId=qls0147&logNo=222157982248&categoryNo=0&parentCategoryNo=0&viewDate=&currentPage=1&postListTopCurrentPage=1&from=postView

This post is licensed under CC BY 4.0 by the author.

beforeunload vs pagehide

딥링크

Comments powered by Disqus.

diff --git a/posts/blackbox-whitebox/index.html b/posts/blackbox-whitebox/index.html new file mode 100644 index 000000000..94c0c706c --- /dev/null +++ b/posts/blackbox-whitebox/index.html @@ -0,0 +1 @@ + 블랙박스/화이트박스 테스트 | 디피의 개발일지
Posts 블랙박스/화이트박스 테스트
Post
Cancel

블랙박스/화이트박스 테스트

블랙박스 테스트

소프트웨어의 내부 구조나 작동 원리는 모르는 상태에서 동작을 검사하는 방식

기법

  • 동등 분할 기법 : 프로그램 입력 도메인을 테스트 케이스가 산출될 수 있는 데이터 클래스로 분류하는 방법
  • 경계값 분석 기법 : 입력 조건의 중간 값보다 경계값에서 에러가 발생될 확률이 높으므로
  • 오류 예측 기법 : 놓치기 쉬운 오류들을 감각 및 경험으로 찾아보는 방법
  • 원인 결과 그래프 기법 : 입력 데이터 간 관계가 출력에 미치는 영향을 그래프로 표현하여 오류를 발견
  • 의사결정 테이블 테스팅 : 논리적 조건이나 상황에서 입력 조건과 결과를 참, 거짓으로 표현하여 조합을 만들고, 테스트 케이스를 작성
  • 상태 전이 테스팅 : 시스템에 반영되는 이전의 상태가 무엇인지 상태간 전이, 상태를 변화시키는 이벤트와 입력값을 파악


화이트박스 테스트

내부 동작을 알고 디테일하게 검사

기법

  • 문장 검증 : 프로그램의 모든 문장이 적어도 한번씩 수행되는 검증 기준
  • 선택 검증 : 선택하는 부분만 검증
  • 경로 검증 : 수행가능한 모든 경로 검사
  • 조건 검증 : if 문장이나 while 문장내 조건식을 조사하는 기준


출처

https://www.crocus.co.kr/1681

This post is licensed under CC BY 4.0 by the author.

전위식/후위식

스키마

Comments powered by Disqus.

diff --git a/posts/case/index.html b/posts/case/index.html new file mode 100644 index 000000000..6498519f3 --- /dev/null +++ b/posts/case/index.html @@ -0,0 +1 @@ + CASE | 디피의 개발일지
Posts CASE
Post
Cancel

CASE

CASE(Computer Aided Software Engineering)

소프트웨어 개발 시 사용되는 분석 자동화 도구. 소프트웨어 개발 과정의 일부나 전체를 자동화하는 도구이다.

CAD 기기와 유사한 것이라고 생각하면 됨.

  • 요구분석 -> 설계 -> 구현 -> 검사 및 디버깅 과정을 CASE를 활용하여 자동화함


CASE의 장점

  • 개발 속도가 빨라짐.
  • 하나의 tool을 활용함으로써, 표준화된 개발 환경을 구축할 수 있음. 따라서 커뮤니케이션이 용이해짐.
  • 오류 수정이 쉬워지고, 이로인해 소프트웨어의 품질 향상이 기대된다.


CASE의 기능

  • 그래픽 지원
  • 소프트웨어 생명주기 전반적인 단계의 연결
  • 다양한 소프트웨어 개발 모형을 지원


상위 CASE

계획과 분석 설계 단계를 지원. 다이어그램 그리기, 명세서 작성 등의 기능을 제공

  • 모델 사이 모순 검사 기능
  • 모델의 오류 검증 기능
  • 자료 흐름도 작성 기능


정보저장소

CASE 환경의 여러 도구들이 상호연관 되어 동작할 수 있도록 도와주는 구심체 역할을 하는 데이터 저장소. 저장소가 없으면 각 기능간의 데이터 연결이 안되고, 따라서 그때그때 데이터를 다시 입력해줘야함.

  • 각 도구들과 생명주기 활동, 사용자들, 응용소프트웨어들 사이의 통신과 소프트웨어 시스템의 정보 공유를 향상시킴.
  • 시스템의 유지보수가 용이해짐.



출처

https://velog.io/@kipsong/%EC%A0%95%EB%B3%B4%EC%B2%98%EB%A6%AC%EA%B8%B0%EC%82%AC-CASEComputer-Aided-Software-Engineering

This post is licensed under CC BY 4.0 by the author.

GoF

객체지향 분석론

Comments powered by Disqus.

diff --git a/posts/closure/index.html b/posts/closure/index.html new file mode 100644 index 000000000..d526eae2b --- /dev/null +++ b/posts/closure/index.html @@ -0,0 +1,23 @@ + closure | 디피의 개발일지
Posts closure
Post
Cancel

closure

Closure

두개의 함수로 만들어진 환경으로 이루어진 특별한 객체의 한 종류이다. 여기서 환경이란, 클로저가 생성될 때 그 범위에 있던 여러 지역변수들이 포함된 context를 말한다. 이 클로저를 통해서 자바스크립트에는 없는 private 속성/메소드, public 속성/메소드를 구현할 수 있다.


클로저 생성하기

다음은 클로저가 생성되는 조건이다.

  1. 내부 함수가 익명 함수로 되어 외부 함수의 반환값으로 사용된다.
  2. 내부 함수는 외부 함수의 실행 환경(execution environment)에서 실행된다.
  3. 내부 함수에서 사용되는 변수 x 는 외부 함수의 변수 스코프에 있다.
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
var name = `Warning`;
+function outer() {
+  var name = `closure`;
+  return function inner() {
+    console.log(name);
+  };
+}
+
+var callFunc = outer();
+callFunc();
+// console> closure
+

위 코드에서 callFunc을 클로저라고 한다. callFunc호출에 의해 name이라는 값이 console 에 찍히는데, 찍히는 값은 Warning이 아니라 closure라는 값이다. 즉, outer 함수의 context 에 속해있는 변수를 참조하는 것이다. 여기서 outer함수의 지역변수로 존재하는 name변수를 free variable(자유변수)라고 한다.

이렇게 외부함수가 내부함수를 반환할때, 내부함수에서 사용하는 변수도 함께 묶어 반환한다.

이처럼 외부함수 호출이 종료되더라도, 외부 함수의 지역변수 및 변수 스코프 객체의 체인 관계를 유지할 수 있는 구조를 클로저라고 한다. 보다 정확히는 외부 함수에 의해 반환되는 내부함수를 가리키는 말이다.


클로저 장단점

  • 장점 : 함수가 본인의 실행 환경을 기억한다는 것에서 많은 응용이 가능하다. 대표적으로 react의 state가 클로저를 통해 구현되었다.
  • 단점 : 의도하지 않았을 경우엔 프로그램 동작이 이상해질 수 있음. 의미없이 클로저를 생성하면 메모리가 낭비된다.


출처

https://github.com/JaeYeopHan/Interview_Question_for_Beginner/tree/master/JavaScript

This post is licensed under CC BY 4.0 by the author.

hoisting

this에 대해서

Comments powered by Disqus.

diff --git a/posts/cs-os/index.html b/posts/cs-os/index.html new file mode 100644 index 000000000..f2c6aa4af --- /dev/null +++ b/posts/cs-os/index.html @@ -0,0 +1 @@ + 운영체제 소개 | 디피의 개발일지
Posts 운영체제 소개
Post
Cancel

운영체제 소개

운영체제 정의

컴퓨터 시스템의 4가지 요소

  1. 유저
  2. 어플리케이션
  3. OS
  4. 하드웨어


운영체제

  • 유저 관점 : 어플리케이션을 수행함. 컴퓨터 사용을 편리하게 해줌
  • 시스템 관점 : 자원할당자. 어플리케이션과 i/o 장치의 수행을 다루는 프로그램 컨트롤러


커널

OS에 속하며, 컴퓨터에서 항상 수행되는 프로그램.

하드웨어의 자원을 자원이 필요한 프로세스에게 나눠주고, 프로세스 제어, 메모리 제어, 시스템 콜 등을 수행하는 부분이다.



컴퓨터 시스템 수행

컴퓨터 시스템 조직

cpu, disk(buffer), memory, usb(buffer), graphic adapter(buffer)

  • 각 I/O 장치들은, 각 장치 내부의 디바이스 컨트롤러가 있고, 이 디바이스 컨트롤러가 각 장치를 직접 다룸.

  • 또 로컬버퍼를 가지고 있으며 이것은 시스템 버스에 연결돼있음

  • 디바이스 컨트롤러는 데이터를 디바이스에서 로컬 버퍼로, 또 로컬버퍼에서 디바이스로 옮김

  • cpu는 메인 메모리를 통해 각 디바이스의 로컬 버퍼로 데이터를 보내거나 받음.

  • cpu와 각 I/O 디바이스는 각자의 작업을 독립적으로 수행함.

  • 디바이스 컨트롤러는 인터럽트를 통해 CPU에게 작업의 끝을 알림.


Interrupt handling

키보드를 치는 작은 하나하나의 작업도 인터럽트이다. CPU는 인터럽트가 올때마다 하던일을 멈추고, ISR로 인터럽트를 처리함.

  • ISR : Interrupt service routine
    • 디스크 인터럽트가 발생
    • 벡터 테이블(주소테이블)로 이동하고, 해당하는 ISR이 위치한 곳의 주소를 참조하여 이동함.
    • ISR을 수행하고 다시 원래 장소로 이동하여 다음 명령을 수행함.



This post is licensed under CC BY 4.0 by the author.

Arrow function

GoF

Comments powered by Disqus.

diff --git "a/posts/css-\354\265\234\354\240\201\355\231\224/index.html" "b/posts/css-\354\265\234\354\240\201\355\231\224/index.html" new file mode 100644 index 000000000..355056ed8 --- /dev/null +++ "b/posts/css-\354\265\234\354\240\201\355\231\224/index.html" @@ -0,0 +1,25 @@ + CSS 최적화 | 디피의 개발일지
Posts CSS 최적화
Post
Cancel

CSS 최적화

CSS 최적화가 필요한 이유

CSS는 렌더링을 막는다.

CSS의 존재만으로도, CSS가 파싱되기 전까지 브라우저는 렌더링이 지연된다. 만약 브라우저가 CSS가 없는 페이지를 그대로 노출하면, 스타일적용이 되지않은 페이지가 나타나기 때문이다. 이를 FOUC라고 불린다.


CSS는 HTML 파싱도 막을 수 있다

브라우저가 CSS가 파싱되기 전까지 콘텐츠를 보여주지 않더라고, HTML의 로딩된 부분만을 일단 보여줄 수 있다. 그러나 스크립트의 경우 async defer 가 없다면 파싱을 막게 된다. 스크립트는 잠재적으로 페이지를 조작할 여자기 있기때문에 브라우저는 스크립트 실행에 매우 주의를 기울이기 때문이다.

https://web-now-rbviiass9-calibreapp.vercel.app/_next/image?url=%2Fimages%2Fblog%2Fcss-performance%2Fparser-blocking-script.png&w=1920&q=75

스크립트가 페이지의 스타일에 영향을 줄 수 있기 때문에, 만약 브라우저가 CSS 관련 작업을 진행중이라면, 이 작업이 완료될 때까지 기다렸다가 스크립트를 실행할 것이다. 스크립트가 실행되기 전까지 문서 파싱을 할 수 없기 때문에, CSS는 더이상 렌더링을 차단하는 요소로 작용하지 않는다. 문서의 외부 스타일시트 및 스크립트 순서에 따라서 때로는 HTML 파싱도 중지할 수 있다.

https://web-now-rbviiass9-calibreapp.vercel.app/_next/image?url=%2Fimages%2Fblog%2Fcss-performance%2Fparser-blocking-css.png&w=1920&q=75

따라서 파싱이 차단되는 상황을 피하기 위해서는 CSS를 최대한 빨리 불러와야하고, 리소스를 최적의 순서로 불러와야한다.



CSS 최적화

CSS 사이즈 최소화

  • CSS minify

  • 사용하지 않는 CSS 제거

  • CSS 우선순위 정하기

    • critical css 를 인라인으로 처리하여, CSS 로딩하는 시간을 아끼기
    • CriticalCSS와 같은 자동화 툴이 있음
  • non-critical CSS는 비동기로 불러오기

    1
    +2
    +3
    +4
    +5
    +6
    +
    <link
    +  rel="stylesheet"
    +  href="non-critical.css"
    +  media="print"
    +  onload="this.media='all'"
    +/>
    +
    • media="print"는 사용자가 페이지를 프린트하려고 하는 경우에만 불러오는 스타일시트로, 렌더링에는 영향을 미치지 않는다.

    • onload="this.media='all'"는 스타일시트 로드가 완료되면 미디어 속성을 다시 all로 바꾸면서 스타일시트가 적용되도록 하는 것이다.


효과적인 CSS 애니메이션

페이지에 애니메이션이 있는 요소가 있는 경우, 브라우저는 종종 문서 내 요소의 위치와 크기를 재계산한다. 예를 들어 어떤 요소의 너비를 바꾸게 되면, 그 자식 요소들까지 영향을 미치면서 페이지 내부에서 큰 레이아웃 변경이 발생할 수 있다. 이 레이아웃의 크기가 커질 수록 성능에 안좋은 영향을 미칠 것이다.

  • height width 대신 transform: scale() 사용하기
  • top right bottom left 대신 transform :translate()를 사용한다.
  • 배경에 blur를 먹이고 싶다면, opacity 대신 blur된 이미지를 불러오는 게 낫다.

좀더 자세한 설명

CSS가 적용되는 4가지 순서

  1. Styles

    브라우저가 객체에 적용할 스타일의 값을 계산, 재계산한다.

  2. Layout

    positiondisplayoverflowwidthheight
    min-widthmin-heightpaddingmarginborder
    topbottomleftrightfont
    white-spaceline-heightvertical-alignfloatclear
  3. Paint

    colorbackgroundvisibilitytext-decoration
    border-radiusborder-stylebox-shadow 
  4. Composite

    transform, opacity


위 순서에 따르면, width나 height 등 Layout 속성을 바꾸면, Paint, Composite 순서를 거치기 때문에 성능 저하가 이루어진다. 이것을 Reflow라고 한다. 또한 Paint속성을 바꾸면 Composite 를 거치기에 성능저하가 이루어지고, 이것을 Repaint라고 한다.

따라서 성능을 최대로 올리려면 Composite속성만 바꾸는 것이 최고의 방법이다.


GPU 가속

GPU 가속을 이용하여 애니메이션 프레임을 최대로 끌어올릴 수 있다. will-change 옵션을 사용하여 GPU 가속을 사용할 수 있다. 브라우저에게 예상되는 변화에 대한 힌트를 미리 제공하여 실제 요소가 변화되기 전에 브라우저가 적절하게 최적화할 수 있도록 하는 방법이다. 하지만 과도하게 사용할 경우 많은 기기 자원을 소모한다. 따라서 적절히 사용한다.

1
+2
+3
+4
+5
+6
+
.app-menu {
+	-webkit-transform: translateX(-100%);
+		transform: translateX(-100%);
+	transition: transform 300ms linear;
+	will-change: transform;
+}
+


HTML 최적화하기

이벤트가 끝나면, 해당 이벤트를 위한 html class는 제거해줘야한다. 클래스를 추가하는 것은 자식요소의 DOM Tree를 변경하는 성능저하를 유발하기 때문이다.


contain 속성

contain은 브라우저에 요소와 하위 요소가 그 문서 트리와 무관한 것으로 간주된다는 것을 알려주는 속성이다. 페이지의 하위트리와 나머지 페이를 분리한다. 그런다음, 브라우저는 페이지에서 독립된 부분의 렌더링을 최적화하여 성능을 향상 시킬 수 있다.

contain은 페이지 내부에서 독립적으로 작동하는 위젝 등에서 효과적이다. 위젯 내부의 변경사항이 바깥으로 전파되는 것을 막을 수 있다.


variable font로 폰트파일 크기 줄이기

Variable Font는 모든 너비, weight, style에 대해서 별도의 폰트파일이 아니라 하나의 파일에 여러가지 다양한 폰트를 통합해줄 수 있도록 한다. CSS와 단일 @font-face 참조로 지정된 글꼴의 다양한 변형에 액세스할 수 있다.

이는 글꼴의 여러 변형이 필요한 파일 크기를 획기적으로 줄일 수 있다. 일반, 볼드, 기울임꼴 폰트 버전을 각각 로딩하는 대신 모든 정보가 포함된 하나의 단일 파일을 로딩할 수 있다.


CSS 선택자 속도는 걱정안해도 된다.

브라우저는 셀렉터는 오른쪽에서 왼쪽으로 읽기에 자식에서부터 부모로 거쳐서 올라가게 된다. 예를들어 nav a {}는 먼저 a를 찾고, 그 중 부모가 nav 인것을 찾는다. 따라서 선택자는 짧은게 더 빠르다.

하지만, 브라우저가 선택자를 매칭시키는 속도는 매우 빠르기에 걱정할 필요가 없다.


출처

https://yceffort.kr/2021/03/improve-css-performance

This post is licensed under CC BY 4.0 by the author.

클래스형 컴포넌트 vs 함수형 컴포넌트

코테대비 SQL 문법

Comments powered by Disqus.

diff --git "a/posts/css\354\204\240\355\203\235\354\236\220/index.html" "b/posts/css\354\204\240\355\203\235\354\236\220/index.html" new file mode 100644 index 000000000..3ac18a704 --- /dev/null +++ "b/posts/css\354\204\240\355\203\235\354\236\220/index.html" @@ -0,0 +1,303 @@ + css 선택자 | 디피의 개발일지
Posts css 선택자
Post
Cancel

css 선택자

*

아무 표시 없이 사용하면 페이지에 있는 모든 요소가 대상

1
+2
+3
+4
+
* {
+    margin : 0;
+    padding : 0;
+}
+

특정 요소의 모든 자식 요소에 적용할 수도 있음

1
+2
+3
+
#container * {
+    border : 1px solid black;
+}
+

주의점 : 남발할 시 성능저하를 불러일으킴

#X

id를 대상으로 삼음

1
+2
+3
+4
+
#container {
+    width : 960px;
+    margin : auto;
+}
+

주의점 : id는 유연성이 부족하기 때문에, 꼭 id를 사용해야하는지 자문하자.

  • 따라서 찾기 어려운 요소에만 id를 사용하자

.X

class 선택자

id와는 다르게 여러개의 요소를 대상으로 정할 수 있음

1
+2
+3
+
.error {
+    color : red;
+}
+

X Y

X요소의 하위 요소중 모든 Y요소에 적용

1
+2
+3
+
li a {
+    text-decoration : none;
+}
+

X

페이지에 있는 모든 X요소에 적용

1
+2
+3
+4
+5
+6
+
a {
+    color : red;
+}
+ul {
+    margin-left:0;
+}
+
  • :link - 클릭하기 전 a 태그의 가상클래스
  • :visited - 클릭 한 이후 a태크긔 가상클래스
1
+2
+3
+4
+5
+6
+
a:link {
+    color:red;
+}
+a:visited {
+    color : purple;
+}
+

X + Y

X 요소 바로 뒤에 있는 요소만 선택함

1
+2
+3
+
ul + p {
+    color:red;
+}
+

X > Y

X 요소의 직계 자식만을 선택함

1
+2
+3
+
div#container > ul {
+   border : 1px solid black;
+}
+

X ~ Y

형제 선택자로, X 요소의 아래에 있는 모든 Y 요소에 적용됨

1
+2
+3
+
ul ~ p {
+    color : red;
+}
+

X[title]

X 요소 중 title 속성이 있는 태그만 선택함

1
+2
+3
+
a[title] {
+    color : green;
+}
+

X[bar=”foo”]

X 요소의 bar 속성의 값이 “foo” 인 태그만 선택

1
+2
+3
+
a[href="https://naver.com"] {
+    color : #1f6053;
+}
+

X[bar*=”foo”]

X 요소의 bar 속성에 “foo”가 있는 태그를 선택

1
+2
+3
+
a[href*="naver"] {
+    color : #1f6053;
+}
+

ex) https://naver.com, https:www.naver.com, http://www.naver.com 모두에 적용됨

X[bar^=”foo”]

bar 속성이 “foo”로 시작할시 적용

1
+2
+3
+4
+
a[href^="http"] {
+   background: url(path/to/external/icon.png) no-repeat;
+   padding-left: 10px;
+}
+

X[bar$=”foo”]

bar 속성의 값이 “foo” 로 끝날 시 적용

1
+2
+3
+
a[href$=".jpg"] {
+   color: red;
+}
+

X[foo~=”bar”]

foo의 띄어쓰기로 구분되는 속성값 중 “bar”가 있으면 적용

1
+2
+3
+4
+5
+6
+
a[data-info~="external"] {
+    color:red;
+}
+a[data-info~="image"] {
+    border:1px solid black;
+}
+

ex)

1
+2
+3
+4
+5
+6
+
// 두 개 다 적용
+<a href="path/to/image.jpg" data-info="external image"> Click Me, Fool </a>
+// external만 적용
+<a href="path/to/image.jpg" data-info="external"> Click Me, Fool </a>
+// image만 적용
+<a href="path/to/image.jpg" data-info="image"> Click Me, Fool </a>
+

X:checked

라디오버튼이나 체크박스처럼 체크되는 사용자 인터페이스 요소만을 대상으로 함

1
+2
+3
+
input[type=radio]:checked {
+    border: 1px solid black;
+}
+

X::before X::after

  • ::before - 실제 내용 바로 앞에서 생성되는 자식요소
  • ::after - 실제 내용 바로 뒤에서 생성되는 자식요소

이때 content 속성값을 통해 특정 내용을 출력할 수도 있다.

3

X:hover

사용자가 요소 위에 마우스를 올렸을때 적용

1
+2
+3
+
div:hover {
+    background : #e3e3e3;
+}
+

X:not(선택자)

모든 X에 적용하고 싶은데, (선택자)로 지정된 것만 빼고 싶을때 사용

1
+2
+3
+4
+5
+6
+7
+
div:not(#container) {
+    color : blue;
+}
+
+*:not(p) {
+    color:green;
+}
+

X::가상요소

첫번째 줄이나 첫 글자에 스타일을 적용할때 사용가능.

반드시 블록 레벨 요소에 적용해야한다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
p::first-line {
+    font-weight :bold;
+    font-size:1.2em;
+}
+p::first-letter {
+    float:left;
+    font-size:2em;
+    font-weight:bold;
+    font-family : cursive;
+    padding-right:2px;
+}
+

::after, ::before 도 가상요소이다.

모든 가상요소 목록

X:nth-child(n)

X의 n번째 자식에만 적용

1
+2
+3
+
li:nth-child(3) {
+    color:red;
+}
+

X:nth-last-child(n)

X의 마지막 n번째 자식에만 적용

1
+2
+3
+
li:nth-last-child(2) {
+    color:red;
+}
+

X:nth-of-type(n)

X와 같은 요소의 n번째 형제를 선택한다.

1
+2
+3
+
li:nth-of-type(3) {
+    border:1px solid black;
+}
+

X:nth-last-of-type(n)

X와 같은 요소의 형제 중 마지막 n번째를 선택함

1
+2
+3
+
ul:nth-last-of-type(3) {
+    border: 1px solid black;
+}
+

X:first-child, X:last-child

부모 요소의 첫번째/마지막 자식에만 적용 가능

1
+2
+3
+4
+5
+6
+
ul li:first-child {
+    border-top : none;
+}
+ul > li:last-child {
+    border-top : none;
+}
+

X:only-child

부모 요소의 단 하나의 자식요소를 지정할 수 있다.

만약 부모에 여러 X가 있을땐 적용이 되지 않음

1
+2
+3
+
div p:only-child {
+    color :red
+}
+
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+
<div>
+    <p>
+        적용됨
+    </p>
+</div>
+<div>
+   <p>
+       적용안됨
+    </p>
+    <p>
+        적용안됨
+    </p>
+</div>
+

X:only-of-type

부모 컨테이너에 형제 요소가 없는 요소를 대상으로 함

1
+2
+3
+4
+5
+6
+
li:only-of-type {
+    font-weight:bold;
+}
+ul > li:only-of-type {
+    font-weight:bold;
+}
+

X:first-of-type

해당 type의 첫번째 형제 선택자를 선택함

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+
<div>
+   <p> My paragraph here. </p>
+   <ul>
+      <li> List Item 1 </li>
+      <li> List Item 2 </li>
+   </ul>
+
+   <ul>
+      <li> List Item 3 </li>
+      <li> List Item 4 </li>
+   </ul>   
+</div>
+

위 html에서 List Item 2 에만 스타일을 적용하는 방법은 아래와 같이 3가지가 있음

1
+2
+3
+4
+5
+6
+7
+8
+9
+
ul:first-of-type > li:nth-child(2) {
+   font-weight: bold;
+}
+p + ul li:last-child {
+   font-weight: bold;
+}
+ul:first-of-type li:nth-last-child(1) {
+   font-weight: bold;   
+}
+

출처

https://code.tutsplus.com/ko/tutorials/the-30-css-selectors-you-must-memorize–net-16048

This post is licensed under CC BY 4.0 by the author.

redux-saga concepts

좋은 코드란 무엇인가?

Comments powered by Disqus.

diff --git a/posts/data/index.html b/posts/data/index.html new file mode 100644 index 000000000..b4fbf7a65 --- /dev/null +++ b/posts/data/index.html @@ -0,0 +1 @@ + 관계 데이터 모델 | 디피의 개발일지
Posts 관계 데이터 모델
Post
Cancel

관계 데이터 모델

관계 데이터 모델

용어

  • 릴레이션 : 하나의 개체에 관한 데이터를 2차원 테이블로 저장한 것.

    • 릴레이션 스키마 : 릴레이션의 이름과 속성이름으로 정의. 정적. 내포
    • 릴레이션 인스턴스 : 릴레이션에 존재하는 튜플들의 집합. 동적. 외연
  • 도메인 : 하나의 속성이 가질 수 있는 모든 값의 집합. 적합성 판단의 기준이 됨


데이터베이스의 구성

  • 데이터베이스 스키마 : 데이터베이스의 전체구조. 릴레이션 스키마의 모음

  • 데이터베이스 인스턴스 : 데이터베이스를 구성하는 릴레이션 인스턴스의 모음


릴레이션의 특성

  • 튜플의 유일성
  • 튜플의 무순서
  • 속성의 무순서
  • 속성의 원자성


  • 슈퍼키 : 유일성을 만족하는 속성들의 집합
  • 후보키 : 유일성과 최소성을 만족하는 속성들의 집합
  • 외래키 : 기본키로 사용할 수 있음.
    • 참조하는 기본키 속성과 이름은 달라도 되지만 도메인은 같아야한다.
    • null 값을 가질 수 있다.
    • 한 릴레이션에 여러 개의 외래키가 있어도 된다.
    • 같은 릴레이션의 기본키를 참조할 수도 있다.


무결성 제약조건

데이터의 무결성을 보장하기 위한 규칙

  • 개체 무결성 제약조건 : 기본키를 구성하는 모든 속성은 널 값을 가질 수 없다

  • 참조 무결성 제악조건 : 외래키는 참조할 수 없는 값을 가질 수 없다.

This post is licensed under CC BY 4.0 by the author.

반정규화

서브네팅

Comments powered by Disqus.

diff --git "a/posts/database-\352\260\234\354\232\224/index.html" "b/posts/database-\352\260\234\354\232\224/index.html" new file mode 100644 index 000000000..82f16b3f1 --- /dev/null +++ "b/posts/database-\352\260\234\354\232\224/index.html" @@ -0,0 +1 @@ + 데이터베이스 개요 | 디피의 개발일지
Posts 데이터베이스 개요
Post
Cancel

데이터베이스 개요

데이터베이스

파일시스템의 문제점

  • 앱과 상호 연동되어있기 때문에 다음과 같은 문제가 발생함.
    • 데이터 종속성
    • 데이터 중복성
    • 데이터 무결성


데이터베이스 특징

  1. 데이터 독립성
    • 물리적 독립성 : 데이터베이스를 수정하더라도, 응용 프로그램을 수정할 필요는 없음
    • 논리적 독립성 : 하나의 논리적인 구조로 다양한 응용 프로그램의 논리적 요구를 만족시켜줄 수 있음.
  2. 데이터의 무결성
    • 여러 경로를 통해 잘못된 데이터가 발생하는 것을 방지하기 위해, 데이터 유효성 검사를 통해 데이터 무결성을 구현함
  3. 데이터 보안성
    • 인가된 사용자들만 접근할 수 있도록 계정 관리 또는 접근 권한을 설정함으로써 보안을 구현할 수 있음.
  4. 데이터 일관성
    • 연관된 정보를 논리적인 구조로 관리함으로써 어떤 하나의 데이터만 변경했을 경우 발생할 수 있는 데이터 불일치성을 배제할 수 있음.
  5. 데이터 중복 최소화
    • 데이터를 통합하여 관리함으로써, 중복된 데이터를 최소화할 수 있다.


데이터베이스 성능

데이터베이스 성능 이슈는 디스크 I/O를 줄이는 것에서 시작함.

디스크 I/O는 디스크 헤더의 이동을 최소화해야 빨라짐. 따라서 순차 I/O가 랜덤 I/O보다 빠름.

따라서 데이터베이스 쿼리 튜닝은 랜덤 I/O 자체를 줄여주는 것이 목적



정규화

정규화를 하는 이유

다음과 같은 이상현상을 해결하기 위해

  • 삽입이상
  • 삭제이상
  • 수정이상


정규화란?

관계형 데이터베이스에서 중복을 최소화하기 위해 데이터를 구조화하는 작업이다. 나쁜 릴레이션을 좋은 작은 릴레이션으로 분해하는 작업이다.

정규화 조건

  • 분해되는 집합 D는 무손실 조인을 보장해야한다.
  • 분해 집합 D는 함수적 종속성을 보존해야하낟.


제 1 정규형

각 속성이 오직 원자값만을 포함하도록 하는 것.

제 2 정규형

모든 비주요 애트리뷰트들이 주요 애트리뷰트에 대해서 완전 함수적 종속이면 제 2 정규형을 만족한다고 볼 수 있다. 완전 함수적 종속이란 X -> Y 라고 가정했을 때, X 의 어떠한 애트리뷰트라도 제거하면 더 이상 함수적 종속성이 성립하지 않는 경우를 말한다. 즉, 키가 아닌 열들이 각각 후보키에 대해 결정되는 릴레이션 형태를 말한다.

제 3 정규형

어떠한 비주요 속성도 기본키에 대해서 이행 종속되지 않으면 만족한다.

BCNF 정규형

여러 후보키가 존재하는 릴레이션에 해당하는 정규화 내용. 복잡한 식별자 관계에 의해 발생하는 문제를 해결하기 위해 제 3 정규형을 보완하는데 의미가 있음. 비주요 속성이 후보키의 일부를 결정하는 것을 분해하는 것.


정규화의 장점

  1. 데이터의 이상현상을 제거할 수 있음
  2. 데이터베이스 구조 확장 시 재디자인 최소화 정규화된 데이터베이스 구조에서는, 새로운 데이터형의 추가로 인한 확장 시, 그 구조를 변경하지 않아도 되거나 일부만 변경해도 된다. 이는 데이터베이스와 연동된 응용프로그램에 최소한의 영향만을 미친다.
  3. 사용자에게 데이터 모델을 더욱 의미있게 제공.


단점

  • 릴레이션이 분해되었으므로, 릴레이션간의 Join 연산이 많아진다. 이로인해 질의 응답이간이 느려질 수 있다.
  • 하지만 데이터의 중복속성이 제거되고, 결정자에 의해 동일한 의미의 일반 속성이 하나의 테이블로 집약되므로 한 테이블의 데이터 용량이 최소화되는 효과가 있음.
  • 따라서 정규화된 테이블은 데이터를 처리할때 속도가 빨라질 수도 있고, 느려질 수도 있는 특성이 있다.


단점 대응책

조회를 하는 SQL 문장에서 조인이 많이 발생하여 이로인한 성능저하가 나타나는 경우에 반정규화를 적용해야한다.

반정규화(비정규화)

  • 반정규화는 정규화된 엔티티, 속성, 관계를 시스템의 성능 향상 및 개발과 운영의 단순화를 위해 중복 통합, 분리 등을 수행하는 데이터 모델링 기법 중 하나이다.
  • 디스크 I/O 량이 많거나, 테이블끼리의 경로가 너무 멀어서 조인으로 인한 성능 저하가 예상되거나, 칼럼을 계산하여 조회할 때 성능이 저하될 것이 예상되는 경우 반정규화를 수행하게 된다.
  • 일반적으로 조회에 대한 처리 성능이 중요하다고 판단될 때 부분적으로 반정규화를 고려하게 된다.

반정규화의 대상

  1. 액세스 하는 프로세스의 수가 가장 많고, 항상 일정한 범위만을 조회하는 테이블
  2. 대량 데이터가 있고, 대량의 범위를 자주 처리하는데 성능상 이슈가 있는 테이블
  3. 지나치게 많은 조인을 하여 데이터를 조회하는 것이 기술적으로 어려울 때

반정규화의 주의점

반정규화를 과도하게하면, 데이터의 무결성이 깨질수있음. 또한 입력, 수정, 삭제의 질의문에 대한 응답시간이 느려질 수 있음.



Transaction

트랜잭션이란?

하나의 논리적인 작업을 수행하는 쿼리문들을 묶은 것. 트랜잭션에 속한 쿼리문들은 모두 완벽히 수행되거나, 그렇지 않으면 원상복구해야한다.


Lock

트랜잭션을 동시에 수행할때, 동시성을 제어해주기 위한 기능. 여러 커넥션에서 동시에 동일한 자원을 요청할 경우 순서대로 한 시점에는 하나의 커넥션만 변경할 수 있게 해주는 역할


트랜잭션의 특성

원자성 : 트랜잭션 수행중 문제가 발생하면, 트랜잭션의 어떠한 작업도 데이터베이스에 반영되면 안됨.

일관성 : 트랜잭션이 완료된 다음의 상태에서도, 트랜잭션이 일어나기 전의 상황과 동일하게 데이터의 일관성을 보장해야함.

고립성 : 각 트랜잭션은 서로 간섭없이 독립적으로 수행되어야함.

지속성 : 트랜잭션이 정상적으로 종료된 다음에는, 영구적으로 데이터베이스에 작업의 결과가 반영되어야함.


트랜잭션의 상태

트랜잭션 상태 다이어그램

  • active
    • 트랜잭션이 실행중인상태
  • failed
  • partially committed
    • 트랜잭션의 commit 명령이 도착한 상태
    • 트랜잭션의 sql문은 다 수행되고 commit만 남은 상태
  • committed
    • 트랜잭션 완료상태
  • aborted
    • 트랜잭션 취소상태.
    • 데이터베이스는 트랜잭션이전으로 돌아간다.


트랜잭션 사용 시 주의점

트랜잭션의 범위는 최소화해야한다. 일반적으로 데이터베이스 커넥션은 개수가 제한적이고, 따라서 각 트랜잭션이 커넥션을 소유하는 시간이 길어지면, 다른 트랜잭션은 커넥션을 가져가기 위해 기다려야하는 상황이 발생할 수 있기 때문이다.


deadlock

두 개 이상의 트랜잭션이 특정 사원의 잠금을 획득한 채, 다른 트랜잭션이 소유하고 있는 잠금을 요구하는 상황이 원형으로 이루어지고 있는 상태. 아무리 기다려도 잠금이 풀리는 일이 없는 상태이다.

예시

classic deadlock

교착상태의 빈도를 낮추는 방법

  • 트랜잭션을 자주 커밋한다.
  • 정해진 순서로 테이블에 접근한다.
  • 읽기 잠금 획득의 사용을 피한다.
  • 한 테이블의 복수행을 복수의 연결에서 순서없이 갱신하면 데드락이 발생하기 쉬움. 이때는 테이블 단위의 잠금을 획득해 갱신을 직렬화하면 동시성은 떨어지지만 데드락은 피할 수 있다.



NoSQL

정의

관계형 데이터 모델을 지양함. 대량의 분산된 데이터를 저장하고 조회하는데 특화되어있으며 스키마없이 사용가능하거나 느슨한 스키마를 제공하는 저장소.

종류마다 쓰기/읽기 특화, 2차인덱스 지원, 오토샤딩 지원 같은 고유한 특성을 가짐. 대량의 데이터를 빠르게 처리하기 위해 메모리에 임시저장하고 응답하는 등의 방법을 사용함. 동적인 스케일 아웃을 지원하기도 하며, 가용성을 위하여 데이터복제 등의 방법으로 RDBMS가 제공하지 못하는 성능과 특징을 제공한다.


CAP이론

어떠한 분산시스템도 아래 세가지를 모두 만족할 수 없다는 이론.

image-20220206005652242

  • RDBMS는 가용성과 일관성을 만족하고, 나머지를 만족할 수 없음
  • NoSQL은 제품에 따라 다름.


1. 일관성(Consistency)

다중 클라이언트에서 같은 시간에 조회하는 데이터는 항상 동일한 데이터임을 보증하는 것을 의미. RDBMS에서는 기본적으로 제공하나, 일관성을 지원하지 않는 NoSQL을 사용하면 데이터의 일관성이 느슨하게 처리되어 동일한 데이터가 나타나지 않을 수 있음.

느슨하게 처리된다는 것은 데이터의 변경을 시간의 흐름에 따라 여러 노드에 전파하는 것을 의미한다.

NoSQL에서 분산 노드 간의 데이터 동기화를 위해 두가지 방법을 사용함.

  1. 데이터의 저장결과를 클라이언트로 응답하기 전에 모든 노드에 데이터를 저장하는 동기식 방법
    • 응답시간은 느리지만, 데이터의 정합성을 보여줌
  2. 메모리나 임시파일에 기록하고 클라이언트에 먼저 응답한 다음, 특정 이벤트 또는 프로세스를 사용하여 노드로 데이터를 동기화하는 비동기식 방법
    • 응답시간은 빠르지만, 쓰기 노드에 장애가 발생했을때 데이터가 손실될 수 있음.

2. 가용성(Availability)

모든 클라이언트의 읽기와 쓰기 요청에 대해 항상 응답이 가능해야함을 보증하는 것. 가용성을 가진 NoSQL은 클러스터 내에서 몇 개의 노드가 망가지더라도, 정상적인 서비스가 가능함.

몇몇 NoSQL은 가용성을 보장하기 위해 데이터 복제를 사용함. 동일한 데이터를 다중 노드에 중복 저장하여 그 중 몇 대가 고장나도 데이터가 유실되지 않도록 하는 것. 데이터 중복 저장 방법에는 동일한 데이터를 가진 저장소를 하나 더 생성하는 Master-slave와 데이터 단위로 중복저장하는 peer-to-peer 방법이 있다.

3. 네트워크 분할 허용성(partition tolerance)

지역적으로 분할된 네트워크 환경에서 동작하는 시스템에서, 두 지역간의 네트워크가 단절되거나 네트워크 데이터의 유실이 일어나더라도 각 지역내의 시스템은 정상적으로 동작해야함을 의히함.


저장 방식에 따른 NoSQL 분류

Key-value model

키 하나로 데이터 하나를 저장하고 조회할 수 있는 단일 키-값 구조를 가짐. 단순한 저장구조이며, 복잡한 조회연산을 지원하지 않는다. 따라서 고속읽기와 쓰기에 최적화되어있다.

사용자의 프로필 정보, 웹 서버 클러스터를 위한 세션정보, 장바구니 정보, URL 단축 정보저장 등에 사용된다.

하나의 서비스 요청에 다수의 데이터 조회 및 수정 연산이 발생하면 트랜잭션 처리가 불가능하여 데이터 정합성을 보장할 수 있음

Redis 등이 있음

Document model

key-value의 확장. 하나의 키에 구조화된 문서를 저장하고 조회하는 방식. 논리적인 데이터 저장과 조회 방법이 RDBMS와 유사함.

키는 문서에 대한 id로 표현됨. 또한 저장된 문서를 컬렉션으로 관리하여 문서 저장과 동시에 문서id에 대한 인덱스를 생성한다. 문서 id에 대한 인덱스를 사용하여 O(1) 시간안에 문서를 조회할 수 있다.

대부분의 문서모델 NoSQL은 B트리 인덱스를 사용하여 2차인덱스를 생성한다. B트리는 크기가 커지면 커질수록 새로운 데이터를 입력하거나 삭제할 때 성능이 떨어지게 된다. 그렇기 때문에 읽기와 쓰기의 비율이 7:3 정도일때 가장 좋은 성능을 보인다.

중앙 집중식 로그 저장, 타임라인 저장, 통계정보저장 등에 사용된다.

MongoDB 등이 있음

Column model

하나의 키에 여러개의 컬럼이름과 컬럼 값의 쌍으로 이루어진 데이터를 저장하고 조회하낟. 모든 컬럼은 항상 타음스탬프 값과 함께 저장된다.

구글의 빅테이블이 대표적인 예로, 차후 컬럼형 NoSQL은 빅테이블의 영향을 받았다. Row key, Column Key, Column Family 같은 빅테이블 개념이 공통적으로 사용된다. 저장의 기본 단위는 컬럼으로 컬럼은 컬럼 이름과 컬럼 값, 타임스탬프로 구성된다. 이러한 컬럼들의 집합이 로우(Row)이며, 로우키(Row key)는 각 로우를 유일하게 식별하는 값이다. 이러한 로우들의 집합은 키 스페이스(Key Space)가 된다.

컬럼모델 NoSQL은 쓰기에 더 특화되어있다. 데이터를 먼저 커밋로그와 메모리에 저장한 후 응답하기 때문에 빠른 응답속도를 제공한다. 그렇기 때문에 읽기 연산 대비 쓰기 연산이 많은 서비스나 빠른 시간 안에 대량의 데이터를 입력하고 조회하는 서비스를 구현할 때 가장 좋은 성능을 보인다.

채팅 내용저장, 실시간 분석을 위한 데이터 저장소 등의 서비스 구현에 적합하다.

출처

https://github.com/JaeYeopHan/Interview_Question_for_Beginner/tree/master/Database#%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4

This post is licensed under CC BY 4.0 by the author.

React) JS 다운로드 시간 동안의 로딩 화면

singleton

Comments powered by Disqus.

diff --git a/posts/datastructure/index.html b/posts/datastructure/index.html new file mode 100644 index 000000000..949af935a --- /dev/null +++ b/posts/datastructure/index.html @@ -0,0 +1,7 @@ + DataStructure 개요 | 디피의 개발일지
Posts DataStructure 개요
Post
Cancel

DataStructure 개요

Array vs Linked List

Array

  • 논리적 저장 순서와 물리적 저장순서가 일치함.

  • 장점

    • Random Access : 인덱스로 원하는 원소에 바로 접근이 가능하여, O(1)에 해당원소로 접근가능

    • 언어에따라 다르지만, 필요한 만큼만 메모리를 차지함

  • 단점

    • 중간에 한 원소를 삭제하거나, 새로운 원소를 넣을때, 다른 원소를 shift 해줘야하여 O(n)의 시간이 걸림.

Linked List

  • 각각의 원소가 자기 다음 (그리고 이전) 원소가 무엇인지만 알고있음
  • 장점
    • 삭제/삽입시 영향을 받는 다음/이전 원소의 값만 변경해주면 되기에 O(1)의 시간이 걸림
  • 단점
    • 논리적 저장순서와 물리적 저장순서가 일치하지 않아서, 원소를 찾을때는 처음 원소부터 차례대로 찾아야하기에 O(n)의 시간이 걸린다.
    • 이때 삭제/삽입 시에도, 원소를 찾는 과정이 필요하기에 결국 삭제/삽입 시에도 O(n)이 걸리게 된다.

그럼 왜 Linked List를 쓰는가? -> 다른 로직과 결합한다면 삭제/삽입/찾기의 시간복잡도를 줄일 수 있고, Tree의 근간이 되는 자료구조이기에

Tree

비선형 자료구조로, 계층적 관계를 표현하는 자료구조이다.

트리는 표현에 집중하며, 무엇인가를 저장하고 꺼낸다기보다는 어떤 데이터의 구조를 표현하는 것에 집중한다.

트리 용어

  • Node : 트리를 구성하는 원소들
  • Edge : 트리를 구성하기 위해 노드와 노드를 연결하는 선
  • Rood Node : 최상위 노드
  • Terminal Node(leaf node) : 하위에 다른 노드가 연결되지 않은 노드
  • Internal Node (내부노드) : 단말 노드를 제외한 모든 노드(루트 포함)
  • Level : 0(루트노드)부터 시작하여, 트리의 층별로 레벨을 하나씩 부여한 것
  • height : 해당 트리의 최고레벨

Binary Tree

한 부모가 두개의 자식만 가질 수 있는 트리를 말한다. 공집합도 이진트리에 포함된다.

  • 포화이진트리 : 모든 레벨이 꽉 찬 이진트리
  • 완전 이진트리 : 위에서 아래로, 왼쪽에서 오른쪽으로 순서대로 채워진 이진트리
  • 정 이진트리(Full Binary Tree) : 모든 노드가 0개 혹은 2개의 자식만을 가진 이진트리

배열로 구성된 Binary Tree는 root가 0이 아닌 1에서 시작할때, 부모노드의 인덱스가 i라고 한다면, 왼쪽 자식은 2i, 오른쪽 자식은 2i + 1이다.

Binary Search Tree

이진탐색트리는 이진트리의 일종으로, 데이터의 탐색을 용이하게 하기 위한 데이터 저장방법으로 고안되었다.

이진탐색트리에 데이터를 저장하는 규칙

  • 이진탐색트리의 노드에 저장된 키는 유일하다
  • 부모의 키가 왼쪽자식 노드의 키보다 크다
  • 부모의 키가 오른쪽자식 노드의 키보다 작다.
  • 왼쪽과 오른쪽 서브트리도 이진탐색트리다.

이진탐색트리에서 탐색연산은, 높이만큼 탐색하기에 O(h)이며, 완전이진트리일 경우 O(log n)의 시간복잡도를 가진다. 하지만 worst case로 데이터가 편향되어 트리에 들어왔을 경우 한쪽으로 치우치게 되어 O(n)의 복잡도를 가지게 된다.

이를 해결하기 위해 Rebalancing 기법을 사용하여, 트리의 균형을 다시 잡는 작업을 하기도 한다. 이 기법을 구현한 트리로는 Red-Black Tree가 있다.

Binary Heap

완전이진트리에 기반한 트리의 일종이다. 최대힙, 최소힙 이 있다.

  • 최대힙 : 각 노드의 값이 자식의 값보다 크거나 같은 완전이진트리. 최댓값을 찾는데 사용
  • 최소힙 : 각 노드의 값이 자식의 값보다 작거나 같은 완전이진트리. 최솟값을 찾는데 사용

삽입/삭제 연산은 최대 층수까지만 하니, O(log n)이다.

구현

  1. 루트노드는 1부터 시작.
  2. 삽입 연산
    • 트리의 가장 마지막에 삽입
    • 부모와 값을 비교해가며 올라감
    • 최소힙일 경우, 부모보다 클경우 멈춤
  3. 삭제 연산
    • 루트 노드가 사용자의 요청으로 사라짐
    • 가장 끝의 노드를 루트노드에 삽입
    • 두 자식보다 값이 작을 때까지 아래로 내려감(둘 중 하나만 본인보다 작을 경우 그쪽과 교환. 둘 다 작을 경우 아무데로)

Red Black Tree

이진 탐색트리에서, Rebalancing 기법을 적용하여 탐색/삭제/삽입에 O(log n)의 시간이 걸리도록 한 트리이다.

즉, 최대 높이를 log n로 만드는 것으로 완전이진트리에 기반을 둔다.

Red Black Tree 정의

RBT는 아래 성질을 만족하는 이진탐색트리이다.

  • 각 노드는 Red or Black이라는 색깔을 갖는다.
  • Root Node의 색상은 Black이다.
  • 각 leaf node(NIL)는 black이다.
  • 어떤 노드의 색깔이 red면, 두 자식은 모두 black이다.(Red가 두번 연속 나올 수 없음)
  • 같은 층에 있는 각 노드에 대하여 노드로부터 descendant leaves 까지의 단순 경로는 모두 같은 수의 black nodes들을 포함하고 있다. 이를 해당 노드의 black-height라고 한다.

Red Black tree의 특징

  • BST의 특징을 모두 갖는다.
  • Root node부터 leaf node까지의 모든 경로 중 최소 경로와 최대 경로의 크기 비율은 2보다 크지 않다. 이러한 상태를 balanced라고 한다.
  • 노드의 child가 없을 경우 child를 가리키는 포인터는 NIL 값을 저장한다. 이 NIL들을 leaf node로 간주한다.

No Image

삽입

  1. BST(이진탐색트리)의 특성을 유지하면서 삽입한다.
  2. 삽입된 노드에 Red를 부여한다.
  3. RBT의 특성을 위배했을 시 노드의 색깔을 조정하고, Black-height가 위배되었다면 rotation을 통해 height를 조정한다.
    • 다른 설명
      • Recoloring : 삽입된 노드의 부모의 형제 색깔이 Red일 경우
        • 부모와 부모의 형제 노드는 Black으로, 부모의 부모는 Red로(단, 루트노드일경우 Black으로 놔둠
        • 다시 double red가 발생했을 경우 다시 작업
      • Restructuring : 삽입된 노드의 부모의 형제 색깔이 Black 또는 NULL인 경우
        • 삽입된 노드, 부모, 부모의 부모를 오름차순으로 정렬
        • 중앙 값을 부모노드로 만들고, 나머지 노드를 자식으로 변환함.
        • 부모노드가 된 노드를 Black, 나머지 노드를 Red로 coloring 함
  4. 이러한 과정을 통해 RBT의 동일한 height에 존재하는 internal node들의 black-height가 같아지게 되고, 최소 경로와 최대경로의 크기 비율이 2 미만으로 유지된다.

삭제

  1. BST의 특성을 유지하면서 삽입한다.
  2. 만약 지워진 노드가 black이면,
    1. black-height가 1 감소한 경로에 black node가 1개 추가되도록 rotation하고 노드의 색깔을 조정한다.
  3. 지워진 노드가 Red라면,
    1. 특성이 위배되지 않으므로 RBT가 그대로 유지된다.

Hash Table

해시는 내부적으로 배열을 사용하여 데이터를 저장하기 때문에 빠른 검색속도를 가진다. 특정한 값을 Search할때 데이터 고유의 인덱스(해시값)으로 접근하므로, 탐색연산의 시간복잡도는 평균 O(1)이다.(충돌 때문에 항상 O(1)은 아님)

이 인덱스로 사용될 해시값은 특별한 알고리즘을 통해 저장할 데이터와 연관된 고유한 숫자를 만들어 낸 뒤 이를 인덱스로 사용한다. 이 인덱스는 그 데이터만의 고유한 위치이기에 삽입/삭제 시 다른 데이터로 채울 필요가 없으므로 연산에서 추가적인 비용이 없도록 만들어진 구조이다.

Hash Function

해시값을 만들어내기 위한 함수. 이 함수에 의해 반환된 데이터의 고유숫자값을 hashcode라고 하며, 저장되는 값들의 key값을 hash function을 통해서 작은 범위의 값들로 바꿔준다.

이때 서로 다른 데이터에 동일한 key 값을 도출해낼 수 있고, 이러한 상황을 Collision이라고 한다.

Collision : 서로 다른 두개의 키가 같은 인덱스로 hasing 되면 같은 곳에 저장할 수 없게 된다. collision이 많아질수록 탐색에 필요한 시간복잡도는 O(n)에 가까워진다.

Resolve Conflict

Open Address(개방 주소법)

다른 해시 버킷에 충돌이 발생한 자료를 삽입하는 방식.

다른 해시버킷을 찾는방법 : 최악의 경우 원래 자리로 돌아옴

1
+2
+3
+
1. Linear Probing : 순차적으로 탐색하여 첫번째로 발견한 빈 곳에 넣음
+1. Quadratic probing : 2차함수를 이용해 찾음
+1. Double hashing probing : 2차 해시 함수를 사용해 새로운 주소를 할당함. 많은 연산량이 필요하다.
+

Separate Chaining 방식 (분리연결법)

  1. 연결리스트를 사용하는 방식
    • 각각의 버킷들을 연결리스트로 만들어, Collision이 발생하면 해당 버킷의 리스트의 끝에 삽입하는 방식
    • 삽입/삭제가 간단함. Open Address 방식에 비해 테이블의 확장을 늦출 수 있다.
    • 작은 데이터를 저장할때 연결리스트 자체의 오버헤드가 부담이 된다는 단점이 있음.
  2. Tree를 사용하는 방식(RBT)
    • 위 방식과 동일한데 트리를 사용하는 것

연결리스트 vs Tree

  • 해시 버킷에 할당된 key-value 쌍의 개수로 어느 것을 사용할지 결정함
  • 데이터의 개수가 적다면 연결리스트를 사용한다. 트리는 메모리 사용량이 많기 때문이다.
  • 이때의 기준은 6개, 8개이다. (6개, 7개일경우 데이터가 하나삽입/하나 삭제 될때마다 자료구조를 바꿔야함. 따라서 2칸의 여유를 둠)
  • 예시
    • 해시버킷에 6개의 key-value 쌍이 있는데 하나의 값이 추가됨. -> 연결리스트 유지
    • 다시 하나의 값이 삭제 됨 -> 연결리스트 유지
    • 값이 다시 2개가 추가됨 -> 트리로 변경
    • 하나가 삭제 됨 -> 트리 유지
    • 하나가 삭제 됨 -> 연결리스트로 변경

보조해시함수

key의 해시값을 변형하여 해시 충돌 가능성을 줄이는 것. Separate chaining방식을 사용할때 함께 사용되며, 보조해시함수로 worst case에 가까워 지는 경우를 줄일 수 있다.

Open address vs Separate Chaining

둘다 worst case에서는 O(M)임. 하지만 Open Address 방식은 연속된 공간에 데이터를 저장하기 때문에 Separate Chaining에 비해 캐시 효율이 높다. 따라서 데이터가 충분히 적으면 Open Adderess가 좋다.

하지만 Open Address는 버킷을 계속 사용하기에 테이블의 확장이 자주 일어난다.

해시 버킷 동적확장(resize)

해시버킷의 개수가 적으면 해시 충돌로 성능상 손실이 발생함. 따라서 key-value 쌍 데이터 개수가 일정 개수 이상이 되면 해시 버킷의 개수를 두배로 늘린다. 이렇게 하면 해시 충돌로 인한 성능저하를 어느정도 해결할 수 있다.

이때 두배로 확장하는 임계점은 기존 해시버킷 개수의 75%이다.

Graph

Undirected Graph 와 Directed Graph (Digraph)

  • Directed Graph : 방향성이 포함된 그래프
  • Undirected Graph : 방향성이 없는 그래프

Degree

각 정점에 연결된 Edge의 개수. 방향그래프일경우 Degree가 둘로 나뉜다.

가중치 그래프

간선이 가중치를 두어서 구성한 그래프

부분그래프

본래 그래프의 일부 정점 및 간선으로 이루어진 그래프

그래프 구현법

인접행렬

  • 해당하는 위치의 value를 통해 정점간의 연결정보를 O(1)로 파악할 수 있음.
  • Edge의 개수와 무관하게 V^2의 메모리를 차지함
  • Dense Graph를 표현할때 적합함

인접 리스트 : 연결리스트

  • vertex의 리스트를 확인해야하기 때문에, 정점간 연결 확인에 오래 결린다.
  • 메모리는 O(E + V)를 차지함
  • Sparse Graph를 표현할때 적합함.

그래프 탐색

DFS

  • 연결되어있는 정점으로 계속 가다가 더이상 연결된 정점이 없으면 되돌아와서 다시 탐색함
  • O(V +E)

BFS

  • 한 정점에 연결되어있는 모든 정점으로 나아감
  • O(V + E). BFS로 구한 경로는 루트로부터 해당 노드로의 최단 경로임

최소신장트리(MST)

크루스칼

가중치가 가장 낮은 edge를 하나씩 추가하되, 사이클을 형성하지 않는 edge를 추가하는 것. V - 1개의 Edge가 추가되면 정지함.

시간 복잡도

  1. Edge의 weight를 기준으로 sorting - O(ElogE)
  2. cycle 생성여부를 검사하고 id를 통일 -> O(E + V log V)

Prim

현재까지의 그래프와 아직 추가되지 않은 노드와 연결되어 있는 간선 중에서 가장 작은 가중치를 가지는 간선만 추가함.

시간복잡도 : O(E logv)

This post is licensed under CC BY 4.0 by the author.

(Typescript) React typescript hack

network 개요

Comments powered by Disqus.

diff --git a/posts/deadlock/index.html b/posts/deadlock/index.html new file mode 100644 index 000000000..e980f2460 --- /dev/null +++ b/posts/deadlock/index.html @@ -0,0 +1 @@ + deadlock 발생 조건 | 디피의 개발일지
Posts deadlock 발생 조건
Post
Cancel

deadlock 발생 조건

deadlock 발생 조건

아래 4가지 조건을 모두 만족하면 데드락 발생가능성이 있음

  1. 상호배제(Mutual Exclusion)
    • 한 리소스는 한번에 한 프로세스만이 사용할 수 있음
  2. Hold and wait
  3. No preemption
  4. Circular wait


deadlock 방지법

  1. Prevention
    • 교착 상태가 발생할 수 있는 요구조건을 만족시키지 않게 함으로써 방지
  2. Avoidance
    • 교착상태 발생 가능성이 있는 자원할당을 하지 않음
    • ex) 은행가 알고리즘
  3. Detection and recovery
    • 교착상태가 발생하면 감지하여 고침
This post is licensed under CC BY 4.0 by the author.

17088 등차수열 변환

IPv6

Comments powered by Disqus.

diff --git a/posts/deep-link/index.html b/posts/deep-link/index.html new file mode 100644 index 000000000..0858a11dd --- /dev/null +++ b/posts/deep-link/index.html @@ -0,0 +1,43 @@ + 딥링크 | 디피의 개발일지
Posts 딥링크
Post
Cancel

딥링크

딥링크는 웹사이트에서 사용자 기기에 설치된 앱을 URI로 실행시킬 수 있도록하는 기술이다.


딥링크의 세가지 유형

이러한 딥링크에는 세가지 유형이 있다.

  • URI Scheme (초기 형태)
  • 보완
    • Universal Link (IOS)
    • App Link (Android)

URI Scheme

앱 개발자가 앱 내 특정 페이지마다 고유한 주소를 설정하고, 그 주소를 웹에서 실행시키면 설정한 특정 페이지가 열리는 형태이다. 이때 주소는 다음과 같은 형태이다.

1
+
myapp://post?version=1
+

여기서 myapp:// 부분을 Scheme(프로토콜)이라고 하고, 앱 개발자가 자신만의 Scheme를 등록할 수 있다.

하지만 이 Scheme를 소유권을 증명하지 않고 개발자가 원하는 값으로 등록할 수 있다는 점에서 Scheme값이 중복되는 문제가 발생했다. path까지 모두 동일한 주소를 사용한다면 android는 딥링크로 오픈할 앱 선택창이 노출되고, IOS는 가장 마지막에 설치한 앱이 자동으로 열린다.

이 문제점을 보완하기 위해 나온 것이 Universal Link와 App Link이다.

URI Scheme의 문제점을 보완하여 OS에 앱에 대한 도메인 주소를 등록함으로써 소유권을 증명한다. 도메인은 도메인 소유자만이 관리할 수 있기에 소유권이 증명되기 떄문이다.

앱 빌드 시 앱을 열 도메인을 등록하고 보안을 위해 등록한 도메인의 특정 경로에 인증 텍스트를 심어 놓는다. 그러면 앱이 설치된 후 OS 레벨에서 해당 도메인 경로의 인증 텍스트와 앱 빌드시의 값이 동일한지 검증하여 소유권을 인지한다.

검증된 도메인으로 생성된 딥링크 클릭 시,

  • 앱이 설치된 경우 앱이 바로 열리고
  • 앱이 설치되지 않은 경우 해당 도메인의 웹 페이지로 이동한다. 이때 웹페이지에서 마켓으로 보낼 수도 있다.

이 Universal Link와 App Link는 도메인으로 접속하기에 다음과 같이 일반적인 http, https 도메인 형식을 따른다.

1
+
https://myapp.com/post?version=1
+


딥링크 사용 시 참고사항

1) 딥링크는 OS, 브라우저마다 다르게 동작한다. 내가 겪은 것은 다음과 같다. 좀 더 자세한 OS/브라우저별 동작 차이는 이 글에 표로 정리되어있다.

  • IOS Safari에서 URI Scheme 사용시 경고창이 뜸
    • 앱이 설치되어있을 경우) "{앱이름}"에서 여시겠습니까? 라는 경고창이 뜸
    • 앱이 설치되어있지 않은 경우) 주소가 유효하지 않기 때문에 Safari가 해당 페이지를 열 수 없습니다. 경고창이 뜸.
  • URI Scheme의 소유권을 증명할 수 없다는 문제 때문에 정해진 보안 정책
  1. Universal LInk을 사용할 때 다음과 같은 상황이 있을 수 있다.

    • 주소창에 링크를 직접 붙여넣기할 경우 동작하지 않음

    • JS로 트리거 된 경우 리다이렉트가 되지 않음

    • openUrl과 같이 앱 내에서 프로그래밍 방식으로 링크를 열 경우 작동하지 않음

  2. Android 에서 intent:// .

    • Android 에서 URI Scheme 대신 다음과 같은 intent:// 을 사용할 수 있다.
      1
      +
      intent://details?id={앱id}&url={마켓주소}#Intent;scheme=market;package=${앱패키지};end;
      +
    • 장점은 앱이 없을 경우 자동으로 마켓으로 리다이렉트 시켜준다는 것이다.


딥링크로 앱 여는 코드

아래 코드는 프로젝트 수행 시 사용했던 코드이다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+
// 안드로이드
+const openOnAndroid = () => {
+    window.location.href = `${URIscheme}`
+    // 1초간 변화없으면 플레이스토어 열기
+    setTimeout(() => {
+        window.location.href = `market://details?id=${package}`
+    }, 1000)
+    return;
+}
+
  • URI scheme을 사용하여 앱 열기를 시도한다. 그 후 setTimeout을 등록하여 1초간 반응이 없을 시 마켓을 연다.
  • intent:// 를 지원하지 않는 상황이라 앱 여는 동작과 앱 없을 시 마켓 여는 동작을 분리했다.
  • 마켓은 market://이라는 URI Scheme을 사용했다.
    • 처음에는 플레이스토어 앱 링크를 사용했는데 정상적으로 앱이 열린 상황에서 다시 웹페이지로 돌아왔을때 플레이스토어로 리다이렉트 된 경우들이 종종 있었다. 백그라운드에서 앱링크가 동작하지 않고 URL로 인식하여 리다이렉트된 것이다. (브라우저, 버전 차이)
    • 하지만 market://으로 URI Scheme을 사용하면 URL이 아니기에 리다이렉트되지 않는다. 다만, 사용자 기기에 원스토어와 같은 다른 마켓이 설치되어있으면 어떤 앱으로 마켓을 열 것인지 선택하는 창이 뜬다.
1
+2
+3
+4
+5
+6
+7
+8
+9
+
const openOnIos = () => {
+    window.location.href = `${URIScheme}`''
+    setTimeout(() => {
+        // 크롬에서는 URI 스킴으로 열어야 앱 이동 후 웹 복귀 시 마켓이 안뜸.
+        // ios 사파리에서는 유니버셜 링크로 앱스토어를 열어야 '"AppStore"에서 여시겠습니까?' 라는 알럿창이 뜨지 않음.
+        window.location.href = isChrome ? `itms-apps://itunes.apple.com/app/${appId}` : `https://itunes.apple.com/app/${appId}`
+        }, 1000)
+    }
+}
+
  • IOS에서도 URI scheme을 사용하여 앱 열기를 시도한다. 그 후 setTimeout을 등록하여 1초간 반응이 없을 시 마켓을 연다.
  • 이때 크롬일경우랑 그 외 브라우저일경우랑 다른 주소로 마켓을 열도록 했다.
    • 크롬에서 유니버셜 링크로 마켓을 열 경우, 정상적으로 앱이 열린 상황에서 다시 웹페이지로 돌아왔을 때 앱스토어가 띄워진 경우가 있었다. 백그라운드에서 유니버셜링크가 실행된 것으로 보인다. 따라서 크롬에서는 URI 스킴을 사용해 앱을 열도록 했다.
    • 다른 브라우저에서는 유니버셜링크를 사용했다. 특히 사파리에서는 URI Scheme을 사용할 경우 "AppStore"에서 여시겠습니까? 라는 알럿창이 뜨기에 유니버셜링크로 마켓을 여는 것이 사용자 경험에 좋다.


출처

https://www.airbridge.io/blog-ko/deeplink-101-for-marketers-and-developers

https://www.airbridge.io/blog-ko/deeplink-101-ios-safari-alert

This post is licensed under CC BY 4.0 by the author.
diff --git a/posts/denormalized/index.html b/posts/denormalized/index.html new file mode 100644 index 000000000..d0b3f782d --- /dev/null +++ b/posts/denormalized/index.html @@ -0,0 +1 @@ + 반정규화 | 디피의 개발일지
Posts 반정규화
Post
Cancel

반정규화

반정규화

시스템의 성능 향상, 개발 및 운영의 편의성을 위해 정규화된 데이터 모델을 통합, 중복, 분리하는 과정으로 의도적으로 정규화 원칙을 위배하는 행위.

과도한 반정규화는 성능 저하를 불러일으킴.

  • 시스셈의 성능과 관리효율성은 증가하지만, 데이터의 일관성 및 정합성을 저하될 수 있음.
    • 따라서 사전에 데이터의 일관성 및 정합성을 우선할지, 데이터베이스의 성능과 단순화를 우선으로 할지 결정해야함

방법

  • 테이블 통합, 테이블 분할, 중복 테이블 추가, 중복 속성 추가


테이블 통합

두개의 테이블이 조인되는 경우가 많아 하나의 테이블로 합쳐 사용하는 것이 성능향상에 도움이 될 경우 수행한다.

  • 두개의 테이블에서 발생하는 프로세스가 동일하게 자주 처리되는 경우, 두 개의 테이블을 이용하여 항상 조회를 수행하는 경우 테이블 통합을 고려.
  • 1:1 관계 테이블 통합, 1:N 관계 테이블 통합, 슈퍼타입/서브타입 테이블 통합이 있음.
  • 고려사항
    • 데이터 검색은 간편하지만, 레코드 증가로 인해 처리량 증가
    • 데이터 통합으로 인해 입력, 수정, 삭제 규칙이 복잡해질 수 있음
    • Not Null, Default, Check 등의 제약조건을 설계하기 어려움


테이블 분할

테이블을 수직 또는 수평으로 분할하는 것

수평분할

  • 레코드를 기준으로 테이블을 분할
  • 레코드 별로 사용빈도의 차이가 큰 경우 사용빈도에 따라 테이블을 분할함

수직 분할

  • 하나의 테이블에 속성이 너무 많을 경우 속성을 기준으로 테이블을 분할함
  • 종류
    • 갱신 위주의 속성 분할 : 갱신이 자주 일어나는 속성들을 수직 분할하여 이용
    • 자주 조회되는 속성분할 : 자주 조회되는 속성이 극 히 일부일 경우 그 속성들을 수직분할하여 사용
    • 크기가 큰 속성 분할 : 이미지나 2GB 이상 저장도리 수 있는 텍스트 형식으로 된 속성들을 수직분할함.
    • 보안을 적용해야하는 속성 분할

고려사항

  • 기본키의 유일성 관리가 어려워짐
  • 데이터의 양이 적거나 사용빈도가 낮으면 굳이 해야하나 고려
  • 데이터 검색에 중점을 두어 테이블 분할 여부 결정


중복 테이블 추가

여러 테이블에서 데이터를 추출해서 사용해야하거나 다른 서버에 저장된 테이블을 이용해야하는 경우 중복 테이블을 추가하여 작업의 효율성을 향상 시킬 수 있음.

  • 중복 테이블을 추가하는 경우
    • 정규화로 인해 수행 속도가 느려지는 경우
    • 많은 범위의 데이터를 자주 처리해야하는 경우
    • 특정 범위의 데이터만 자주 처리하는 경우
    • 처리 범위를 줄이지 않고는 수행 속도를 개선할 수 없는 경우
  • 중복 테이블을 추가하는 방법
    • 집계 테이블 추가 : 집계 데이터를 위한 테이블을 생성하고, 각 원본 테이블에 트리거를 설정하여 사용. 트리거 오버헤드에 유의해야함
    • 진행 테이블 추가 : 이력 관리 등의 목적으로 추가하는 테이블로, 적절한 데이터 양의 유지와 활용도를 높이기 위해 기본키를 적절히 설정.
    • 특정 부분만을 포함하는 테이블의 추가 : 데이터가 많은 테이블의 특정 부분만을 사용하는 경우, 해당 부분만으로 새로운 테이블을 생성


중복속성 추가

조인해서 데이터를 처리할 떄, 데이터를 조회하는 경로를 단축하기 위해 자주 사용하는 속성을 하나 더 추가하는 것.

중복 속성을 추가하면 데이터의 무결성 확보가 어렵고, 디스크 공간이 추가로 필요하다.

  • 중복속성을 추가하는 경우
    • 조인이 자주 발생하는 속성
    • 접근 경로가 복잡한 속성
    • 액세스의 조건으로 자주 사용되는 속성
    • 기본키의 형태가 적절하지 않거나 여러개의 속성으로 구성된 경우
  • 고려사항
    • 테이블 중복과 속성의 중복 고려
    • 데이터 일관성 및 무결성에 유의
    • SQL 그룹 함수를 이용하여 처리할 수 있어야함.
    • 저장 공간의 지나친 낭비 고려



출처

https://lipcoder.tistory.com/337

This post is licensed under CC BY 4.0 by the author.

응집도와 결합도

관계 데이터 모델

Comments powered by Disqus.

diff --git a/posts/design-pattern-Apache-Kafka/index.html b/posts/design-pattern-Apache-Kafka/index.html new file mode 100644 index 000000000..1fbf7d674 --- /dev/null +++ b/posts/design-pattern-Apache-Kafka/index.html @@ -0,0 +1 @@ + Apache Kafka | 디피의 개발일지
Posts Apache Kafka
Post
Cancel

Apache Kafka

Apache Kafka는 빠르고 확장 가능한 작업을 위해 데이터 피드의 분산 스트리밍, 파이프 라이닝 및 재생을 위한 실시간 스트리밍 데이터를 처리하기 위한 목적으로 설계된 오픈 소스 분산형 게시-구독 메시징 플랫폼이다.

Kafka는 서버 클러스터 내에서 데이터 스트림을 레코드로 유지하는 방식으로 작동하는 브로커 기반 솔루션이다. Kafka 서버는 여러 데이터 센터에 분산되어 있을 수 있으며 여러 서버 인스턴스에 걸쳐 레코드 스트림(메시지)을 토픽으로 저장하여 데이터 지속성을 제공할 수 있다.

apache-kafka-diagram


Apache Kafka의 구성요소

  • 토픽
    • 지정된 데이터 스트림(일련의 레코드, 메시지)에 대한 관심을 표시하는데 사용되는 주소 지정가능한 추상화
    • 게시 및 구독할 수 있으며 애플리케이션에서 주어진 데이터 스프림에 대한 관심을 표시하는데 사용되는 추상화 계층
    • kafka 데이터 스트림이 어디에 publish 될지 정하는데 쓰임.
    • 파티션을 모아두는 곳이라고 이해해도 좋다
  • 파티션
    • 요청하는 메시지들의 모임
    • 파티션마다 커밋로그가 따로 쌓인다.
    • kafka 시스템에서 각 레코드/메시지에는 지정된 파티션의 메시지/레코드를 식별하는데 사용되는 오프셋이라는 순차 ID가 할당된다
  • 메세지/레코드

    • byte 배열로 주로 String, json avro를 사용한다
    • 메시지 크기에는 제한이 없지만 kb에서 해결하기를 권장
    • 데이터는 사용자가 지정한 시간만큼 저장된다.
    • 파티션에 들어가는 최소단위
  • 영속성
    • kafka는 레코드/메시지가 게시될 때 지속적으로 유지되는 서버 클러스터를 유지/관리하여 작동한다.
    • kafka 클러스터는 보존 시간 제한을 사용하여 소비에 관계없이 주어진 레코드가 지속되는 기간을 결정한다.
    • 레코드/메시지가 보존 시간 제한 내에 있는 동안 레코드/메시지를 사용할 수 있고, 보존 시간을 초과할 시 레코드/메시지는 삭제되는 공간이 확보된다.
  • 프로듀서
    • 주어진 레코드/메시지가 게시되어야하는 토픽을 정의하고, 파티션도 정의할 수 있다.
    • 파티션을 지정하지 않을 시엔 메시지는 파티션에 라운드 로빈 방식으로 분배된다 (로드 밸런싱)
  • 컨슈머
    • 레코드/메시지를 처리하는 엔티티이다.
    • 컨슈머는 개별 워크로드에서 독립적으로 작업하거나, 지정된 워크로드에서 다른 컨슈머와 협력하여 작업하도록 구성할 수 있다.(로드밸런싱)
    • 컨슈머는 컨슈머 그룹 이름을 기반으로 워크로드를 처리하는 방법을 관리한다.
    • 컨슈머 그룹 이름을 사용하면 컨슈머를 단일 프로세스 내, 여러 프로세스, 심지어 여러 시스템에 분산시킬 수 있다.
  • Broker Cluster

    • 임의 개수의 노드로 구성되는 클러스터로 토픽을 임의 개수만큼 호스팅할 수 있다

    • 브로커는 Replication Factor를 조절할 수 있다. Replication Factor를 2로 지정하면 토픽에 나눠서 한번 씩 담기게 되는 구조를 가진다. 이중 한 브로커가 없어지더라도 완전히 서빙이 될 수 있게 하기 위함이다.
    • Replication Factor를 설정하면 리더를 가진다. 그 리더를 중심으로 쓰기 읽기가 가능 하다. 리더가 사라지면 다음 Broker가 받아서 리더가 된다.
  • 토픽/파티션 확장

    • kafka는 서버 클러스터로 작동하기 때문에 주어진 토픽/파티션에서 각 서버에 부하를 공유하여 토픽/파티션을 확장할 수 있다.
    • 이 부하 공유를 통해 kafka 클러스터의 각 서버는 주어진 토픽/파티션에 대한 레코드/메시지의 배포 및 영속성을 처리할 수 있다.
    • 개별 서버가 모든 배포 및 영속성을 처리하는 동안 모든 서버는 서버가 실패할 경우 내결함성과 고가용성을 제공하는 데이터를 복제한다
    • 파티션은 파티션 리더로 선택된 한개 서버와 팔로워 역할을 하는 다른 모든 서버들로 분할된다.
    • 파티션 리더인 서버는 데이터의 모든 배포 및 영속성 (읽기/쓰기)을 처리하고 팔로워 서버는 내결함성을 위한 복제 서비스를 제공한다.


Apache Kafka 사용 사례

  • 서로 다른 구성 요소 간의 안정적인 데이터 교환
    • 분산 큐잉 시스템
      • 어떤 서비스의 웹 애플리케이션 서버에서 처리하는데 자원을 많이 소비해야할 때, 내부에서 처리하지 않고 백그라운드의 다른 태스크 프로세서에 요청하기 위한 규로 사용
  • 데이터 처리를 위한 실시간 스트리밍
    • 실시간 데이터 업데이트 ex) 통계
    • 채팅
  • 데이터 메시지 재생에 대한 기본 지원


Kafka vs Socket

실시간으로 통신하고, Pub-Sub 구조로 통신을 하는 방법이 필요하다면 소켓으로도 충분히 해결 가능하다. 소켓이 훨씬 가볍기에 이와 같은 상황에선 소켓이 가장 잘 맞는다. 그럼 kafka는 언제 쓰일까? 먼저 소켓의 단점부터 알아보자.

소켓의 단점

  1. producer와 consumer가 동시에 온라인 상태여야한다. 둘 중 하나가 오프라인이면 어떤 메세지도 보내지지 않고, 받아지지도 않는다.
  2. consumer는 producer만큼 스케일 업 되어야한다. producer가 10배의 메세지를 보내면, consumer도 10배 더 받을 수 있어야한다.
  3. consumer와 producer가 서로를 알아야한다.
  4. 또다른 consumer가 생기면 producer도 적절한 처리를 해줘야한다.
  5. 한번 보내진 메세지는 특수한 처리를 하지 않는한 저장되지 않는다.

kafka 등 메세징 시스템에서는

  1. consumer와 producer가 동시에 online일 필요가 없다.
  2. consumer와 producer가 서로를 몰라도 된다. 브로커만 알면 되고, 따라서 consumer와 producer 시스템 간 조정을 하지 않아도 된다.
  3. 메시징 시스템이 consumer와 producer 사이에서 버퍼로 작동한다. 메세지 볼륨이 커지면, 그 메세지들은 카프카에서 버퍼로 관리된되고, consumer는 하나씩 가져오면 된다. 따라서 매번 보내는 메세지의 볼륨 사이즈가 자주 변동되면, kafka는 좋은 선택지이다. 물론 계속 많은 메세지가 오면 consumer는 스케일 업 할 필요가 있지만, 가끔만 많이 오면 kafka가 적절하다
  4. 브로커를 사용하면, consumer와 producer는 새로운 누군가가 보내고 받는지를 신경쓰지 않아도 된다. 브로커만 신경 쓰면 된다.
  5. 메세지를 저장하고, offset으로 접근할 수 있다.

이와 같이 kafka는 socket의 단점을 해결해줄 수 있는 방법이며, 따라서 socket의 단점이 문제가 될 때 사용하는 방법이다. 단순히 클라이언트와 실시간 통신을 하는 정도는 socket으로 해결할 수 있지만, 좀 더 복잡한 상황에서는 kafka가 필요하다. 그렇기에 kafka는 주로 백엔드 서비스들 사이의 통신을 하기 위해 사용한다.

예를들어 128대의 서버간 망형태로 연결이 필요한 경우 128^2개의 소켓이 필요하다. 하지만 kafka를 사용하면 128개의 연결만으로도 해결이 가능하다.


출처

  • https://www.tibco.com/ko/reference-center/what-is-apache-kafka
  • https://engineering.linecorp.com/ko/blog/how-to-use-kafka-in-line-1/
  • https://magpienote.tistory.com/212
  • https://www.reddit.com/r/learnprogramming/comments/l0vqao/should_i_use_websockets_or_kafk
  • https://ence2.github.io/2020/11/kafka%EC%B9%B4%ED%94%84%EC%B9%B4%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80/
This post is licensed under CC BY 4.0 by the author.

커스텀 Annotation

ElasticSearch

Comments powered by Disqus.

diff --git "a/posts/design-pattern-\353\213\250\354\235\274\354\261\205\354\236\204\354\233\220\354\271\231/index.html" "b/posts/design-pattern-\353\213\250\354\235\274\354\261\205\354\236\204\354\233\220\354\271\231/index.html" new file mode 100644 index 000000000..cca5091f5 --- /dev/null +++ "b/posts/design-pattern-\353\213\250\354\235\274\354\261\205\354\236\204\354\233\220\354\271\231/index.html" @@ -0,0 +1,73 @@ + 단일책임원칙(SRP) | 디피의 개발일지
Posts 단일책임원칙(SRP)
Post
Cancel

단일책임원칙(SRP)

좋은 OOP 설계에서 지켜져야하는 규칙인 SOLID 중 S인 규칙으로, 하나의 모듈은 하나의 책임을 가져야한다는 원칙을 말한다.

클래스는 단 한개의 책임을 가져야한다.

하나의 모듈은 오직 하나의 액터에 대해서만 책임져야한다.

클래스를 변경하는 이유는 단 한개여야 한다.

여기서 책임은 액터에 대한 책임을 의미한다. 하나의 액터에 대해 하나의 책임을 지는 객체를 정의하고 사용하라는 것이다. 액터는 시스템과 상호작용하는 시스템 외부의 어떤 존재를 뜻하는 말로, 시스템의 이해관계자로 정의할 수 있다. 쇼핑 앱을 예로 들면, 물건을 구매하려는 고객이 액터이다.

이 정의는 모듈의 응집도를 높이기 위해 사용된다. 단일 액터를 책임지는 코드들을 묶어 응집도를 높여 단일 책임 원칙을 준수함을 통해 모듈의 응집도를 높이는 것이다.

이렇게 모듈의 응집도를 높여 하나의 액터에 대해 하나의 책임을 지는 객체를 정의한다면, 단일 액터만이 단일 객체에 대해 변경할 이유를 가질 수 있다.


SRP를 지켜야하는 이유

한가지 예시를 들며 SRP를 지키는 것이 무엇이며 어떻게 개발 비용을 증가시키는지 알아보자.

만약 도서관의 도서 관리 시스템을 맡게 되었고, 아래와 같은 유즈케이스 다이어그램을 도출하였다고 해보자.

usecase

이 유즈케이스 중 대여, 연체관리, 연체금 표시를 묶어 하나의 rental이라는 클래스에 기능을 구현하였고, 회원은 연체금이 10000원이 넘어가면 더이상 책을 대여할 수 없다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+
import { Injectable } from '@nestjs/common';
+
+@Injectable()
+export class RentalService {
+  async userRentalBook() {
+    // 연체료 계산
+    // 대여 로직 수행
+  }
+
+  async getUserArrears() {
+    // 연체료 계산
+    // 연체료 표시 
+  }
+
+  ...
+}
+

위의 두 함수, 책을 대여하는 함수와 연체금을 표시하는 함수는 책의 연체금을 계산하는 로직은 각 메서드 내부에 있다.

위 코드는 잘동작한다. 하지만, 만약 어느날 연체금 정책이 변경되었다고 해보자. 원래는 모든 책이 연체일 하루당 1000원이었지만, 사람들이 많이 읽는 책은 1500원, 잘 읽지 않는 책은 500원으로 변경되었다.

이때 문제가 발생한다. 대여함수와 연체금 표시에서 각각 연체금 계산 로직을 새로 수정해야한다.

원인은?

원인은 하나의 클래스에 속한 두 함수는 연체금 계산이라는 로직을 공유하지만 두 함수가 제공하는 기능의 책임이 다른 곳에 있기 때문이다.

책 대여 함수는 회원 액터에 대한 책임이 있고, 연체금 표시는 관리자 액터에 대한 책임이 있다. 이렇게 두 개의 액테에 대한 책임이 한 클래스에 부여되어 변경사항을 반영해야하는 범위가 넓어진 것이다.

해결책

단일책임원칙을 지켜 대여는 회원 액터만이, 연체금 표시는 사서 액터만이 사용하도록 변경해주면 된다. RestalService 클래스에 있는 두 메서드를 완전히 분리하면 해결할 수 있다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
@Injectable()
+export class ArrearsService {
+  async calcUserArrears() {
+    // 연체금 계산
+  }
+
+  async getUserArrears() {
+    // 연체금 표시
+  }
+
+  ...
+}
+
+@Injectable()
+export class RentalService {
+    async userRentalBook() {
+    // 책 대여
+    }
+  ...
+}
+

연체금과 관련된 연체금 계산 로직 및 연체금 표시 메서드는 ArrearsService에 위임하고, 대여와 관련된 메서드는 RentalService에 위임하였다. 이로써 ArrearsService는 누가 대여하는지 알지 못한다. 단순히 연체일에 따라 연체금을 표시하도록 변경되었다. RentalService는 연체금 계산 방식에 대해 알지 못한다. 단순히 대여가능 여부만을 알 수 있다.

이렇게 메서드를 분리한 후 아까와 같은 변경사항이 왔다고 해보자. ArrearsService에 연체금 계산 메서드가 변경되겠지만, RentalService는 변경하지 않아도 된다. 어떻게 계산이 되든 10000원이 넘어가는 연체금이라면 대여할 수 없다. 변경사항은 연체금에 대한 수정사항이었고, 관리자 액터로부터 야기된 수정사항이다.


출처

https://velog.io/@qjqdn1568/SOLID-%EB%8B%A8%EC%9D%BC-%EC%B1%85%EC%9E%84-%EC%9B%90%EC%B9%99-SRP

This post is licensed under CC BY 4.0 by the author.

RestTemplate

ResponseStatus vs ResponseEntity

Comments powered by Disqus.

diff --git "a/posts/design-pattern-\354\234\240\353\213\233\355\205\214\354\212\244\355\212\270\354\227\220\354\204\234-\352\265\254\355\230\204-\352\262\200\354\246\235\354\235\264\353\236\200/index.html" "b/posts/design-pattern-\354\234\240\353\213\233\355\205\214\354\212\244\355\212\270\354\227\220\354\204\234-\352\265\254\355\230\204-\352\262\200\354\246\235\354\235\264\353\236\200/index.html" new file mode 100644 index 000000000..1a957ebed --- /dev/null +++ "b/posts/design-pattern-\354\234\240\353\213\233\355\205\214\354\212\244\355\212\270\354\227\220\354\204\234-\352\265\254\355\230\204-\352\262\200\354\246\235\354\235\264\353\236\200/index.html" @@ -0,0 +1,343 @@ + 유닛테스트 구현 검증이란? | 디피의 개발일지
Posts 유닛테스트 구현 검증이란?
Post
Cancel

유닛테스트 구현 검증이란?

비즈니스 코드를 먼저 작성하고 유닛테스트를 작성할 때, 완성된 코드를 보고 테스트 코드를 작성하다보면 내부 구현에 맞추어 테스트를 작성하게 되고, 구현 자체를 검증하게 되는 일이 많이 발생하였다.

유닛 테스트에서는 구현이 아닌 행동을 검증(test behaviour, not implementation)해야한다. 따라서 구현을 검증한 테스트 코드를 고치려 했으나 어디까지가 구현을 검증한 것인지가 모호했다.

A 메서드 안에서 B 메서드를 제대로 호출했는지를 보는 것도 구현 검증일까?

구현 검증을 피하고자 DB를 검사해도 되는 걸까?

최종 결과물을 내지 않는 void 메서드는 어떻게 테스트하지?

명확한 기준을 세우고 싶어 여러모로 조사하였다.


구현을 검증하는 경우

아래 내용은 유닛테스트에서 내부구현 검증 피하기라는 포스트에서 가져온 내용으로, 구현 검증이 무엇인지 이해하기 위해 살펴봤다.

상세 구현부를 다 검증하는 경우

다음과 같은 클래스가 있다

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+
export class OrderAmountSum {
+   minusSum: number = 0;
+   plusSum: number = 0;
+
+   constructor(amounts: number[]) {
+      this.addPlusAmounts(amounts);
+      this.addMinusAmounts(amounts);
+   }
+
+   get sumAmount(): number {
+      return this.plusSum + this.minusSum;
+   }
+
+   addPlusAmounts(amounts: number[]): void {
+      this.plusSum = amounts
+         .filter((amount) => amount > 0)
+         .reduce((before, current) => before + current);
+   }
+
+   addMinusAmounts(amounts: number[]): void {
+      this.minusSum = amounts
+         .filter((amount) => amount < 0)
+         .reduce((before, current) => before + current);
+   }
+}
+

이 클래스에서 중요한 비즈니스 로직은 sumAmount에 있다. addPlusAmounts, addMinusAmounts은 부차적인 기능에 불과하다. 하지만 여기서 다음과 같이 부차적인 기능에 대한 테스트를 짜는 실수를 할 수 있다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+
it("plusSum에는 양수들의 총합이 등록된다", () => {
+   const sut = new OrderAmountSum([1000, 300, -100, -500]);
+
+   expect(sut.plusSum).toBe(1300);
+});
+
+it("minusSum에는 음수들의 총합이 등록된다", () => {
+   const sut = new OrderAmountSum([1000, 300, -100, -500]);
+
+   expect(sut.minusSum).toBe(-600);
+});
+
+it("전체 합계 금액을 구한다", () => {
+   const sut = new OrderAmountSum([1000, 300, -100, -500]);
+
+   expect(sut.sumAmount).toBe(700);
+});
+

이렇게 되면 만약 다음과 같이 코드가 변경되었을 경우 sumAmount 테스트 말고는 전부 깨질 것이다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
export class OrderAmountSum2 {
+   amounts: number[];
+
+   constructor(amounts: number[]) {
+      this.amounts = amounts;
+   }
+
+   get sumAmount(): number {
+      return this.amounts.reduce((before, current) => before + current);
+   }
+}
+

핵심 로직이 아닌 부차적인 기능까지 테스트하여 발생한 문제다. 따라서 OrderAmountSum의 테스트 코드는 다음과 같이 작성되어야한다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+
describe("OrderAmountSum2", () => {
+   it("전체 합계 금액을 구한다", () => {
+      const sut = new OrderAmountSum2([1000, 300, -100, -500]);
+
+      expect(sut.sumAmount).toBe(700);
+   });
+
+   it("-금액들의 합계 금액을 구한다", () => {
+      const sut = new OrderAmountSum2([-1000, -300, -100, -500]);
+
+      expect(sut.sumAmount).toBe(-1900);
+   });
+});
+


모의 객체 행위 검증하기

다음과 같이 Order를 업데이트하거나 생성하는 메소드가 있다고 해보자.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+
export class OrderService {
+    constructor(private readonly orderRepository: OrderRepository) {
+    }
+
+    saveOrUpdate(order: Order): void {
+        const savedOrder = this.orderRepository.findById(order.id);
+        if (savedOrder) {
+            this.orderRepository.update(order);
+        } else {
+            this.orderRepository.save(order);
+        }
+    }
+}
+

이때 stub을 통한 모의 객체를 사용하여 다음과 같은 테스트를 작성할 수 있다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+
it("기존 주문이 있으면 새 정보로 갱신된다", () => {
+   const savedOrder = Order.create(1000, LocalDateTime.now(), "");
+   when(mockedRepository.findById(anyNumber())).thenReturn(savedOrder);
+   const sut = new OrderService(instance(mockedRepository));
+
+   sut.saveOrUpdate(createOrder(savedOrder, 200));
+
+   verify(mockedRepository.update(anything())).called();
+});
+

여기서의 문제는 saveOrUpdate 내부에서 repository를 하나하나 어떻게 쓰는지 검증했다는 점이다. 만약 다음과 같이 saveOrUpdate가 변경된다면 어떻게 될까?

1
+2
+3
+
saveOrUpdate(order: Order): void {
+    this.orderRepository.saveOrUpdate(order);
+}
+

실제로는 잘 저장 또는 업데이트 되어도 테스트는 실패했다고 뜰 것이다. 따라서 테스트는 다음과 같이 진행되어야한다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+
it("[After] 기존 주문이 있으면 새 정보로 갱신된다", () => {
+   const savedOrder = realRepository.save(
+      Order.create(1000, LocalDateTime.now(), "")
+   );
+   const expectAmount = 200;
+
+   const sut = new OrderService(realRepository);
+   sut.saveOrUpdate2(createOrder(savedOrder, expectAmount));
+
+   const result = realRepository.findById(savedOrder.id);
+
+   expect(result.amount).toBe(expectAmount);
+});
+


의문 하나씩 살펴보기

위에서 살펴본 경우를 보고 한번 글 초반에 작성했던 의문에 대한 답을 생각해보자

A 메서드 안에서 B 메서드를 제대로 호출했는지를 보는 것도 구현 검증일까?

구현 검증이다. A 메서드는 B 메서드를 호출하든 안하든 A 메서드에 부여된 일을 수행하면 될 뿐이다.

만약 구현이 변경되어 A메서드는 B 메서드가 아닌 C 메서드를 호출하는 것으로 변경되었으나 기능은 기존과 동일할때, B 메서드에 의존한 테스트는 실패하게 된다. 이는 위 예시로 봤을 때 구현 검증으로 테스트가 깨진 사례에 속한다.

구현 검증을 피하고자 DB를 검사해도 되는 걸까?

좋지 않은 패턴이다. DB에 의존하여 결과를 확인하는 것은 DB의 상태, 버전 등에 따라 테스트 결과가 달라질 수도 있다. 매번 결과가 다른 테스트가 작성되는 것이고, 이는 테스트의 F.I.R.S.T 원칙의 Repeatable 을 위반한 사례이다.

최종 결과물을 내지 않는 void 메서드는 어떻게 테스트하지?

구현이 아닌 행동를 테스트해야하는 건 잘 알았다. 하지만 결과를 쉽게 알 수 없는 void 메서드일 경우는 어떻게 테스트 해야할까?

테스트 코드 작성 경험이 별로 없는 나에겐 빠르게 생각하기 어려웠다. 따라서 한번 관련 글들을 조사해보았다.


Test behaviour, not implementation

Test behaviour, not implementation이라고 검색한 후 나온 포스트들을 하나씩 보며 구현을 검증하지 않고 행동을 검증하는 것이 무엇인지를 알아보았다

Testing Behavior vs. Testing Implementation

Testing Behavior : 정답이 왜 나오는 지는 모르겠는데, 이 환경에서 이 결과만 나오면 돼!

Testing Implementation : 결과는 신경안쓰고, 과정만 제대로 확인해!

실제로는 결과를 보며 테스트를 하지만, 너무 많은 mock와 stub에 의존하며 결과가 어떻게 나오는지에 대한 많은 assertions을 만들게 된다. 이러한 mocks와 stub은 시스템이 발전함에 따라 문제가 된다.

mocks와 stub가 나쁜 것은 아니다. mocks와 stub은 서로 다른 시나리오와 환경에서 테스트를 할 수 있게 해준다. 이 정도는 supporting implementation이며 testing implementation이 아니다. 하지만 너무 많은 assertions이 mocks와 stub에 의존하여 나온다면 문제가 된다.

Testing behavior와 testing implementation이 같아지는 때가 있다. 테스트하는 메서드가 coordinator 메서드일 경우 그렇다. 이러한 메서드는 결정을 내리고 다른 메서드나 클래스를 호출하는 역할을 한다. 이러한 메서드를 테스트할 때는 메서드가 적절히 불러졌는지 확인하는 것밖에 없다.

Testing begavior와 testing implementation이 같아지는 예시)

다음과 같이 terminalService에서 terminal list을 가져와 하나씩 ContactTerminal 메서드에 넣어 실행시키는 FindTerminals 메서드가 있다고 해보자.

1
+2
+3
+4
+5
+
void FindTerminals() {
+    terminals = terminalService.FindAll();
+    foreach (var terminal in terminals)
+        ContactTerminal(terminal);
+}
+

FindTerminals 에 대한 테스트는 다음과 같다. terminalService.FIndAll() 메서드의 응답으로 3개의 terminal 을 응답하도록 stub 처리하고, FindTerminals을 실행시킨다. 그리고 정확히 3번 ContactTerminal이 실행되는지 확인하는 코드이다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+
var terminals = new List<Terminal>{ new Terminal(), new Terminal(), new Terminal() };
+terminalServiceMock
+    .Setup(f => f.FindAll())
+    .Returns(terminals);
+
+var activeTerminalLocatorMock = new Mock<ActiveTerminalLocator>(
+        terminalServiceMock.Object,
+        networkServiceFactoryMock.Object,
+        backgroundWorkerFactory
+);
+activeTerminalLocatorMock.Object.FindTerminals(null);
+
+activeTerminalLocatorMock.Verify(
+    f => f.ContactTerminal(It.IsAny<Terminal>()),
+    Times.Exactly(3)
+);
+

위와 같은 테스트는 구현을 검증한 예시이다. 어떤 메서드가 몇번 호출되었는지 확인하는 코드이기 때문이다. 하지만 결과를 검증하는 예시이기도 하다. ContactTerminal이 주어진 terminal만큼 실행시키는 것이 FindTerminals의 목적이고, 그것을 검증하는 코드이기 때문이다. 예를 들어 FindTerminals가 다음과 같이 변경되어도, FindTerminals의 목적은 똑같기에 때문에 테스트 코드는 여전히 유효하다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+
void FindTerminals() {
+    terminals = terminalService.FindAll();
+    foreach (var terminal in terminals) {
+        using(var worker = workerFactory.CreateWorker(){
+            worker.DoWork += (o, ea) => ContactTerminal(terminal);	// 3번 실행됨.
+            worker.RunWorkerAsync();
+         }
+     }
+}
+

이와 같이 테스트 대상의 목적을 검증하는 것과 구현을 검증하는 것이 같아지는 때가 있다. 이때는 구현이 크게 변경되어도, 목적이 달라지지 않는다면 테스트 코드는 변화하지 않는다.


포스트 후기

이 포스팅에서 얻은 것은 테스트 대상의 목적을 확실히하고, 그 목적을 테스트한다면 구현을 테스트하더라도 위에서 걱정했던 테스트 코드를 변경하는 문제는 나타나지 않는다는 점이다.

예시에서 봤듯이 어떤 메서드를 몇번 호출하였는지를 확인하는 것은 구현 검증이지만, 목적 자체가 ContactTerminal을 호출하는 것이었기에 테스트 코드는 변화하지 않았다.

만약 ContactTerminal를 호출하지 않는 사양으로 메서드가 변경된다면 그것은 목적이 변한 것이니 애초에 메서드를 분리해서 구현하든가, 테스트 코드도 마땅히 바꿔야하는 일이다.


Michel - Test behavior, not implementation

다음과 같이 간단한 메서드가 있다고 해보자.

1
+2
+3
+
public List<Book> getAllBooks() {
+	return bookRepository.getAllBooks();
+}
+

이 메서드에 대한 테스트를 다음과 같이 작성할 수 있다. 이는 bookRepository의 메서드가 제대로 호출되었는지 확인하는 것이므로 구현을 테스트한 것에 속한다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
private Book lord_of_the_rings = new Book("Lord of the rings");
+
+@Test
+public void reallyInterestedInWhatThisServiceIsDoing() {
+	BookRepository bookRepository = mock(BookRepository.class);
+   when(bookRepository.getAllBooks()).thenReturn(Collections.singletonList(lord_of_the_rings));
+	BookService bookService = new BookService(bookRepository);
+
+	assertThat(bookService.getAllBooks(), containsInAnyOrder(lord_of_the_rings));
+	verify(bookRepository, times(1)).getAllBooks();
+}
+

우리는 behaviour를 테스트해야한다. 따라서 위 테스트는 아래와 같이 behaviour만 테스트하는 형태로 변경되어야한다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+
private Book lord_of_the_rings = new Book("Lord of the rings");
+
+@Test
+public void shouldGetAllBooks() {
+	BookRepository bookRepository = mock(BookRepository.class);
+   when(bookRepository.getAllBooks()).thenReturn(Collections.singletonList(lord_of_the_rings));
+	BookService bookService = new BookService(bookRepository);
+
+	assertThat(bookService.getAllBooks(), containsInAnyOrder(lord_of_the_rings));
+}
+

물론 테스트가 돌아가게 하기위해 bookRepository은 mock 객체이고, when을 사용하여 behaviour을 정의하고 합치는 collaborator는 필요하다.

Verify behaviourmutation에서도 적용된다. 물론 그러한 메서드는 void 타입을 반환을 할 수 있다. 유일한 차이는 결과가 아래에서 나온다는 점이다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+
public void createBook(Book book) {
+	bookRepository.createBook(book);
+}
+...
+@Test
+public void insertBook() {
+	BookRepository bookRepository = mock(BookRepository.class);
+	BookService bookService = new BookService(bookRepository);
+	Book the_dark_tower = new Book("The Dark Tower");
+	bookService.createBook(the_dark_tower);
+
+	verify(bookRepository, times(1)).createBook(the_dark_tower);
+}
+

mutation의 결과를 반환하는 경우도 있다. 그럴 경우 반환값과 호출 여부를 모두 확인해야한다. 하지만 보통 이런 메서드는 사이드 이펙트를 부르는 안티패턴이고, 두 개의 메서드로 분리되어야한다.


포스트 후기

첫번째 포스트와 비슷하게 다른 메서드 호출이 중요한 메서드의 테스트는 호출여부를 확인하였다.


Unit testing behaviours without coupling to implementation details

Q : save X to some data source와 같이 일반적인 service-repotisoty구조에서 구현을 함께 테스트하지 않고 어떻게 테스트를 하는가?

A1)

save X to some data source 과 같은 경우는 외부 의존성과 통신하는 경우이므로, 특정한 메서드가 제대로 호출되었는지 테스트 해야하는 경우에 속한다. 테스트해야할 behaviour가 통신이 예상대로 이뤄졌는가이기 때문이다.

어플리케이션과 외부 의존성 사이의 인터페이스는 구현상세가 아니고 시스템 아키텍쳐 정의이다. 변경될 일이 적다는 의미이다. 따라서 repository 인터페이스를 테스트하는 것은 그리 많은 문제를 내지 않는다.(만약 문제를 많이 내면, 인터페이스가 어플리케이션의 역할을 빼았은 경우에 속한다.)

UI, 데이터베이스, 외부 서비스를 제외한 어플리케이션의 비즈니스 로직만이 테스트와 구현상세를 분리해야하는 부분이다.

A2)

  • 모든 인터페이스는 input, output, collaborator(like database) 와 함께 구성된다.
  • input 인터페이스는 테스트하라 : 메서드를 부르고, 반환값을 assert하라
  • output 인터페이스는 mock 하라 : 예상되는 메서드가 올바른 값으로 호출되었는지 확인하라
  • collaborator는 fake하라 : 간단하지만 동작하는 구현을 붙여라

database는 일반적으로 collaborator다. mock되기보단 fake를 붙이는 게 좋다. 손으로 구현하긴 어렵지만, 사용할 수 있는 도구들이 있다. 로그처럼 한번 써지고 다시는 읽히지 않은 database는 output이므로 mock되어야한다.

Input-output-collaborator에 대해 자세히

  • interface는 외부 세계(db, socket, http 등)과 통하는 port를 감싸고 있다.
  • output interface : exception을 제외한 반환값을 내지 않고, port를 통해 외부 세계로 나가는 인터페이스이다
  • input interface : 반환 값을 내며, 외부로 나가지 않는 인터페이스이다
  • Collaborator : input, output 둘 다한다

후기

답변 1에서는 service의 behaviour가 repository와 통신하는 것이므로 메서드 호출 여부를 확인해야하며, repository는 제대로 설계되었다면 변경될 일이 적으므로 괜찮다는 말이다.

답변 2는 input-output-collaborator로 인터페이스를 나눠서 repository와 같은 collaborator는 가짜 구현을 붙여 결과를 테스트 하라고 하였다.

첫번째 답변에서는 repository를 크게 변경하여 테스트가 너무 많이 깨진다면 애초에 설계가 잘못된 것이라는 점을 알았다. 두번째 답변에서는 가짜 데이터베이스를 붙여 결과를 확인하는 것도 가능하는 것을 알았다.


결론

세가지 글을 읽고 나니 구현 검증 없이 테스트를 어떻게 해야하는 것인지 어느정도 감이 잡혔다. 다음과 같은 유형으로 정리할 수 있을 거 같다

  • 주어진 input에 올바른 output을 반환하는 것이 목적인 경우

    -> input 값에 따른 output이 올바른지 테스트

  • 다른 메서드를 올바르게 호출하는 것이 목적인 경우

    • 호출해야하는 메서드를 올바른 값으로 호출하였는지 테스트 (행동 검증 == 구현 검증)
    • 이때 호출해야하는 것이 collaborator 인터페이스라면, fake 구현을 붙일 수도 있다.

중요한 것은 테스트 대상의 목적을 명확히 하는 것이다. 그 후 그 목적을 테스트한다면, 구현을 검증하더라도 행동을 검증하는 것이 된다.

그리고 좀 더 세부적으로 일반적인 service-repository 구조에서 repository가 제대로 호출되었는지 확인하는 것은 구현을 테스트하는 것이 아니라고 볼수도 있다. repository는 외부 데이터베이스의 인터페이스이므로 구현보다는 시스템 아키텍쳐에 가깝기 때문이다. 따라서 respository의 변경은 일반적으로 테스트를 깨트리지 않고, 만약 테스트를 너무 많이 깨트린다면 repository 설계가 잘못된 것이기에 마땅히 테스트를 수정해야한다.

service-repository 구조에서는 repository를 제대로 호출하는 것으로 검증할수도 있지만, 만약 정말로 데이터가 제대로 저장되는지 확인하고 싶다면 테스트용 DB를 사용하는 방법도 있다.


주의할 점

테스트를 작성하는 것은 완벽히 하기 위해 하는 것이 아니다. 코드를 조금 더 좋게 하기 위해 하는 것이다. 따라서 좋은 테스트를 작성하도록 노력은 하되 너무 집착하여 완벽한 테스트를 작성하고자 하진 않아도 된다.


출처

  • https://jojoldu.tistory.com/614
  • https://understandlegacycode.com/blog/is-it-ok-to-change-code-for-testing-sake/
  • https://launchscout.com/blog/testing-behavior-vs-testing-implementation
  • https://craftsmen.nl/test-behaviour-not-implementation/
  • https://softwareengineering.stackexchange.com/questions/234024/unit-testing-behaviours-without-coupling-to-implementation-details
This post is licensed under CC BY 4.0 by the author.

Keep-Alive

Spring 파라미터 Validation

Comments powered by Disqus.

diff --git "a/posts/design-pattern-\354\242\213\354\235\200-\353\241\234\352\271\205\354\235\204-\354\234\204\355\225\264-\354\225\214\354\225\204\354\225\274\355\225\240-\352\262\203/index.html" "b/posts/design-pattern-\354\242\213\354\235\200-\353\241\234\352\271\205\354\235\204-\354\234\204\355\225\264-\354\225\214\354\225\204\354\225\274\355\225\240-\352\262\203/index.html" new file mode 100644 index 000000000..a310a9327 --- /dev/null +++ "b/posts/design-pattern-\354\242\213\354\235\200-\353\241\234\352\271\205\354\235\204-\354\234\204\355\225\264-\354\225\214\354\225\204\354\225\274\355\225\240-\352\262\203/index.html" @@ -0,0 +1,17 @@ + 좋은 로깅을 위해 알아야할 13가지 | 디피의 개발일지
Posts 좋은 로깅을 위해 알아야할 13가지
Post
Cancel

좋은 로깅을 위해 알아야할 13가지

이 글은 Logging Best Pratices : The 13 You Should Know를 기반으로 요약, 학습한 글입니다.


좋은 로깅을 위해 알아야할 13가지

Don’t write logs by yourself

printf을 사용하거나 파일에 직접 입력하지마라. 로깅을 위한 표준 라이브러리를 사용하라.

표준 라이브러리를 사용할 때 장점

  • 어플리케이션의 다른 부분과 잘 조화됨을 보장할 수 있음
  • 올바른 곳에 로그를 남길 수 있음.

표준 라이브러리 종류

  • syslog()
  • log4j, logback


Log at the proper level

어떤 레벨로 로그를 남겨야할지는 어렵다. 대략적인 팁은 다음과 같다

  • TRACE : 프로덕션에서 사용되면 문제가 될 레벨로, 개발 단계에서 버그를 추적할 때 쓰여야한다. VCS에는 커밋하면 안된다
  • DEBUG : 프로그램에서 발생하는 어떤 것이든 남기는 레벨이다. 디버깅에 주로 쓰인다.
  • INFO : 유저가 일으킨 모든 액션에 대해 남긴다. 또는 스케쥴 동작 같은 시스템 동작에도 남긴다
  • NOTICE : error와는 상관없이 프로덕션 상에서 프로그램이 돌아갈 때 알릴만한 이벤트에 대해서 남긴다
  • WARN : error로 바뀔 수 있는 모든 이벤트에 남긴다.
  • ERROR : 모든 error에 대해 남긴다. 예시로는 API error나 internal error가 있다.
  • FATAL : 아주 위험한 이벤트가 발생했을 때 남긴다. 시스템이 네트워크에 연결이 안되거나, 프로그램이 종료되었을 때 남긴다.


Employ the proper log category

대부분의 로깅 라이브러리는 category 지정을 지원한다.

적절한 카테고리를 지정해서 알아보기 쉽게하자


write meaningful log messages

로그를 읽는 사람이 프로그램에 대해 잘 알고 있음을 가정하고 남기는 로그보다 나쁜 것은 없다. 로그를 남길 때는 항상 긴급 상황을 상상하라.

로그를 작성할 때는 코드 컨텍스트에 맞춰 작성하기 마련이다. 하지만 실제로 로그를 읽어야할 때는 그러한 컨텍스트는 없고, 로그는 이해하기 어려워진다.

따라서 로그 메세지에 부가정보를 남기든가, 아니면 해당 작업의 목적이 무엇인지, 바람직한 결과는 무엇인지를 남겨라.

또한 이전 로그에 의존한 내용을 작성하지 마라. 이전 로그는 기록되지 않을 수도 있고, 다른 카테고리나 레벨로 기록되었을 수도 있다.


Write log mesages in english

영어로 작성하면 아스키문자로 작성하였다는 것을 의미한다. 이는 어떤 소프트웨어를 사용하든 잘 작동한다는 것이다. UTF-8으로 작성하였다면 어떤 소프트웨어에서는 잘 작동하지 않을 수도 있다.

만약 영어로 작성하지 않을 때는 의미있는 에러 코드를 앞에 작성하라.


Add context to your log messages

만약 로그 메세지가 아래와 같다고 해보자.

1
+2
+3
+
Transaction failed
+User operation succeds
+java.lang.IndexOutOfBoundsException
+

왜 실패했고 성공했는지 알수가 없다. 이처럼 적절한 컨텍스트가 없는 로그는 노이즈다. 따라서 로그는 아래와 같이 작성되어야한다.

1
+2
+3
+
Transaction 2346432 failed : cc number checksum incorrect
+User 54543 successfully registered e-mail user@domain.com
+IndexOutOfBoundsException: index 12 is greater than collection size 10
+

이를 위해선 MDC라는 자바 로깅 라이브러리가 사용될 수 있다. MDC는 한 thread에서 공유되는 연관 배열이다. 로거 설정으로 MDC 내용물이 항상 로그에 남도록 설정할 수 있다. 만약 프로그램이 per-thread paradigm이라면 context를 남기는 것은 MDC를 이용하면 쉽다.

다만 MDC는 비동기 로깅 스키마하고는 잘 동작하지 않는다.


Log in machine parseable format

로그 파일을 사람이 일일히 읽기에 힘들 수도 있다. 따라서 자동화 프로그램으로 로그를 읽으려하는데 파싱하기 어려운 형태면 프로그램을 만들 때 문제가 될 수 있다.

  • 파싱하기 어려운 로그

    1
    +
    2013-01-12 17:49:37,656 [T1] INFO  c.d.g.UserRequest  User 1334563 plays 4 of spades in game 23425656
    +
  • 파싱하기 좋은 로그

    1
    +
    2013-01-12 17:49:37,656 [T1] INFO  c.d.g.UserRequest  User plays {'user':1334563, 'card':'4 of spade', 'game':23425656}
    +


But make the logs human-readable as wwell

로그는 파싱하기는 쉬워야하지만 사람이 읽기에도 편해야한다. 사람도 로그를 읽기 때문이다.

  • 표준 date, time 포맷을 사용하라 (ISO8601)
  • UTC나 offset을 더한 지역 timestamp를 사용하라
  • log level를 적절히 사용해라
  • 로그의 세분화를 위해 로그를 서로 다른 타겟에 다른 레벨로 분리하라
  • 예외를 로깅할 땐 stack trace를 포함하라
  • multi-thread 어플리케이션에선 스레드 이름을 남겨라


Don’t log to much or too little

로그 양에는 적절한 밸런스가 중요하다. 이를 달성하기위한 마법의 규칙은 없다.

대신 처음엔 많이 로그를 남겼다가, 실제 배포 후 분석하여 로그를 줄이거나 늘리는 방법으로 조절할 수 있다. 특히 트러블슈팅을 할 때, 더 늘리거나 줄였으면 하는 부분을 고쳐가며 조절하면 좋다. 이 과정에서 ops와 devs 간의 커뮤니케이션이 필요하다


Think of tour audience

로그를 읽는 사람이 누구냐에 따라 남길 메세지, 컨텍스트, 카테고리, 레벨이 달라진다.

  • end-user 일수도 있다 (ex.- 데스크탑 앱일 경우)
  • 시스템 관리자일수도 있다.
  • 개발자일수도 있다


Don’t log for troubleshooting purposes only

로그는 트러블슈팅 이외에 다른 목적으로도 쓰일 수 있다.

  • auditing : 비즈니스 요구사항에 필요할 수 있다. 의미있는 이벤트를 잡아 경영 전략을 세우는데 사용할 수 있다.
  • profiling : 로그에 timestamp가 있다면, 프로그램을 profiling하는데 좋은 도구가 될 수 있다. 성능 측정 등의 지표로 사용 될 수 있다.
  • statistics : 프로그램의 통계를 만드는데 사용할 수 있다.


Avoid vendor lock-in

한가지 로깅 라이브러리나 프레임워크에 종속되지 말고, 원할 때 바꿀 수 있어야한다.

로깅 라이브러리를 감싼 wrapper를 만들고, 그 wrapper의 인터페이스로 로그를 남기면 교체를 하고 싶을 때 그 wrapper만 수정하면 되므로 교체가 수월하다.

slf4j 같은 라이브러리는 로깅 프레임워크 교체를 수월하게 해주는 표준화된 추상화를 제공한다.


Don’t log sensitive information

password, 카드번호, 주민번호와 같은 민감한 정보는 로그에 남겨선 안된다. 유출되거나 악용되면 큰일나기 때문이다.

무엇을 로그로 남기면 안되는지에 대한 법도 존재한다.


출처

https://www.dataset.com/blog/the-10-commandments-of-logging/

This post is licensed under CC BY 4.0 by the author.

SpEL - Spring Expression Language

mybatis.type-aliases-package

Comments powered by Disqus.

diff --git "a/posts/devops-Canary-\355\205\214\354\212\244\355\212\270/index.html" "b/posts/devops-Canary-\355\205\214\354\212\244\355\212\270/index.html" new file mode 100644 index 000000000..a1f146e3b --- /dev/null +++ "b/posts/devops-Canary-\355\205\214\354\212\244\355\212\270/index.html" @@ -0,0 +1 @@ + Canary 테스트 | 디피의 개발일지
Posts Canary 테스트
Post
Cancel

Canary 테스트

[devops] Canary 테스트

안정적인 버전을 릴리즈하기 전에 테스트 버전을 일부 사용자에게 배포하는 것을 말한다.

만약 카나리 버전에 심각한 버그가 발생한다해도 사용하는 사용자가 적기 때문에 피해를 최소화할 수 있다. 또한 안정적인 버전과 테스트 버전이 모두 배포된 상태이기 때문에 A/B 테스트가 가능하다.

canary_testing

유저가 직접 카나리 버전을 설치하는 경우도 있지만, 유저는 모르고 사용하도록 할 수 있다. 일부의 유저만 새로운 버전으로 서비스를 제공하는 방식이다.


출처

https://codechacha.com/ko/what-is-canary-development-test/

This post is licensed under CC BY 4.0 by the author.

blue-green 배포

Helm

Comments powered by Disqus.

diff --git a/posts/devops-HPA/index.html b/posts/devops-HPA/index.html new file mode 100644 index 000000000..d0dc72e25 --- /dev/null +++ b/posts/devops-HPA/index.html @@ -0,0 +1,27 @@ + HPA | 디피의 개발일지
Posts HPA
Post
Cancel

HPA

[devops] HPA

HorizontalPodAutoscaler의 약자로, k8s에서 CPU 사용률을 체크하여 Pod의 개수를 스케일링하는 기술이다. 지정한 메트릭을 controller가 체크하여 부하에 따라 필요한 pod의 replica 수가 되도록 자동으로 pod 수를 늘리거나 줄일 수 있는 기술이다.


Auto scaling

Auto scaling은 사용자가 정의한 주기(스케줄링)나 이벤트(모니터링 알람)에 따라 서버를 자동으로 생성하거나 삭제한다. 서비스 트래픽에 따라 서버를 늘리거나 줄여 발생하는 요금을 최소화한다.

![0FW7GEykC2wItvKxP](https://miro.medium.com/max/1400/0fMKxP-l3N6lO97rV)

k8s에서는 다음과 같은 알고리즘을 통해 적절한 pod 개수를 유지한다.

  • Horizontal Pod Autoscaler 컨트롤러는 원하는(desired) 메트릭 값과 현재 메트릭 값 사이의 비율로 작동한다.
  • 원하는 replica 수 = ceil[현재 replica 수 * (현재 메트릭 값 / 원하는 메트릭 값)]
    • ex) 현재 메트릭 값이 200m이고, 원하는 값이 100m인 경우 replica 수는 두배가 된다. 만약 현재 값이 50mdㅣ면 replica 수는 절반이 된다.


Scale 정책 설정

Deployment 설정 예시

초기 Replica(Pod) 개수를 1개로 설정

![1rRw2_cr-91eWD15rk-NViQ](https://miro.medium.com/max/1222/1rRw2_cr-91eWD15rk-NViQ.webp)

HPA 설정 예시

리소스 상태에 따라서 10개까지 scaling 가능하도록 설정

![1gpX_9eCEwnIXlGxWxOe9tw](https://miro.medium.com/max/1340/1gpX_9eCEwnIXlGxWxOe9tw.webp)


메트릭 수집 방법

auto scaling을 위해서는 k8s에서 서버 메트릭 정보를 수집할 수 있어야한다. k8s에서는 metric server를 통해 pod, node에 대한 리소스 자원들에 대한 metric 정보들을 수집한다.

Metric server가 수집한 metric 정보를 k8s aggregator에게 노티하는 방식으로 metric 정보가 관리된다. 만약 metric 정보를 얻고 싶다면 별도의 tool을 이용해서 aggregator에 있는 metric 정보들을 가져가게 된다.

이러한 metric server는 기본적은 k8s component에 포함되어있지 않기에 별도의 적용이 필요하다.

1
+
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
+

정상적으로 설치됐다면 kubectl top 명령어를 사용할 수 있다. 이 명령도 마찬가지로 metric 정보를 수집한 것을 바탕으로 보여준다.


VPA(Vertical Pod Autoscaler)

k8s에서 metric 정보를 기반으로 auto scaling을 지원해주는 component에는 HPA 외에도 VPA가 있다. 둘의 차이점은 다음과 같다.

  • HPA : Scale out에 대한 기능 제공. pod가 배포되는 개수를 높여준다.
  • VPA : Scale up에 대한 기능 제공. pod의 리소스 할당량을 높여준다.

VPA에서는 metric을 기준으로 리소스를 늘려야한다고 판단되면, 기존 pod의 리소스 할당량을 높여주는 방식으로 작동한다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+
recommendation:
+	containerRecommendations:
+	- containerName: nginx
+  	lowerBound:
+    	cpu: 40m
+    	memory: 3100k
+  	target:
+    	cpu: 60m
+    	memory: 3500k
+  	upperBound:
+    	cpu: 831m
+    	memory: 8000k
+

하지만 여기에는 큰 단점이 있다. 바로 리소스 제한을 변경시키기 위한 방법이 Restart만 존재 한다는 점이다. 즉, 서비스 중인 pod가 운영정지될 수도 있다는 말이다. 따라서 HPA와 같이 기존 pod는 유지하되 새로운 pod를 추가하여 서비스를 중단시키지 않는 방식이 선호된다.


k8s traffic 분산 방법

그럼 k8s에서는 어떻게 클라이언트 트래픽을 여러 pod들로 분산할까?

kubernetes 공식문서에 따르면, k8s는 iptable를 사용하여 pod들로 트래픽을 분산한다. 이때 발생하는 일은 다음과 같다.

  1. 사용자가 kind:service object를 생성한다
  2. k8s는 virtual clusterIP를 만들고, kube-proxy deamonset에게 iptable을 업데이트하라고 지시한다.
  3. virtual clusterIP로 온 요청은 pod ip로 로드밸런싱 된다.

로드밸런싱 policy는 기본적으로 round robin 방식이지만, 100% 정확한 표현은 아니다. 자세한 얘기는 다음 게시글에서 확인할 수 있다


출처

  • https://medium.com/dtevangelist/k8s-kubernetes%EC%9D%98-hpa%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%EC%98%A4%ED%86%A0%EC%8A%A4%EC%BC%80%EC%9D%BC%EB%A7%81-auto-scaling-2fc6aca61c26
  • https://huisam.tistory.com/entry/k8s-hpa
  • https://stackoverflow.com/questions/54865746/how-does-k8s-service-route-the-traffic-to-mulitiple-endpoints
This post is licensed under CC BY 4.0 by the author.

Helm

checked vs unchecked exception

Comments powered by Disqus.

diff --git a/posts/devops-Helm/index.html b/posts/devops-Helm/index.html new file mode 100644 index 000000000..582459672 --- /dev/null +++ b/posts/devops-Helm/index.html @@ -0,0 +1,81 @@ + Helm | 디피의 개발일지
Posts Helm
Post
Cancel

Helm

Helm

Kubernetes 패키지 관리를 도와주는 도구. node.js의 npm과 같은 역할을 수행한다.

Helm을 사용하면 쿠버네티스 클러스터에서 동작하도록 작성된 패키지들을 관리할 수 있다. 즉, Helm을 사용하면 클러스터에 배포한 애플리케이션을 쉽게 설치, 업데이트, 삭제할 수 있다.

일반적으로 쿠버네티스는 여러 오브젝트로 구성되어있는데, 만약 애플리케이션을 새로 업데이트하고 싶으면, 애플리케이션을 포함하는 모든 오브젝트마다 kubectl 명령을 날려야하는데, Helm을 사용하면 하나의 명령으로 애플리케이션을 배포할 수 있다.

주요 개념

helm은 Chart, Repository, Release 로 구성되어있으며 다음과 같이 연계된다.

K8s cluster 내부에 Helm chart를 원하는 repository에서 검색후 설치 -> 각 설치에 따른 새로운 Release 생성

각 구성요소는 다음과 같다.

Chart(차트)

Helm 패키지로, k8s cluster에서 애플리케이션이 기동되기 위해 필요한 모든 리소스들을 포함. 여러 쿠버네티스 오브젝트를 하나로 묶은 것이라 볼 수 있다.

특정 디렉터리 하위에 모여있는 파일들을 통틀어서 chart라고 부른다. 애플리케이션을 Helm으로 배포하려면 애플리케이션을 정의하는 Helm 차트를 작성해야한다. Chart 구성에 관한 자세한 내용은 공식문서에서 확인 가능하며 기본적으로 아래와 같다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
chart/
+	Chart.yaml               # 차트의 정보를 담고 있음.
+	LICENSE                  # OPTIONA
+	README.md                # OPTIONAL
+	requirements.yaml        # OPTIONAL: 차트의 의존성을 포함한 파일
+	values.yaml              # 배포때마다 바뀔 수 있는 값을 포함
+	charts/                  # 애플리케이션의 의존성들의 helm chart를 저장
+					# ex) 애플리케이션 helm chart 설치시 필요하면 mysql helm chart를 저장
+	templates/               # 쿠버네티스 오브젝트 템플릿의 디렉터리. 실제 배포시 필요함.
+					# 설정값과 결합하여 쿠버네티스 메니페스트를 생성하는데 사용한다.
+	templates/NOTES.txt      # OPTIONAL: A plain text file containing short usage notes
+

위와 같은 구조에 version 정보가 붙어서 tgz 형태로 압축 패키징 된다. ex) nginx-1.2.3.tgz

Repository(저장소)

차트 저장소로, 차트를 모아두고 공유하는 장소

Release(릴리즈)

K8s cluster에서 구동되는 차트 인스턴스. 일반적으로 동일한 chart를 여러 번 설치할 수 있고 이는 새로운 Release로 관리되게 된다. Release 될 때 패키지된 차트와 config가 결합되어 정상 실행되게 된다.


Chart.yaml

1
+2
+3
+4
+5
+
apiVersion: v1
+appVersion: "1.0"
+description: A Helm chart for Kubernetes
+name: mychart
+version: 0.1.0
+
  • apiVersion : helm chart api 버전
    • helm 3 이상이 필요하면 v2 여야함.
    • helm 3 이전을 사용하면 v1을 사용.
  • appVersion : 어플리케이션 버전을 나타내는 필드. 정보만 제공하고 차트 버전 계산에 사용되지 않음,
  • description : 프로젝트 설명
  • name : 차트명
  • version : semver 2 버전. 해당 차트의 버전을 나타내는 필드.
  • kubeVersion : 호환되는 쿠버네티스 버전의 SemVer 범위


values.yaml

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+
# Default values for mychart.
+# This is a YAML-formatted file.
+# Declare variables to be passed into your templates.
+
+replicaCount: 1
+
+image:
+  repository: nginx
+  tag: stable
+  pullPolicy: IfNotPresent
+
+imagePullSecrets: []
+nameOverride: ""
+fullnameOverride: ""
+

templates에서 사용할 값들을 저장해놓은 곳. go template


templates

차트를 작성할 때 가장 중요한 부분중 하나는 templates/ 디렉터리이다. 이 디렉터리의 템플릿들이 실제로 생성할 쿠버네티스 오브젝트를 정의하기 때문이다.

Helm 템플릿은 Go의 템플릿 방식을 따른다. \{\{\}\}로 감까진 영역을 템플릿 디렉티브라고 하는데, 이 템플릿 디렉티브를 활용하여 어떤 값을 특정 값으로 치환하거나 if/else와 같은 컨트롤 구조를 나타낼 수 있다. 또한 함수를 사용할 수도 있다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: \{\{ include "test-chart.fullname" . \}\}
+  labels:
+\{\{ include "test-chart.labels" . | indent 4 \}\}
+...
+\{\{- if .Values.ingress.enabled -\}\}
+
+

chart 템플릿 작성 가이드

templates/ 디렉터리 규칙

링크

  1. templates/ 아래에 있는 모든 파일은 NOTES.txt를 제외하곤 전부 쿠버네티스 메니페스트로 다뤄진다.
  2. _로 시작하는 파일은 쿠버네티스 메니페스트로 다뤄지지 않는다. 쿠버네티스 오브젝트를 만들지도 않고, 다른 차트 메니페스트에서 사용한다.


Helm 작성법 링크


출처

  • https://tech.ktcloud.com/51
  • https://reoim.tistory.com/entry/Kubernetes-Helm-%EC%82%AC%EC%9A%A9%EB%B2%95
This post is licensed under CC BY 4.0 by the author.

Canary 테스트

HPA

Comments powered by Disqus.

diff --git "a/posts/devops-blue-green-\353\260\260\355\217\254/index.html" "b/posts/devops-blue-green-\353\260\260\355\217\254/index.html" new file mode 100644 index 000000000..447c24850 --- /dev/null +++ "b/posts/devops-blue-green-\353\260\260\355\217\254/index.html" @@ -0,0 +1 @@ + blue-green 배포 | 디피의 개발일지
Posts blue-green 배포
Post
Cancel

blue-green 배포

[devops] blue-green 배포

애플리케이션의 이전 버전에 있던 사용자 트래픽을 이전 버전(blue)과 거의 동일한 새 버전(green)으로 점진적으로 이전하는 애플리케이션 배포 모델이다. 이때 두 버전 모두 프로덕션 환경에서 실행 상태를 유지한다.

blue에서 green으로 완전히 이전되면 blue는 롤백에 대비하여 대기 상태에 두거나 프로덕션에서 가져온 후 업데이트하여 다음 업데이트의 템플릿으로 삼을 수 있다.

k8s는 클라우드 네이티브 애플리케이션의 마이크로서비스를 패키지화하는 컨테이너의 오케스트레이션을 지원하며, 개발자가 애플리케이션 아키텍처를 처음부터 새로 구축할 필요 없이 재사용할 수 있는 아키텍쳐 패턴 컬렉션은 이러한 k8s를 지원한다.

이러한 k8s 패턴 중 선언적 배포 패턴이 가장 유명하다. 선언적 배포 패턴을 사용하면 k8s 아키텍처에서 pod를 배포할 때 수작업의 부담이 줄어든다.


출처

https://www.redhat.com/ko/topics/devops/what-is-blue-green-deployment

This post is licensed under CC BY 4.0 by the author.

스프링 파일업다운로드

Canary 테스트

Comments powered by Disqus.

diff --git "a/posts/devops-docker-nginx-+-spring-+-react-\354\235\264\353\257\270\354\247\200-\353\271\214\353\223\234-\353\260\217-\354\273\250\355\205\214\354\235\264\353\204\210-\354\213\244\355\226\211/index.html" "b/posts/devops-docker-nginx-+-spring-+-react-\354\235\264\353\257\270\354\247\200-\353\271\214\353\223\234-\353\260\217-\354\273\250\355\205\214\354\235\264\353\204\210-\354\213\244\355\226\211/index.html" new file mode 100644 index 000000000..341b4ae02 --- /dev/null +++ "b/posts/devops-docker-nginx-+-spring-+-react-\354\235\264\353\257\270\354\247\200-\353\271\214\353\223\234-\353\260\217-\354\273\250\355\205\214\354\235\264\353\204\210-\354\213\244\355\226\211/index.html" @@ -0,0 +1,145 @@ + nginx + spring boot+ react로 구성된 앱 dockerfile 작성 | 디피의 개발일지
Posts nginx + spring boot+ react로 구성된 앱 dockerfile 작성
Post
Cancel

nginx + spring boot+ react로 구성된 앱 dockerfile 작성

nginx + spring boot+ react로 구성된 앱 dockerfile 작성

docker flow

docker flow

도커에서 이미지를 빌드하고, 앱이 실행되는 컨테이너를 실행하는 과정은 크게 이미지 빌드, 이미지 푸시, 컨테이너 실행 단계로 나뉜다. nginx + spring boot + react로 구성된 앱을 도커 이미지로 관리하려면 먼저 이미지를 빌드할 dockerfile을 잘 작성해야한다.

먼저 이번 글에서 사용될 프로젝트(빌드 컨텍스트)의 디렉터리 구조부터 알아보자


프로젝트 구조

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+
.
+├── Dockerfile
+├── nginx.conf
+└── startApp.sh
+├── client
+│   ├── README.md
+│   ├── build
+│   ├── node_modules
+│   ├── package-lock.json
+│   ├── package.json
+│   ├── patches
+│   ├── postcss.config.js
+│   ├── public
+│   ├── src
+│   ├── tailwind.config.js
+│   ├── tsconfig.json
+├── server
+│   ├── bin
+│   ├── build
+│   ├── build.gradle
+│   ├── gradle
+│   ├── gradlew
+│   ├── gradlew.bat
+│   ├── logs
+│   ├── out
+│   ├── settings.gradle
+└── ├── src
+

최상단 디렉터리에 Dockerfile과 nginx 설정에 필요한 nginx.conf를 배치해두었다. startApp.sh는 컨테이너가 실행될 때 스프링과 nginx를 실행할 스크립트 파일이다. React와 Spring boot는 각각 client, server 디렉터리에 배치하였다.

이제 본격적으로 dockerfile을 작성해보자. 이때 dockerfile은 multi stage build로 작성하였고, spring boot 빌드 -> react 빌드 -> nginx 및 spring 설정 및 실행 과정을 거치도록 작성하였다.


Spring Boot 빌드

1
+2
+3
+4
+5
+6
+7
+8
+9
+
# syntax=docker/dockerfile:1.2
+# Stage 1 - spring 빌드
+FROM openjdk:18-jdk-alpine AS spring-builder
+COPY ./server/gradlew ./
+COPY ./server/gradle ./gradle
+COPY ./server/settings.gradle ./
+COPY ./server/build.gradle ./
+COPY ./server/src ./src
+RUN ./gradlew bootJar
+
  • FROM : spring boot 빌드를 위해 openjdk를 base image로 삼았다.
  • COPY : 빌드에 필요한 파일들을 빌드 컨텍스트에서 베이스이미지로 복사한다
  • RUN : 빌드 명령어를 실행하여 spring을 빌드한다.

gradlew 실행시 권한 오류가 발생했을 경우

두가지 해결책이 있다.

  • 빌드 컨텍스트, 즉 내 로컬에 있는 gradlew 파일의 권한을 바꾼다.

  • 베이스 이미지 상에서 gradlew 파일 권한을 바꾼다.

    다음과 같이 RUN 하기 전에 바꾸면 됨.

    1
    +2
    +
    RUN chmod +x ./gradlew
    +RUN ./gradlew bootJar
    +


React 빌드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+
# Stage 2 - react 빌드
+FROM node:alpine AS react-builder
+WORKDIR /usr/src/app
+# 의존성 설치를 위한 파일 복사
+COPY ./client/package-lock.json ./
+COPY ./client/package.json ./
+RUN npm ci # 의존성 설치
+# React 빌드를 위한 파일 복사
+COPY ./client/tailwind.config.js ./tailwind.config.js
+COPY ./client/postcss.config.js ./postcss.config.js
+COPY ./client/tsconfig.json ./
+COPY ./client/public ./public
+COPY ./client/src ./src
+RUN npm run build
+
  • FROM : react 빌드를 위해 node를 baseimage로 삼았다
  • WORKDIR : 작업할 디렉터리 지정
  • COPY ~ : React 빌드를 위한 파일 및 npm 의존성 설치를 위한 파일을 base image로 복사한다.
  • RUN : 의존성을 설치한다. 이때 npm ci를 사용하는 것이 package-lock.json을 지키며 의존성을 설치할 수 있어서 좋다. 버전에 큰 문제가 없다면 npm install을 사용해도 된다
  • COPY ~ : 소스 코드를 가져온다.
  • RUN : react를 빌드한다.

COPY, RUN 배치 전략

React 빌드를 위한 dockerfile을 봤을 때, COPY, RUN 커맨드를 어떤 기준으로 배치하였는지 궁금할 수 있다. docker에서는 이미지는 여러 레이어로 구성되어있다. FROM, RUN, COPY, ADD 등의 커맨드를 기준으로 레이어를 구성한다. 레이어는 이미지 빌드 시간을 최소화하기 위한 것으로, 이미지를 빌드할 때 변경되지 않은 레이어는 기존에 저장된 데이터를 사용하고, 변경된 레이어는 그 레이어부터 상위 레이어까지 새로 명령어를 실행한다.

docker layer

따라서 COPY, RUN 커맨드를 사용할 땐, 커맨드의 결과물이 자주 변경되지 않는 것은 가능한 상단에 두어 낮은 레이어에 위치시키면 이미지 빌드 속도를 크게 개선할 수 있다.

따라서 위 react 빌드 dockerfile에서는 상대적으로 자주 변경되지않는 package.json 과 같은 의존성 관리 파일을 상단에 두어 먼저 설치하고, 상대적으로 자주 변경되는 소스파일을 복사하는 COPY 명령어를 후순위에 둔 것이다.

이때 RUN, COPY, ADD 커맨드는 도커 이미지 사이즈에 영향을 주는 커맨드들이다. 잘못 쓰일 수록 도커의 이미지가 필요이상으로 커지므로 가능한 적절히 사용하는 것이 좋다.

참고로 도커 레이어 캐시가 저장되는 곳은 OS마다 다르지만, 기본적으로는 사용자 로컬 환경이다. 이미지 빌드시에 --graph 옵션을 주어 레이어 캐시가 저장되는 위치를 변경할 수 있다.


nginx 및 spring 준비

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+
# Stage 3
+FROM amd64/nginx
+
+# jdk 설치
+WORKDIR /home1/irteam/download
+RUN curl -X GET https://download.oracle.com/java/17/latest/jdk-17_linux-x64_bin.tar.gz --output jdk-17.tar.gz && \
+    tar xvzf jdk-17.tar.gz && \
+    rm jdk-17.tar.gz
+
+# 어플리케이션 준비
+COPY ./nginx.conf /etc/nginx/nginx.conf
+COPY --from=spring-builder /build/libs/*.jar /app/server/spring-server.jar
+COPY --from=react-builder /usr/src/app/client/build /app/client/build
+
  • FROM : amp64/nginx 를 base image로 삼는다.
  • RUN curl ~ : spring 실행에 필요한 jdk를 다운로드 받고, 압축해제한다
    • RUN 커맨드는 레이어를 생성하고, 도커 이미지 사이즈에 영향을 준다. 만약 curljdk파일을 가져오는 작업과, 압축해제, 삭제 작업을 분리하여 RUN으로 실행하면 어떻게 될까?
    • 하나하나가 개별의 레이어로 저장되고, 최종적으로 필요없는 압축 해제 전의 jdk 파일마저 이미지에 포함되게 된다.
    • 따라서 최대한 이미지 사이즈를 줄이기 위해서는 && 을 사용하여 한줄에 명령어를 모두 작성하였다.
  • COPY : nginx.conf 파일을 가져와 nginx 설정을 하고, 어플리케이션 실행을 위한 파일을 이전 빌더로부터 가져온다.


실행

1
+2
+3
+
# 실행 스크립트
+COPY ./startApp.sh ./
+ENTRYPOINT ["./startApp.sh"]
+
  • 어플리케이션 실행을 위한 실행 파일을 가져오고, ENTRYPOINT로 커맨드를 작성하여 컨테이너가 실행하자마자 실행 스크립트가 실행시킨다.
  • 실행 스크립트는 아래와 같다.
1
+2
+3
+4
+
#!/bin/sh
+
+nginx
+/jdk-17.0.5/bin/java -jar /app/server/spring-server.jar
+


This post is licensed under CC BY 4.0 by the author.

SockJS

kubernetes

Comments powered by Disqus.

diff --git a/posts/devops-ingress/index.html b/posts/devops-ingress/index.html new file mode 100644 index 000000000..32afa0938 --- /dev/null +++ b/posts/devops-ingress/index.html @@ -0,0 +1,35 @@ + 쿠버네티스 Ingress | 디피의 개발일지
Posts 쿠버네티스 Ingress
Post
Cancel

쿠버네티스 Ingress

쿠버네티스 Ingress

ingress는 일반적으로 외부로부터 서버 내부로 유입되는 네트워크 트래픽을 뜻한다.

쿠버네티스에서는 ingress라는 리소스 오브젝트가 존재한다. 쿠버네티스의 ingress는 외부에서 쿠버네티스 클러스터 내부로 들어오는 http, https 요청을 어떻게 처리할지 정의한다. 즉, 외부에서 쿠버네티스에서 실행 중인 deployment와 service에 접근하기 위한 일종의 gateway 같은 역할을 담당한다.

ingress를 사용하지 않으면 NodePort나 LoadBalancer를 사용하여 외부 요청을 받을 수 있다. 하지만 NodePortLoadBalancer는 일반적으로 L4(TCP, UDP) 에서의 요청을 처리하며, 네트워크 요청에 대한 세부적인 처리 로직을 구현하기는 한계가 있다.

kubernetes-loadbalancer-services

쿠버네티스에서 ingress를 사용하면 L7에서의 요청을 처리할 수 있다.

  • L7 로드 밸런싱
  • TLS/SSL 인증서 처리
  • path에 따른 라우팅


Ingress와 Ingress Controller

쿠버네티스에서 ingress를 사용하기위해서는 다음 두가지가 필요하다

  • kind: Ingress로 정의되는 Ingress 오브젝트
  • Ingress 규칙이 적용될 Ingress Controller

Ingress 오브젝트

Ingress를 정의하는 yaml 파일은 다음과 같다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+
apiVersion: extensions/v1beta1
+kind: Ingress
+metadata:
+  name: nginx-ingress
+  annotations:
+    nginx.ingress.kubernetes.io/rewrite-target: /
+spec:
+  rules:
+  - host: callmeshin75.com   # 1
+    http:  # 2
+      paths:
+      - path: /api/hostname-service # 3
+        backend:
+          serviceName: hostname-service
+          servicePort: 80
+
  1. callmeshin75.com 호스트명으로 접근하는 네트워크 요청에 대해서 ingress 규칙을 적용
    • host를 지정하지 않으면 지정된 IP 주소를 통해 들어오는 모든 인바운드 HTTP 트래픽에 규칙이 적용된다.
  2. http 프로토콜 사용
  3. /api/hostname-service 경로로 접근하는 요청을 대상으로
  4. hostname-service라는 서비스의 80 포트로 요청을 전달

위 yaml 파일로부터 ingress를 생성해도 아무 일도 일어나지 않는다. ingress는 단지 ingress 규칙을 정의하는 선언적인 오브젝트일 뿐 외부 요청을 받아들이는 실제 서버가 아니기 때문이다.

Ingress는 Ingress controller라고 불리는 서버 컨테이너에 적용되어야만 Ingress에 적용된 규칙이 활성화된다. 즉 Ingress controller는 외부로부터 네트워크 요청을 수신했을 때, Ingress 규칙에 기반해 이 요청을 어떻게 처리할지를 결정한다.

Ingress controller

ingress controller

Ingress는 직접 생성해 사용할수도, 클라우드 플랫폼에 위임할 수도 있다. 직접 구동하려면 Nginx 웹 서버 기반의 Nginx Ingress Controller를 사용할 수 있고, 클라우드 플랫폼에 위임하려면 Google Kubernetes Engine을 사용할 수도 있다.

Ingress Controller는 Ingress보다 먼저 생성할 경우, 쿠버네티스로부터 Ingress의 생성을 watch하고 있으므로, Ingress가 생성되면 자동으로 Nginx에 등록된다. 자세한 내용은 이 포스트에 나와있다.

Nginx ingress controller에 관한 자세한 내용은 공식문서를 참고하면 된다. (ingress controller 생성에 사용되는 mandatory.yaml)

ingress 상태 확인

kubectl get ingress로 생성한 ingress의 상태를 확인할 수 있다.

1
+2
+
NAME           CLASS         HOSTS   ADDRESS         PORTS   AGE
+test-ingress   external-lb   *       203.0.113.123   80      59s
+
  • HOSTS : ingress 설정에서 세팅한 host 필드의 값
  • ADDRESS : ingress controller가 ingress를 충족시키기 위해 할당한 IP. 할당될 때까지는 주소는 pending으로 표시된다.


출처

https://kubernetes.io/ko/docs/concepts/services-networking/ingress/

https://blog.naver.com/alice_k106/221502890249

This post is licensed under CC BY 4.0 by the author.

nginx에서 환경변수 사용하는 방법

php 기초 강의

Comments powered by Disqus.

diff --git a/posts/devops-istio/index.html b/posts/devops-istio/index.html new file mode 100644 index 000000000..cf5be6511 --- /dev/null +++ b/posts/devops-istio/index.html @@ -0,0 +1 @@ + istio | 디피의 개발일지
Posts istio
Post
Cancel

istio

Istio

istio는 service mesh를 위한 tool입니다. istio를 사용하면 MSA(Micro Service Architecture)를 적용할 때 반복적으로 설정해야하는 L7/모니터링을 쉽고 간편하게 설정할 수 있습니다.

Service mesh

Service mesh는 API 등을 사용하여 마이크로 서비스 간 통신을 안전하고, 빠르고, 신뢰할 수 있게 만들기 위해 설계된 전용 인프라 계층이다.

Service mesh는 보통 application 서비스에 경량화 프록시를 사이드카 방식으로 배치하여 서비스 간 통신을 제어하며, Service Discovery, Load Balancing, Dynamic Request Routing, Circuit Breacking, Retry and Timeout, TLS, Distributed Tracing, Metric 수집, Access Control, A/B Testing 기능 등을 지원한다.

MSA 시스템에서 수십개의 마이크로 서비스가 동작할 수 있기에 관리하기가 매우 복잡하다. 또한 인스턴스가 수행되는 네트워크 간의 레이턴시, 신뢰성, 안전성 등을 보장할 수도 없다. 이러한 문제점들은 어플리케이션 계층(7계층)에서 해결할 수도 있지만, 그렇게 되면 application 언어 및 런타임에 종속성이 생기고, 기능 변경 등을 할 때마다 비용이 발생한다.

따라서 MSA 시스템에서는 인프라 레벨에서 별도의 소스 수정 없이 안정적으로 관리할 수 있는 Service mesh가 필요하다.


istio 정의

istio는 Service mesh를 구현할 수 있는 오픈소스 솔루션이다. istio를 사용하면 서비스 코드 변경 없이 로드밸런싱, 서비스 간 인증, 모니터링 등을 적용하여 마이크로 서비스를 쉽게 관리할 수 있다.

마이크로 서비스 간의 모든 네트워크 통신을 담당할 수 있는 프록시인 Envoy를 사이드카 패턴으로 마이크로 서비스들에 배포한다음, 프록시들의 설정값 저장 및 관리/감독을 수행하고, 프록시들에 설정값을 전달하는 컨트롤러 역할을 수행한다.

각 마이크로 서비스에 사이드카 패턴으로 배포된 envoy 프록시를 Data plane이라고 하고, data plane을 컨트롤하는 부분을 control plane이라고 한다.


istio 구조/구성요소

arch

Data plane

envoy proxy 세트로 구성되어있다. envoy는 사이드카 방식으로 각각의 마이크로 서비스에 배포되어 서비스로 들어오고 나가는 모든 트래픽을 통제하게 된다. envoy를 통해서 서비스를 호출할 때 호출하는 서비스의 IP주소는 파일럿(Pilot)에 저장된 엔드포인트 정보를 활용한다.

Control Plane

Data plane의 Envoy를 컨트롤하는 부분이다. Control plane은 Istiod라는 하나의 모듈로 되어있다.(istio 1.4 버전까지는 별도로 되어있었음)

  • Pilot : Envoy 설정 관리를 수행하는 모듈. Envoy가 호출하는 서비스의 주소를 얻을 수 있는 서비스 디스커버리 기능을 제공. 서비스 트래픽 라우팅 기능 제공. 서비스간 호출 시 재시도(retry), 서킷브레이커, 타임아웃 등의 기능 제공
  • Citadel : 보안 관련 기능을 수행. 사용자 인증, 인가 등을 통한 서비스/엔드 유저 간 인증 강화. TLS(SSL)을 이용한 통신 암호화 및 인증서 관리
  • Galley : istio의 구성 및 설정 검증, 배포 관리 수행.


Istio 주요 특징

트래픽 관리

istio의 간편한 규칙 설정과 트래픽 라우팅 기능을 통해 서비스 간의 트래픽 흐름과 API 호출을 제어할 수 있다. 또한 서킷 브레이커, 타임아웃, 재시도 기능과 같은 서비스 레벨의 속성 구성을 단순화하고, 백분율 기반으로 트래픽을 분할하여 A/B test, canary rollout, staged rollout 과 같은 작업을 쉽게 설정할 수 있다.

보안

istio는 기본적인 보안 통신 채널을 제공하며, 대규모 서비스 통신의 인증, 권한부여, 암호화 등을 관리한다. istio를 사용하면 기본적으로 서비스 통신은 보호되기 때문에, 다양한 프로토콜이나 런타임에서 어플리케이션 변경을 거의하지 않고 일관된 정책을 시행할 수 있다. 쿠버네티스 네트워크 정책과 함께 사용하여 pod 간 통신을 보호하는 기능 등의 이점이 있다.

관찰 가능성

트레이싱, 모니터링, 로깅 기능으로 서비스 메쉬에 배포된 서비스들에 대해 더욱 자세히 파악할 수 있다.

플랫폼 지원

istio는 플랫폼에 독립적이며 다양한 환경에서 실행되도록 설계되었다.

통합과 사용자 정의

istio의 구성 요소들은 확장 및 커스터마이징을 통해 ACL, 로깅, 모니터링, 할당량, 감사 등을 위한 기존 솔루션과 통합될 수 있다.

요약

istio는 마이크로 서비스에서 L7 모니터링, 서비스간 인증, 로드밸런싱을 쉽고 빠르게 붙일 수 있는 오픈소스 솔루션이다. 마이크로 서비스 간 통신을 위해 설계된 인프라계층인 service mesh를 구현한다. 각 마이크로 서비스에는 사이드카 패턴으로 배포된 envoy 프록시가 있고, 이 envoy 프록시들을 Data plane이라고 한다. 그리고 Data plane을 컨트롤 하는 부분을 control plane이라고 한다. envoy 프록시는 사이드카 방식으로 각 마이크로 서비스에 들어오고 나가는 모든 트래픽을 통제한다.


참고문서 1

This post is licensed under CC BY 4.0 by the author.

사이드카 패턴

pinpoint

Comments powered by Disqus.

diff --git "a/posts/devops-kubenetes-\354\213\244\354\212\265/index.html" "b/posts/devops-kubenetes-\354\213\244\354\212\265/index.html" new file mode 100644 index 000000000..dbc6c8cc9 --- /dev/null +++ "b/posts/devops-kubenetes-\354\213\244\354\212\265/index.html" @@ -0,0 +1,763 @@ + kubernetes 실습 | 디피의 개발일지
Posts kubernetes 실습
Post
Cancel

kubernetes 실습

이 포스트는 subicura 님의 kubenetes 안내서을 읽으며 요약/기록한 게시글입니다.

Kubenetes 실습

Pod 생성하기

YAML로 설정파일 작성하여 생성하기

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+
apiVersion: v1
+kind: Pod
+metadata:
+  name: echo
+  labels:
+    app: echo
+spec:
+  containers:
+    - name: app
+      image: ghcr.io/subicura/echo:v1
+
정의설명
apiVersionAPI 서버 버전v1, app/v1, autoscaling/v1
kind종류Pod, ReplicaSet, Deployment, Service
metadata메타데이터name과 label, annotation으로 구성
spec상세명세리소스 종류마다 다름.

apiVersion, kind, metadata, spec은 리소스를 정의할 때 반드시 필요한 요소이다.

apiVersion은 API 서버의 버전을 나타내는 필드로, apiVersion에 따라 지원하는 리소스가 달라 주의해야한다. 어떤 버전을 사용해야하는지 궁금하면 이 게시글을 참고하자.

위 YAML 파일을 작성하고 다음과 같이 테스트해볼 수 있다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+
# Pod 생성
+kubectl apply -f echo-pod.yml
+
+# Pod 목록 조회
+kubectl get pod
+
+# Pod 로그 확인
+kubectl logs echo
+kubectl logs -f echo
+
+# Pod 컨테이너 접속
+kubectl exec -it echo -- sh
+# ls
+# ps
+# exit
+
+# Pod 제거
+kubectl delete -f echo-pod.yml
+

컨테이너 상태 모니터링

컨테이너 실행과 실제 서비스 준비는 약간 차이가 있다. 서버를 실행하고 초기화를 거친다음 실제로 접속이 가능할 때 서비스가 준비되었다고 한다.

쿠버네티스는 libenessProbe와 readnessProbe로 컨테이너생성과 서비스 준비를 체크하여 초기화하는 동안 서비스 되는 것을 막을 수 있다. 보통은 둘을 동시에 적용한다.

livenessProbe

livenessProbe는 컨테이너가 정상적으로 동작하는지 체크하고 정상적으로 동작하지 않는다면 컨테이너를 재시작하여 문제를 해결한다. 정상상태인지 체크하는 방식에는 여러 가지가 있는데, http get으로 확인하는 방법은 다음과 같다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+
apiVersion: v1
+kind: Pod
+metadata:
+  name: echo-lp
+  labels:
+    app: echo
+spec:
+  containers:
+    - name: app
+      image: ghcr.io/subicura/echo:v1
+      livenessProbe:
+        httpGet:
+          path: /not/exist
+          port: 8080
+        initialDelaySeconds: 5
+        timeoutSeconds: 2 # Default 1
+        periodSeconds: 5 # Defaults 10
+        failureThreshold: 1 # Defaults 3
+

위 예시는 존재하지 않는 path와 port를 입력하여 일부러 실패에 이르게 하였다. kubectl apply로 pod을 생성하고 시간이 지난 후 kubectl get pod을 통해 pod의 상태를 확인하면 CrashLoopBackOff 상태에 빠진 것을 알 수 있다.

http get이외에도 exec, tcpSocket방법으로도 확인이 가능하다

readinessProbe

readinessProbe는 컨테이너가 준비되었는지 체크하고 정상적으로 준비되지 않았다면 pod으로 들어오는 요청을 제외한다. livenessProbe와의 차이점은 문제가 있어도 pod을 재시작하지 않고 pod으로 오는 요청만 제외한다는 점이다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+
apiVersion: v1
+kind: Pod
+metadata:
+  name: echo-rp
+  labels:
+    app: echo
+spec:
+  containers:
+    - name: app
+      image: ghcr.io/subicura/echo:v1
+      readinessProbe:
+        httpGet:
+          path: /not/exist
+          port: 8080
+        initialDelaySeconds: 5
+        timeoutSeconds: 2 # Default 1
+        periodSeconds: 5 # Defaults 10
+        failureThreshold: 1 # Defaults 3
+

livenessProbe와 똑같이 존재하지않는 곳으로 요청을 보냈지만, CrashLoopBackOff에는 빠지지 않고 pod의 READY 상태만 0/1로 유지된다.

livenessProbe + readinessProbe

보통은 둘을 함께 적용한다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+
apiVersion: v1
+kind: Pod
+metadata:
+  name: echo-health
+  labels:
+    app: echo
+spec:
+  containers:
+    - name: app
+      image: ghcr.io/subicura/echo:v1
+      livenessProbe:
+        httpGet:
+          path: /
+          port: 3000
+      readinessProbe:
+        httpGet:
+          path: /
+          port: 3000
+

정상적으로 생성된다.

다중 컨테이너

하나의 pod에는 여러개의 컨테이너를 가질 수도 있다. 하나의 Pod에 속한 컨테이너는 서로 네트워크를 localhost로 공유하고 동일한 디렉터리를 공유할 수 있다.

서로 네트워크를 localhost로 공유할 수 있는 이유는 Pod 안의 모든 네트워크는 동일한 IPC namespace 아래에서 시행되기 때문이다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+
apiVersion: v1
+kind: Pod
+metadata:
+  name: counter
+  labels:
+    app: counter
+spec:
+  containers:
+    - name: app
+      image: ghcr.io/subicura/counter:latest
+      env:
+        - name: REDIS_HOST
+          value: "localhost"
+    - name: db
+      image: redis
+

요청횟수를 redis에 저장하는 웹 어플리케이션이다. 다음과 같이 테스트할 수 있다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+
# Pod 생성
+kubectl apply -f counter-pod-redis.yml
+
+# Pod 목록 조회
+kubectl get pod
+
+# Pod 로그 확인
+kubectl logs counter # 오류 발생 (컨테이너 지정 필요)
+kubectl logs counter app
+kubectl logs counter db
+
+# Pod의 app컨테이너 접속
+kubectl exec -it counter -c app -- sh
+# curl localhost:3000
+# curl localhost:3000
+# telnet localhost 6379
+  dbsize
+  KEYS *
+  GET count
+  quit
+
+# Pod 제거
+kubectl delete -f counter-pod-redis.yml
+

현재틑 터미널로 테스트하였지만, Service를 적용하면 브라우저 테스트를 할 수 있다.


ReplicaSet

pod을 단독으로 만들면 pod에 어떤 문제가 생겼을 때 자동으로 복구되지않는다. 이러한 pod을 정해진 만큼 복제하고 관리하는 것이 ReplicaSet이다.

replicaset

ReplicaSet 만들기

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+
apiVersion: apps/v1
+kind: ReplicaSet
+metadata:
+  name: echo-rs
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: echo
+      tier: app
+  template:
+    metadata:
+      labels:
+        app: echo
+        tier: app
+    spec:
+      containers:
+        - name: echo
+          image: ghcr.io/subicura/echo:v1
+
정의설명
spec.selectorlabel 체크 조건
spec.replicas원하는 pod의 개수
spec.template생성할 pod의 명세

RepliacaSet은 label을 체크해서 원하는 수의 Pod이 없으면 새로운 Pod을 생성한다.

다음과 같이 테스트 가능하다

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
# ReplicaSet 생성
+kubectl apply -f echo-rs.yml
+
+# 리소스 확인
+kubectl get po,rs
+
+# 생성한 pod의 label을 확인
+kubectl get pod --show-labels
+
+# app- 를 지정하면 app label을 제거
+kubectl label pod/echo-rs-tcdwj app-
+
+# 다시 Pod 확인 -> label을 삭제한 pod은 그대로 있고, label과 일치하는 pod이 새로 생김
+kubectl get pod --show-labels
+
+# app=으로 라벨 추가
+kubectl label pod/echo-rs-tcdwj app=echo
+
+# 다시 Pod 확인 -> 새로 생성된 pod이 제거됨.
+kubectl get pod --show-labels
+

ReplicaSet은 다음과 같이 동작한다.

  1. ReplicaSet Controller는 ReplicaSet조건을 감시하면서 현재 상태와 원하는 상태가 다른 것을 체크
  2. ReplicaSet Controller가 원하는 상태가 되도록 Pod을 생성하거나 제거
  3. Scheduler는 API서버를 감시하면서 할당되지 않은 unassigned Pod이 있는지 체크
  4. Scheduler는 할당되지 않은 새로운 Pod을 감지하고 적절한 노드node에 배치
  5. 이후 노드는 기존대로 동작

주의

ReplicaSet은 원하는 개수의 Pod을 유지하는 역할을 담당한다. label을 이용하여 Pod을 체크하므로 label이 겹치지 않게 신경써서 정의해야한다.

실전에서 ReplicaSet을 단독으로 쓰는 경우는 거의 없다. Deployment가 ReplicaSet을 이용하고,주로 Deployment를 이용한다.


Deployment

하나의 배포판을 의미하는 것으로, 쿠버네티스에서 가장 널리 사용되는 오브젝트이다. ReplicaSet을 이용하여 Pod을 업데이트하고 이력을 관리하여 롤백하거나 특정버전으로 돌아갈 수 있다.

Deployment 만들기

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+
apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: echo-deploy
+spec:
+  replicas: 4
+  selector:
+    matchLabels:
+      app: echo
+      tier: app
+  template:
+    metadata:
+      labels:
+        app: echo
+        tier: app
+    spec:
+      containers:
+        - name: echo
+          image: ghcr.io/subicura/echo:v1
+

kind와 metadate.name을 제외하곤 ReplicaSet과 동일한 설정이다.

다음과 같이 테스트 가능하다

1
+2
+3
+4
+5
+
# Deployment 생성
+kubectl apply -f echo-deployment.yml
+
+# 리소스 확인
+kubectl get po,rs,deploy
+

한번 위 yml 파일의 spec.template.spec.containers[0].image를 다음과 같이 변경하고 다시 Deployment를 생성해보자

1
+2
+
...
+          image: ghcr.io/subicura/echo:v2
+

kubectl get po,rs,deploy 명령어로 확인해보면, 새로운 ReplicaSet이 생기고 Pod이 업데이트되는 것을 확인할 수 있다.

Deployment는 새로운 이미지로 업데이트하기 위해 ReplicaSet을 이용한다. 버전을 업데이트하면 새로운 ReplicaSet을 생성하고 해당 ReplicaSet이 새로운 버전의 Pod을 생성한다. 이때 다음과 같은 과정을 거친다.

  1. 새로운 ReplicaSet을 0 -> 1개로 조정하고 정상적으로 Pod이 동작하면 기존 ReplicaSet을 4 -> 3개로 조정한다.
  2. 1을 기존 ReplicaSet이 0개가 될 때까지 반복한다.
  3. 기존 ReplicaSet을 제거한다.

API 서버와의 통신과정은 다음과 같다.

  1. Deployment Controller는 Deployment 조건을 감시하면서 현재 상태와 원하는 상태가 다른 것을 체크
  2. Deployment Controller가 원하는 상태가 되도록 ReplicaSet 설정
  3. ReplicaSet Controller는 ReplicaSet 조건을 감시하면서 현재 상태와 원하는 상태가 다른 것을 체크
  4. ReplicaSet Controller가 원하는 상태가 되도록 Pod을 생성하거나 제거
  5. Scheduler는 API서버를 감시하면서 할당되지 않은 unassigned Pod이 있는지 체크
  6. Scheduler는 할당되지 않은 새로운 Pod을 감지하고 적절한 노드에 배치
  7. 이후 노드는 기존대로 동작

버전관리

deployment는 변경된 상태를 기록한다

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
# 히스토리 확인
+kubectl rollout history deploy/echo-deploy
+
+# revision 1 히스토리 상세 확인
+kubectl rollout history deploy/echo-deploy --revision=1
+
+# 바로 전으로 롤백
+kubectl rollout undo deploy/echo-deploy
+
+# 특정 버전으로 롤백
+kubectl rollout undo deploy/echo-deploy --to-revision=2
+

배포전략 설정

Deployment엔 다양한 배포 전략이 있다. 여기선 롤링 업데이트(RollingUpdate) 방식을 사용할 때 동시에 업데이트하는 Pod의 개수를 변경해보자

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+
apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: echo-deploy-st
+spec:
+  replicas: 4
+  selector:
+    matchLabels:
+      app: echo
+      tier: app
+  minReadySeconds: 5
+  strategy:
+    type: RollingUpdate
+    rollingUpdate:
+      maxSurge: 3
+      maxUnavailable: 3
+  template:
+    metadata:
+      labels:
+        app: echo
+        tier: app
+    spec:
+      containers:
+        - name: echo
+          image: ghcr.io/subicura/echo:v1
+          livenessProbe:
+            httpGet:
+              path: /
+              port: 3000
+

다음과 같이 테스트해보면,

1
+2
+3
+4
+5
+6
+7
+8
+
kubectl apply -f echo-strategy.yml
+kubectl get po,rs,deploy
+
+# 이미지 변경 (명령어로)
+kubectl set image deploy/echo-deploy-st echo=ghcr.io/subicura/echo:v2
+
+# 이벤트 확인
+kubectl describe deploy/echo-deploy-st
+

Pod을 하나씩 생성하지 않고, 한번에 3개가 생성된 것을 확인할 수 있다.

Deployment는 가장 흔하게 사용하는 배포방식이다. 이외에 StatefulSet, DaemonSet, CronJob, Job등이 있지만 사용법은 크게 다르지 않다.


Service

Pod을 자체 IP를 가지고 다른 Pod과 통신할 수 있다. 하지만 쉽게 사라지고 생성되며 그 과정에서 Pod의 IP가 변경되기에 직접 통신하는 방법은 권장하지 않는다.

쿠버네티스는 Pod과 직접 통신하는 대신 별도의 고정된 IP를 가진 서비스를 만들고, 그 서비스를 통해 Pod에 접근한다.

service

Service(ClusterIP) 만들기

ClusterIP는 클러스터 내부에 새로운 IP를 할당하고 여러 개의 Pod을 바라보는 로드밸런서 기능을 제공한다.

서비스 이름을 내부 도메인 서버에 등록하여 Pod 간에 서비스 이름으로 통신할 수 있습니다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+
apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: redis
+spec:
+  selector:
+    matchLabels:
+      app: counter
+      tier: db
+  template:
+    metadata:
+      labels:
+        app: counter
+        tier: db
+    spec:
+      containers:
+        - name: redis
+          image: redis
+          ports:
+            - containerPort: 6379
+              protocol: TCP
+# 하나의 yaml 파일에 여러개의 리소스를 정의할 땐 "---"를 구분자로 사용한다.
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: redis
+spec:
+  ports:
+    - port: 6379
+      protocol: TCP
+  selector:
+    app: counter
+    tier: db
+

다음과 같이 테스트하면, redis라는 이름의 서비스가 생성되었음을 알 수 있다.

1
+2
+3
+4
+
kubectl apply -f counter-redis-svc.yml
+
+# Pod, ReplicaSet, Deployment, Service 상태 확인
+kubectl get all
+

같은 클러스터에서 생성된 Pod이라면 redis라는 도메인으로 redis Pod에 접근할 수 있다.

ClusterIP 서비스의 설정은 다음과 같다

정의설명
spec.ports.port서비스가 생성할 Port
spec.ports.targetPort서비스가 접근할 Pod의 Port(디폴트 : port와 동일)
spec.selector서비스가 접근할 Pod의 label 조건

이제 redis에 접근할 counter앱을 Deployment로 만들어보자

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+
apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: counter
+spec:
+  selector:
+    matchLabels:
+      app: counter
+      tier: app
+  template:
+    metadata:
+      labels:
+        app: counter
+        tier: app
+    spec:
+      containers:
+        - name: counter
+          image: ghcr.io/subicura/counter:latest
+          env:
+            - name: REDIS_HOST
+              value: "redis"    # redis 도메인으로 접근  
+            - name: REDIS_PORT
+              value: "6379"
+

다음과 같이 테스트할 수 있다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+
kubectl apply -f counter-app.yml
+
+# counter app에 접근
+kubectl get po
+kubectl exec -it counter-<xxxxx> -- sh
+# curl localhost:3000
+# curl localhost:3000
+# telnet redis 6379
+  dbsize
+  KEYS *
+  GET count
+  quit
+

Service 생성 흐름

서비스는 각 pod을 바라보는 로드밸런서 역할을 하면서 내부 도메인서버에 새로운 도메인을 생성한다.

서비스는 다음과 같이 동작한다

  1. Endpoint ControllerServicePod을 감시하면서 조건에 맞는 Pod의 IP를 수집
  2. Endpoint Controller가 수집한 IP를 가지고 Endpoint 생성
  3. Kube-ProxyEndpoint 변화를 감시하고 노드의 iptables을 설정
  4. CoreDNSService를 감시하고 서비스 이름과 IP를 CoreDNS에 추가

iptables은 커널 레벨의 네트워크 도구이고, CoreDNS는 빠르고 편리하게 사용할 수 있는 클러스터 내부용 도메인 네임 서버이다. iptables 설정으로 여러 IP에 트래픽을 전달하고, CoreDNS를 이용하여 IP 대신 도메인 이름을 사용한다.

iptables 는 규칙이 많아지면 성능이 느려지는 이슈가 있어, ipvs를 사용하는 옵션도 있다

CoreDNS는 클러스터에서 호환성을 위해 kube-dns라는 이름으로 생성된다.

Endpoint는 서비스의 접속 정보를 가지고 있다. Endpoint의 상태는 다음과 같이 확인이 가능하다

1
+2
+3
+4
+5
+
kubectl get endpoints
+kubectl get ep #줄여서
+
+# redis Endpoint 확인
+kubectl describe ep/redis
+

Service(NodePort) 만들기

NodePort는 클러스터의 모든 노드에 포트를 오픈한다. 여러개의 노드가 있다면 아무 노드로 접근해도 지정한 pod으로 접근할 수 있다.

참고로 NodePort는 ClusterIP의 기능을 기본으로 포함한다.

Service(LoadBalancer) 만들기

NodePort의 단점은 노드가 사라졌을 때 자동으로 다른 노드를 통해 접근이 불가능하다는 점이다. 예를 들어 3개의 노드가 있다면 3개 중에 아무 노드로 접근해도 NodePort로 연결할 수 있지만 어떤 노드가 살아있는지는 알수가 없다.

load balancer

자동으로 살아있는 노드에 접근하기 위해 모든 노드를 바라보는 Load Balancer가 필요하다. 브라우저는 NodePort에 직접 요청을 보내는 것이 아니라 LoadBalancer에 요청하고 LoadBalancer가 알아서 살아있는 노드에 접근하면 NodePort의 단점을 없앨 수 있다.

ClusterIP vs NodePort vs LoadBalancer

  • ClusterIP : 클러스터 내부에서 접근 가능한 ip를 염.
    • ip는 자동으로 생성되고, port는 selector로 걸린 pod의 targetPort를 port로 지정한 값으로 염.
    • 클러스터 내부의 Pod에서만 접근 가능
    • ClusterIP의 name을 도메인으로 사용할 수 있음.
    • 로드밸런서의 역할도 제공한다.
  • NodePort : 클러스터 외부에서 접근 가능한 포트를 염
    • selector로 걸린 pod의 targetPort를 port로 지정한 값으로 ClusterIP를 염. 외부로는 nodePort로 지정한 값으로 염.
    • NodePort는 기본적으로 ClusterIP의 기능을 포함함. 따라서 클러스터 내부의 pod들은, NodePort로 연 port를, NodePort를 생성할 때 지정되는 ClusterIP를 통해 접근가능하다.
    • 분산 노드 애플리케이션에서는 오토스케일링을 이유로 노드들의 네트워크 환경이 동적으로 변한다면, 서비스 디스커버리와 같은 방법으로 클라이언트에서 노드들의 네트워크 엔드포인트르 관리해야함. -> 로드 밸런서로 해결
  • LoadBalancer : NodePort 기능 + 살아있는 노드를 자동으로 찾아 요청을 전달함.
    • selector로 걸린 pod의 targetPort를 port로 지정한 값으로 염. 외부로는 nodePort로 지정한 값으로 염.
    • LoadBalancer는 기본적으로 NodePort의 기능을 포함함.

주의

실전에서 NodePort와 LoadBalancer는 제한적으로 사용한다. 보통 웹 어플리케이션을 배포하면 80 또는 443 포트만 사용하고 하나의 포트에서 여러 개의 서비스를 도메인이나 경로에 따라 다르게 설정하기 때문이다. 이 부분은 Ingress에서 처리할 수 있다.


Ingress

하나의 클러스터에서 여러가지 서비스를 운영한다고 해보자. 이때 NodePort를 사용하여 서비스 개수만큼 포트를 열고 사용자에게 어떤 포트인지 알려주는 방법은 유저 경험에 좋지 않다.

좋은 방법은 같은 포트 아래에서 각 서비스를 path로 구분하는 방법이고, Ingress 사용해 구현한다.

ingress

Ingress 만들기

Ingress 생성 흐름

  1. Ingress ControllerIngress 변화를 체크
  2. Ingress Controller는 변경된 내용을 Nginx에 설정하고 프로세스 재시작

YAML로 만든 Ingress 설정을 단순히 nginx 설정으로 바꾸는 과정을 Ingress Controller가 자동으로 해주는 것이다.

Ingress는 도메인, 경로만 연동하는 것이 아니라 요청 timeout, 요청 max size 등 다양한 프록시 서버 설정을 할 수 있다.


Volume

MySQL과 같은 데이터베이스는 데이터가 유실되지 않도록 반드시 별도의 저장소에 데이터를 저장하고, 컨테이너를 새로 만들 때 이전 데이터를 가져와야한다.

쿠버네티스는 Volume을 사용하여 컨테이너의 디렉토리를 외부 저장소와 연결하고 다양한 플러그인을 지원하여 흔히 사용하는 대부분의 스토리지를 별도 설정없이 사용할 수 있다.

Volume(empty-dir) 만들기

empty-dir은 같은 pod 안에 속한 컨테이너 간 디렉터리를 공유하는 방법으로 보통 사이드카 패턴에서 사용한다. 예를들어 특정 컨테이너에서 생성되는 로그 파일을 별도의 컨테이너가 수집할 수 있다.

아래 예시는 app 컨테이너는 /var/log/example.log에 로그 파일을 만들고, sidecar 컨테이너는 해당 로그파일을 처리하도록 하는 예시다

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+
apiVersion: v1
+kind: Pod
+metadata:
+  name: sidecar
+spec:
+  containers:
+    - name: app
+      image: busybox
+      args:
+        - /bin/sh
+        - -c
+        - >
+          while true;
+          do
+            echo "$(date)\n" >> /var/log/example.log;
+            sleep 1;
+          done
+      volumeMounts:
+        - name: varlog
+          mountPath: /var/log
+    - name: sidecar
+      image: busybox
+      args: [/bin/sh, -c, "tail -f /var/log/example.log"]
+      volumeMounts:
+        - name: varlog
+          mountPath: /var/log
+  volumes:
+    - name: varlog
+      emptyDir: {}
+

다음 명령어로 테스트해볼수 있다.

1
+2
+3
+4
+
kubectl apply -f empty-dir.yml
+
+# sidecar 로그 확인
+kubectl logs -f sidecar -c sidecar
+

hostpath

호스트(node) 디렉터리를 컨테이너 디렉터리에 연결하는 방법이다.

hostpath

아래 예시는 호스트의 /var/log를 컨테이너의 /host/var/log 디렉터리로 마운트한다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+
apiVersion: v1
+kind: Pod
+metadata:
+  name: host-log
+spec:
+  containers:
+    - name: log
+      image: busybox
+      args: ["/bin/sh", "-c", "sleep infinity"]
+      volumeMounts:
+        - name: varlog
+          mountPath: /host/var/log
+  volumes:
+    - name: varlog
+      hostPath:
+        path: /var/log
+

아래 명령어로 마운트된 디렉터리를 확인할 수 있다.

1
+2
+3
+4
+5
+
kubectl apply -f hostpath.yml
+
+# 컨테이너 접속 후 /host/var/log 디렉토리를 확인
+kubectl exec -it host-log -- sh
+ls -al /host/var/log
+


ConfigMap

컨테이너에서 설정 파일을 관리하는 방법은 이미지를 빌드할 때 복사하거나, 컨테이너를 실행할 때 외부 파일을 연결하는 방법이 있다.

쿠버네티스는 ConfigMap으로 설정을 관리한다.

ConfigMap을 만드는 방법을 다음 세가지가 있다. 실습

  • ConfigMap 파일 직접 생성
  • env 파일로 생성
  • YAML 파일로 생성

ConfigMap은 컨테이너에서 volume으로 마운트하여 파일을 가져오는 방식으로 사용한다. 하지만 volume이 아닌 환경변수로 설정할 수도 있다. 이때는 다음과 같이 환경 변수를 작성하는 곳에 configMap을 키로 연결하면 된다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+
apiVersion: v1
+kind: Pod
+metadata:
+  name: alpine-env
+spec:
+  containers:
+    - name: alpine
+      image: alpine
+      command: ["sleep"]
+      args: ["100000"]
+      env:
+        - name: hello
+          valueFrom:
+            configMapKeyRef:
+              name: my-config
+              key: hello
+


Secret

쿠버네티스에서 비밀번호, SSH 인증, TLS Secret와 같은 보안 정보를 관리하는 방법으로, ConfigMap과 유사하지만, 보안 정보를 관리하기 위해 Secret을 별도로 제공한다.

ConfigMap와의 차이점은 데이터가 base64로 저장되는 거 말고는 거의 없다.

주의

Secret은 암호화 되지 않는다. 있는 정보를 그대로 저장하며, etcd에 접근이 가능하면 누구나 저장된 Secret을 확인할 수 있다. vault와 같은 외부 솔루션을 사용하여 보안을 강화할 수 있다.

Secret 만들기



출처

https://subicura.com/k8s/

This post is licensed under CC BY 4.0 by the author.

kubernetes

nginx에서 환경변수 사용하는 방법

Comments powered by Disqus.

diff --git a/posts/devops-kubernetes/index.html b/posts/devops-kubernetes/index.html new file mode 100644 index 000000000..83dede7df --- /dev/null +++ b/posts/devops-kubernetes/index.html @@ -0,0 +1,39 @@ + kubernetes | 디피의 개발일지
Posts kubernetes
Post
Cancel

kubernetes

이 포스트는 subicura 님의 kubenetes 게시글을 읽으며 요약/기록한 게시글입니다.

Kubenetes

쿠버네티스(kubenetes, k8s)는 도커 컨테이너를 쉽고 빠르게 배포/확장하고 관리를 자동화해주는 오픈소스 플랫폼이다. 단순한 컨테이너 플랫폼이 아닌 마이크로 서비스, 클라우드 플랫폼을 지향하고 컨테이너로 이루어진 것들을 손쉽게 담고 관리할 수 있는 그릇 역할을 한다.


쿠버네티스 특징

다양한 배포방식

쿠버네티스는 다음과 같은 다양한 배포방식을 지원한다.

  • Deployment : 새로운 버전의 매플리케이션을 다양한 전략으로 무중단 배포할 수 있다
  • StatefulSets : 실행 순서를 보장하고 호스트 이름과 볼륨을 일정하게 사용할 수 있어 순서나 데이터가 중요한 경우에 사용
  • DaemonSet : 로그나 모니터링 등 모든 노드에 설치가 필요한 경우에 사용
  • Job : 배치성 작업에 사용
  • CronJob : 배치성 작업에 사용

ingress 설정

다양한 웹 어플리케이션을 하나의 로드 밸런서로 서비스하기 위해 ingress 기능을 제공한다.

웹 어플리케이션은 보통 외부에서 직접 접근할 수 없도록 애플리케이션을 내부망에 설치하고, 외부에서 접근이 가능한 ALB나 nginx, Apache를 프록시 서버로 활용한다. 프록시 서버는 도메인과 Path 조건에 따라 등록된 서버로 요청을 전달하는데 서버가 바뀌거나 IP가 변경되면 매번 설정을 수정해야한다.

쿠버네티스의 ingress는 이를 자동화하여 기존 프록시 서버에서 사용하는 설정을 거의 그대로 사용할 수 있다. 새로운 도메인을 추가하거나 업로드 용량을 제한하기 위해 일일히 프록시 서버에 접속하여 설정할 필요가 없다.

ingress

하나의 클러스터에 여러 개의 Ingress 설정을 할 수 있어 관리자 접속용 Ingress와 일반 접속용 Ingress를 따로 관리할 수 있다.

클라우드 지원

쿠버네티스는 Cloud Controller를 사용하여 클라우드 연동을 손쉽게 확장할 수 있다. AWS 등 많은 클라우드 업체에서 모듈을 제공하여 관리자는 동일한 설정파일을 서로 다른 클라우드에서 동일하게 사용할 수 있다.

Namespace & label

하나의 클라스터를 namespace에 따라 논리적으로 구분하여 사용할 수 있다. 더 세부적인 설정으로 Label 기능을 사용하여 유연하고 확장성 있게 리소스를 관리할 수 있다.

RBAC(Role-Based Access Control)

각각의 리소스에 대해 유저별로 세부적인 권한을 손쉽게 지정할 수 있다. AWS의 경우 IAM을 연동할 수 있다.

CRD (Custom Resource Definition)

필요한 기능이 쿠버네티스에서 제공하지 않을 수 있다. 이럴때 그러한 기능을 쿠버네티스 기본 기능과 동일한 방식으로 적용하고 사용할 수 있다.

예를 들어 쿠버네티스는 기본적으로 SSL 인증서 관리기능을 제공하지 않지만 cert-manager를 설치하고 Certificate 리소스를 사용하면 쿠버네티스 명령어로 인증서를 관리할 수 있다.

Auto scaling

CPU, Memory 사용량, 현재 접속자 수와 같은 메트릭으로 Auto Scaling을 할 수 있다.

컨테이너 개수를 조정하는 Horizontal Pod Autoscaler, 컨테이너의 리소스 할당량을 조정하는 Vertical Pod Autoscaler, 서버 개수를 조정하는 Cluster Autoscaler 방식이 있다.

Federation, Multi Cluster

클라우드에 설치한 쿠버네티스 클러스터와 자체 서버에 설치한 쿠버네티스를 묶어서 하나로 사용할 수 있다. 구글에서 발표한 Anthos를 이용하면 한 곳에서 여러 클라우드의 여러 클러스터를 관리할 수 있다.


쿠버네티스 기본 개념

Desired State

Desired State는 관리자가 원하는 시스템의 환경을 의미한다.(ex : 얼마나 많은 웹서버가 동작하면 좋은지, 몇번 포트로 서비스되면 좋은지 등)

desired state

쿠버네티스는 현재 상태를 모니터링하면서 관리자가 설정한 원하는 상태를 유지하려고 내부적으로 작업하는 로직을 가지고 있다. 이러한 개념 때문에 쿠버네티스는 관리자가 서버를 배포할 때, 직접적인 동작을 명령하지 않고, 상태를 선언하는 방식을 사용한다.

kubernetes object

쿠버네티스는 상태를 관리하기 위한 대상을 오브젝트로 정의한다. 기본으로 수십가지 오브젝트를 제공하고 새로운 오브젝트를 추가하기 쉽기 떄문에 확장성이 좋다. 주요 오브젝트는 다음과 같다.

  • Pod

    pod

    • 쿠버네티스에서 배포가능한 가장 작은 단위로, 한 개 이상의 컨테이너와 스토리지, 네트워크 속성을 가진다.
    • Pod에 속한 컨테이너는 스토리지와 네트워크를 공유하고 서로 localhost로 접근할 수 있다. 컨테이너를 하나만 사용하는 경우도 반드시 Pod으로 감싸서 관리한다.
  • ReplicaSet

    replicaSet

    • 같은 Pod을 여러 개 복제하여 관리하는 오브젝트이다.
    • Pod을 생성하고 개수를 유지하려면 반드시 ReplicaSet을 사용해야한다.
    • RespicaSet은 복제할 개수, 개수를 체크할 라벨 선택자, 생성할 Pod의 설정값 등을 가지고 있다. 직접적으로 ReplicaSet을 사용하기보단 Deployment 등 다른 오브젝트에 의해서 사용되는 경우가 많다.
  • Service

    service

    • 네트워크와 관련된 오브젝트로, Pod을 외부 네트워크와 연결해주고 여러 개의 Pod을 바라보는 내부 로드밸런서를 생성할 때 사용한다.
    • 내부 DNS에 서비스 이름을 도메인으로 등록하기 때문에 서비스 디스커버리 역할도 한다.
  • Volume

    volume

    • 저장소와 관련된 오브젝트로, 호스트 디렉터리를 그대로 사용할 수 있고 EBS 같은 스토리지를 동적으로 생성하여 사용할 수 있다.
    • 다양한 저장방식을 지원한다.

오브젝트 명세 -YAML

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+
apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: nginx-deployment
+spec:
+  selector:
+    matchLabels:
+      app: nginx
+  replicas: 2 # tells deployment to run 2 pods matching the template
+  template:
+    metadata:
+      labels:
+        app: nginx
+    spec:
+      containers:
+      - name: nginx
+        image: nginx:1.14.2
+        ports:
+        - containerPort: 80
+

오브젝트 명세(Spec)은 YAML 파일로 정의하고, 여기에 오브젝트 종류와 원하는 상태를 입력한다. 이러한 명세는 생성, 조회, 삭제로 관리할 수 있기 때문에 REST API로 쉽게 노출할 수 있다. 접근 권한 설정도 같은 개념을 적용하여 누가 어떤 오브젝트에 어떤 요청을 할 수 있는지도 정의할 수 있다.

쿠버네티스 배포방식

쿠버네티스는 애플리케이션을 배포하기 위해 원하는 상태(desired state)를 다양한 오브젝트에 라벨을 붙여 정의(yaml)하고 API 서버에 전달하는 방식을 사용한다.

“컨테이너를 2개 배포하고 80 포트로 오픈해줘”라는 간단한 작업을 위해 다음과 같은 구체적인 명령을 전달해야한다.

“컨테이너를 Pod으로 감싸고 type=app, app=web이라는 라벨을 달아줘. type=app, app=web이라는 라벨이 달린 Pod이 2개 있는지 체크하고 없으면 Deployment Spec에 정의된 템플릿을 참고해서 Pod을 생성해줘. 그리고 해당 라벨을 가진 Pod을 바라보는 가상의 서비스 IP를 만들고 외부의 80 포트를 방금 만든 서비스 IP랑 연결해줘.”


쿠버네티스 아키텍처

쿠버네티스는 중앙(Master)에 API 서버와 상태 저장소를 두고 각 서버(Node)의 에이전트(kubelet)와 통신하는 단순한 구조이다. 하지만 앞에서 얘기한 개념을 여러 모듈로 쪼개어 구현하고, 다양한 오픈소스를 사용하기 떄문에 설치가 까다롭고 구성이 복잡해보이는 것이다.

마스터 - 노드 구조

master-node

쿠버네티스는 전체 클러스터를 관리하는 마스터와 컨테이너가 배포되는 노드로 구성되어있다. 모든 명령은 마스터의 API 서버를 호출하고 노드는 마스터와 통신하면서 필요한 작업을 수행한다. 특정 노드의 컨테이너에 명령하거나 로그를 조회할 때도 노드에 직접 명령하는게 아니라, 마스터에 명령을 내리고 마스터가 노드에 접속하여 대신 결과를 응답한다.

마스터

마스터 서버는 다양한 모듈이 확장성을 고려하여 기능별로 쪼개져있다. 관리자만 접속할 수 있도록 보안 설정을 해야하고, 마스터 서버가 죽으면 클러스터를 관리할 수 없기 때문에 보통 3대를 구성하여 안정성을 높인다.

AWS EKS 같은 경우 마스터를 AWS에서 자체 관리하여 안정성을 높였고(마스터에 접속 불가), 개발환경이나 소규모 환경에서는 마스터와 노드를 분리하지 않고 같은 서버에 구성하기도 한다.

마스터의 구성요소는 다음과 같다.

master components

  • API 서버 kube-apiserver
    • 쿠버네티스의 모든 요청을 처리하는 마스터의 핵심 모듈
    • 실제로 하는 일을 요약하면 원하는 상태를 key-value 저장소(etcd)에 저장하고, 저장된 상태를 조회하는 작업
    • kubectl의 요청, 내부 모듈의 요청 처리, 권한 체크, 노드에서 실행중인 컨테이너의 로그를 보여주고 명령을 보내는 디버거 역할 등 수행
      • kubectl : 마스터의 API 서버는 json 또는 protobuf 형식을 이용한 http 통신을 지원한다. 하지만 직접 http 통신으로 명령을 내리면 불편하므로, 보통 kubectl 이라는 명령행 도구를 사용한다
  • 분산 데이터 저장소 etcd
    • key-value 저장소로, 클러스터의 모든 설정/상태 데이터가 저장되는 곳.
    • RAFT 알고리즘을 사용하였고, 여러 개로 분산하여 복제할 수 있다. watch 기능을 포함하여 상태 변경 시 바로 체크하여 로직을 실행할 수 있다.
    • 나머지 모듈은 stateless하게 동작하기에 etcd만 안전하게 백업해두면 언제든지 클러스터를 복구할 수 있다.
    • 오직 API 서버와 통신하고, 다른 모듈은 API 서버를 거쳐 etcd 데이터에 접근한다.
  • 스케줄러, 컨트롤러
    • 현재 상태를 모니터링하다가 원하는 상태와 다르면 각자 맡은 작업을 수행하고 상태를 갱신한다.
    • API 서버는 요청을 받으면 etcd 저장소와 통신하고 실제로 상태를 바꾸는 건 스케줄러와 컨트롤러이다.
    • 구성요소
      • 스케줄러 : 할당되지 않은 Pod을 여러가지 조건(필요한 자원, 라벨)에 따라 적절한 노드 서버에 할당시켜주는 모듈
      • 큐브 컨트롤러(kube-controller) : 쿠버네티스에 있는 거의 모든 오브젝트의 상태를 관리.
        • 오브젝트별로 철저하게 분업화되어 Deployment는 ReplicaSet을 생성하고 ReplicaSet은 Pod을 생성하고 Pod은 스케줄러가 관리하는 식
      • 클라우드 컨트롤러 : AWS 등 클라우드에 특화된 모듈. 노드를 추가/삭제하고 로드밸런서를 연결하거나 볼륨을 붙일 수 있다. 각 클라우드 업체에서 인터페이스를 구현하여 확장성이 좋고, 자체 모듈을 만들어 제공한다.

노드

노드 서버는 마스터 서버와 통신하면서 필요한 Pod을 생성하고 네트워크와 볼륨을 설정한다. 실제 컨테이너들이 생성되는 곳이며 수백, 수천대로 확장할 수 있다. 각각의 서버에 라벨을 붙여 사용목적을 정의할 수 있다.

노드의 구성요소는 다음과 같다

node-components

  • Kubelet
    • 노드에 할당된 pod의 생명주기를 관리한다.
    • pod을 생성하고 pod 안의 컨테이너에 이상이 없는지 확인하면서 주기적으로 마스터에 상태를 전달한다.
    • API 서버의 요청을 받아 컨테이너의 로그를 전달하거나 특정 명령을 대신 수행해준다.
  • kube-proxy
    • pod으로 연결되는 네트워크를 관리한다
    • TCP, UDP, SCTP 스트림을 포워딩하고 여러 개의 Pod을 라운드로빈 형태로 묶어 서비스를 제공할 수 있다
    • iptables를 설정하여 각 pod으로 요청을 보내준다. 하지만, iptables에 등록된 규칙이 많아지면 느려지는 문제 때문에 최근엔 IPVS를 지원하기 시작했다.

Node vs cluster

쿠버네티스를 학습하다보면 노드와 클러스터란 단어를 많이 접할 수 있다. 이 둘의 차이는 무엇일까?

  • 노드 : 하나의 머신이나 서버. Pod들을 포함함.
  • 클러스터 : 서로 높은 가용성으로 통신하는 노드들의 집합.

추상화

쿠버네티스는 도커 컨테이너 이외에도 CRI(Container Runtime Interface)를 구현한 다양한 컨테이너 런타임을 지원한다. 또한 CNI(네트워크), CSI(스토리지)를 지원하여 인터페이스만 구현한다면 쉽게 확장하여 사용할 수 있게 제공한다.


하나의 Pod이 생성되는 과정

관리자가 애플리케이션을 배포하기 위해 ReplicaSet을 생성하면 다음과 같은 과정을 거쳐 Pod을 생성한다.

order

각 모듈은 서로 통신하지 않고 오직 API Server하고만 통신한다. API Server를 통해 etcd에 저장된 상태를 체크하고, 현재 상태와 원하는 상태가 다르면 필요한 작업을 수행한다. 각 모듈을 살펴보자

kubectl

  • ReplicaSet 명세를 yml 파일로 정의하고 kubectl 도구를 이용하여 API Server에 명령을 전달
  • API Server는 새로운 ReplicaSet Object를 etcd에 저장

kube controller

  • Kube Controller에 포함된 ReplicaSet Controller가 ReplicaSet을 감시하다가 ReplicaSet에 정의된 Label Selector 조건을 만족하는 Pod이 존재하는지 체크
  • 해당하는 Label의 Pod이 없으면 ReplicaSet의 Pod 템플릿을 보고 새로운 Pod(no assign)을 생성. 생성은 역시 API Server에 전달하고 API Server는 etcd에 저장

Scheduler

  • Scheduler는 할당되지 않은 Pod이 있는지 체크
  • 할당되지 않은 Pod이 있으면 조건에 맞는 Node를 찾아 해당 Pod을 할당

Kubelet

  • Kubelet은 자신의 Node에 할당되었지만 아직 생성되지 않은 Pod이 있는지 체크
  • 생성되지 않은 Pod이 있으면 명세를 보고 Pod을 생성
  • Pod의 상태를 주기적으로 API Server에 전달


출처

https://subicura.com/2019/05/19/kubernetes-basic-1.html

https://subicura.com/k8s/guide/#%E1%84%8B%E1%85%AF%E1%84%83%E1%85%B3%E1%84%91%E1%85%B3%E1%84%85%E1%85%A6%E1%84%89%E1%85%B3-%E1%84%87%E1%85%A2%E1%84%91%E1%85%A9

This post is licensed under CC BY 4.0 by the author.

nginx + spring boot+ react로 구성된 앱 dockerfile 작성

kubernetes 실습

Comments powered by Disqus.

diff --git a/posts/devops-pinpoint/index.html b/posts/devops-pinpoint/index.html new file mode 100644 index 000000000..f4049086d --- /dev/null +++ b/posts/devops-pinpoint/index.html @@ -0,0 +1 @@ + pinpoint | 디피의 개발일지
Posts pinpoint
Post
Cancel

pinpoint

Pinpoint

Pinpoint는 대규모 분산 시스템의 성능을 분석하고 문제를 진단, 처리하는 플랫폼입니다. 2012년 7월에 개발을 시작해 2015년 1월 9일에 오픈소스로 공개했습니다


Pinpoint 개발 동기와 Pinpoint의 특징

인터넷 서비스의 시스템 복잡도가 증가함에 따라 장애나 성능 문제가 발생했을 때 해결이 어려워졌다. 네이버의 시스템도 마찬가지였다. 시스템 복잡도가 높아지며 발생하는 문제를 해결하기 위해 n계층 아키텍처를 효과적으로 추적할 수 있는 새로운 플랫폼을 개발하기로 했고 그것이 Pinpoint이다.

Pinpoint에는 n계층 아키텍처를 추적할 수 있는 다음과 같은 특징이 있다.

  • 분산된 애플리케이션의 메시지를 추적할 수 있는 분산 트랜잭션 추적
  • 애플리케이션 구성을 파악할 수 있는 애플리케이션 토폴로지 자동 발견
  • 대규모 서버군을 지원할 수 있는 수평 확장성
  • 코드 수준의 가시성을 제공해 문제 발생 지점과 병목 구간을 쉽게 발견
  • bytecode instrumentation 기법으로 코드를 수정하지 않고 원하는 기능을 추가


Google Dapper 스타일의 분산 트랜잭션 추적

Pinpoint는 Google Dapper 스타일의 추적 방식을 사용해, 분산된 요청을 하나의 트랜잭션으로 추적한다.

Google Dapper의 분산 트랜잭션 추적방법

분산 트랜잭션 추적의 핵심은 다음 그림처럼 Node 1에서 Node 2로 메시지를 전송했을 때, 분산된 Node 1과 Node 2가 처리한 메시지의 관계를 찾아내는 것이다.

google dapper 그림

google dapper에서는 Node 1에서 발송하는 메시지에 태그를 추가하여 각 메시지를 구분하도록 하여 추적이 가능하도록 했다.

Pinpoint에서는 Google Dapper 스타일의 추적 방법을 변형해 호출 추적에 사용한다. 원격 호출 시 분산 트랜잭션의 추적을 위해 애플리케이션 수준에서 태그 데이터를 호출 헤더에 추가한다. 태그 데이터는 키의 집합으로 구성되며, 이 집합을 TraceId라고 정의한다.

Pinpoint 의 자료구조

Span

  • RPC(remote procedure call) 추적을 위한 기본 단위다.
  • RPC가 도착했을 때 처리한 작업을 나타내며 추적에 필요한 데이터가 들어 있다. 코드 수준의 가시성을 확보하기 위해, Span의 자식으로 SpanEvent라는 자료구조를 가지고 있다.
  • Span은 TraceId를 가지고 있다.

Trace

  • Span의 집합으로, 연관된 RPC(Span)의 집합으로 구성된다.
  • Span의 집합은 TransactionId가 같다.
  • Trace는 SpanId와 ParentSpanId를 통해 트리 구조로 정렬된다.

TraceId

  • TransactionId와 SpanId, ParentId로 이루어진 키의 집합이다.
  • TransactionId는 메시지의 아이디이며, SpanId와 ParentId는 RPC의 부모 자식 관계를 나타낸다. - TransactionId(TxId): 분산된 노드를 거쳐 다니는 메시지의 아이디로, 전체 서버군에서 중복되지 않아야 한다. - SpanId: RPC 메시지를 받았을 때 처리되는 작업(job)의 아이디를 정의한다. RPC가 노드에 도착했을 때 생성한다. - ParentSpanId(pSpanId): 호출한 부모의 SpanId를 나타낸다.

Pinpoint 강의

Observability 와 APM 필요성

마이크로 서비스가 많아지면서 서비스의 흐름을 파악하기 어려워졌다. 이러한 상황에서 서비스 전반의 Observability를 확보하기 위해 다음 세가지가 필요해졌다.

  • Trace: 서비스에 request가 들어와서 나갈 때까지의 경로를 추적하는 정보 –> Pinpoint
  • Log : 수많은 서버에 나뉘어진 로그정보. –> NELO
  • Metric : 인프라나 인스턴스의 메트릭 정보 –> NPOT

APM(Application Performance Management)

  • 어플리케이션의 가용성과 성능을 관리하고 모니터링하는 시스템
  • pinpoint는 대규모 분산시스템에 적합한 APM 이다.

APM 필요한 이유

  • 기존의 문제 해결 방법
    • 문제 발생 -> A 시스템의 문제로 확인 -> 담당자 연결 -> A 시스템의 B 서버 문제로 확인 -> 담당자 연결 -> … 반복
  • APM
    • 문제 발생 -> APM(pinpoint) 확인 -> 담당자 연결

Pinpoint 소개

요청 처리 순서

  1. 유저의 요청
  2. Pinpoint agent instrumentation 에서 요청을 가로채서 필요한 정보를 주입함
  3. 어플리케이션에선 요청에 맞는 작업 수행 후 응답 발송
  4. Pinpoint agent instrumentation 에서 응답을 가로채서 정보를 수집 후 비동기로 pinpoint collector로 데이터를 보냄.

bytecode 주입

  • java, node와 같은 언어에서는 bytecode를 주입하여 pinpoint에 필요한 작업을 수행
  • 다른 언어에선 SDK를 제공하여 직업 pinpoint를 호출

overload

  • pinpoint로 발생하는 성능저하는 현재 3% 미만
  • 지속적으로 관찰하여 pinpoint의 성능을 모니터링한다.

pinpoint feature 소개

server map

  • pinpoint로 모니터링하는 서버의 구조를 보여줌

scatter chart

  • 초록색 : 성공한 transaction, 빨간색 : 실패한 transaction
  • 세로축은 응답 시간.
  • 드래그하면 선택된 점들의 정보가 보여짐

call stack

  • transaction 하나를 선택하면 상태 콜스택이 보여짐
  • 해당 transaction이 어떻게 호출되었는지 확인할 수 있다.

inspector

  • 인스턴스의 기본 정보 및 메트릭 정보를 확인가능

참고문서

Pinpoint 강의

This post is licensed under CC BY 4.0 by the author.

istio

클래스 초기화 블록

Comments powered by Disqus.

diff --git "a/posts/devops-\354\202\254\354\235\264\353\223\234\354\271\264-\355\214\250\355\204\264/index.html" "b/posts/devops-\354\202\254\354\235\264\353\223\234\354\271\264-\355\214\250\355\204\264/index.html" new file mode 100644 index 000000000..543e0ad35 --- /dev/null +++ "b/posts/devops-\354\202\254\354\235\264\353\223\234\354\271\264-\355\214\250\355\204\264/index.html" @@ -0,0 +1,59 @@ + 사이드카 패턴 | 디피의 개발일지
Posts 사이드카 패턴
Post
Cancel

사이드카 패턴

사이드카 패턴

쿠버네티스의 패턴 중 하나로, 어플리케이션 컨테이너와 독립적으로 동작하는 별도의 컨테이너를 붙이는 패턴이다. 어플리케이션 컨테이너의 변경이나 수정 없이 독립적으로 동작하는 컨테이너를 붙였다 뗐다 할 수 있다.


파드에서의 사이드카

파드는 쿠버네티스에서 가장 기본적인 배포 단위로서 자신에게 속한 컨테이너들에게 런타임제약을 걸 수 있다. 예를 들어 모든 컨테이너는 동일한 노드에 배치되고, 동일한 파드 수명주기를 공유하게 할 수 있다. 파드는 또한 컨테이너들이 볼륨을 공유하고 로컬 네트워크 또는 호스트 IPC를 통해 서로 통신할 수 있게 해준다. 이러한 이유로 컨테이너 그룹을 파드로 만든다.

사이드카 패턴은 컨테이너의 기능을 확장시키기 위해 컨테이너를 파드에 넣는 것과 유사하다. 다음은 HTTP 서버와 깃 동기화 컨테이너가 정의된 파일이다. HTTP가 어플리케이션 컨테이너이고, 깃 동기화 컨테이너가 사이드카 컨테이너이다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+
apiVersion: v1
+kind: Pod
+metadata:
+  name: web-app
+spec:
+  containers:
+    - name: app
+      image: docker.io/centos/httpd // HTTP로 파일을 제공하는 기본 애플리케이션 컨테이너
+      ports:
+        - containerPort: 80
+      volumeMounts:
+        - mountPath: /var/www/html  //사이드카와 기본 애플리케이션 컨테이너 간에 데이터를 교환하기위해 공유된 장소
+          name: git
+    - name: poll
+      image: axeclbr/git            // 깃 서버로부터 데이터를 가져오고 병렬로 실행하는 사이드카 컨테이너
+      volumeMounts:
+        - mountPath: /var/lib/data //사이드카와 기본 애플리케이션 컨테이너 간에 데이터를 교환하기 위해 공유된 장소
+          name: git
+      env:
+      - name: GIT_REPO
+        value: https://github.com/mdn/beginner-html-site-scripted
+      command:
+        - "sh"
+        - "-c"
+        - "git clone ${GIT_REPO} . && watch -n 600 git pull"
+        workingDir: /var/lib/data
+      volumes:
+        - emptyDir: {}
+          name: git
+

사이드카 패턴에서는 기본 컨테이너가 있고, 공동작업을 향상시키는 보조 컨테이너가 있다. 일반적으로 기본 컨테이너는 컨테이너 목록에 나열된 첫번째 컨테이너가 기본 컨테이너다.

사이드카 패턴을 통해 컨테이너는 런타임에 협업이 가능해지며 그와 동시에 두 컨테이너의 관심을 분리시킴으로서 서로의 의존성을 없앤다.

또한 사이드카는 같은 파드 안에서 애플리케이션 컨테이너와 동일한 수명주기를 공유하므로 애플리케이션 컨테이너와 함께 만들어지고 사용중지된다.


예시

  • 보안을 위해 사이드카로 Nginx reverse proxy를 붙여 https 통신을 한다.
  • 성능을 위해 사이드카로 Nginx content cache 등을 붙인다.
  • 인프라 개발팀은 인프라에 액세스할 언어별 라이브러리가 아닌 각 애플리케이션과 함께 배포될 서비스를 만든다. 인프라 서비스는 사이드카로 로드되고, 로깅/검색 등 인프라 서비스에 대한 공통 계층을 제공한다. 또한 애플리케이션 컨테이너의 호스트 환경 및 프로세스를 모니터링하고 중앙집중식 서비스에 정보를 기록한다.

장단점

장점

  • 어플리케이션 간의 상호의존성을 줄일 수 있다.
  • 대부분 같은 스토리지를 공유할 수 있기 때문에 공유에 대한 고민이 적다

단점

  • 어플리케이션이 너무 작은 경우 배보다 배꼽이 더 커진다.
  • 프로세스간 통신이 많기에 최적화해야한다면, 한 어플리케이션에서 함께 처리하는게 좋을 수 있다.

참고문서

참고문서2

참고문서3

This post is licensed under CC BY 4.0 by the author.

API 예외처리

istio

Comments powered by Disqus.

diff --git a/posts/es6/index.html b/posts/es6/index.html new file mode 100644 index 000000000..436e8f13c --- /dev/null +++ b/posts/es6/index.html @@ -0,0 +1,223 @@ + es6에서 도입된 문법 | 디피의 개발일지
Posts es6에서 도입된 문법
Post
Cancel

es6에서 도입된 문법

let, const 키워드를 통한 변수선언

기존 자바스크립트에서는 var 키워드로만 변수선언이 가능했다. 하지만, let const 키워드를 추가하여 보다 예측가능한 코드를 작성할 수 있게 됐다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+
// es6 이전.
+// var는 재선언 가능
+var a = "This is string";
+var a = 1234;
+
+// ES6 이후
+// let은 재선언 불가능
+let b = "This is string";
+b = 1234;
+
+// const는 재할당 불가능
+const c = "This is string";
+c = 1234; // <-- 에러 발생
+

const는 상수 키워드로 재할당, 재선언이 불가능하다. 변경할 수 없다로 이해하기 쉽지만, letvar는 헷갈릴 수 있다. 차이는 다음과 같다.

 varlet
재선언가능불가능
재할당가능가능
scopefunction-scopeblock-scope
호이스팅일어남안일어남
(정확히는 일어나지만 할당문을 만나기 전까지는 사용불가능)


템플릿리터럴

문자열 내에 변수사용을 쉽게 만들어주는 문법으로 다음과 같이 사용한다.

1
+2
+3
+4
+5
+6
+7
+
const name = "user-1";
+const count = 324;
+
+// es6 이전
+var str1 = name + " has pushed button " + count + " times."; 
+// es6 이후
+const str2 = `${name} has pushed button ${count} times.`;
+


화살표함수

함수 표현식을 화살표함수로 표현하여 간결하게 함수를 작성할 수 있다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
// es6 이전
+function func(a, b) {
+  return a + b;
+}
+
+// es6 이후
+const func = (a, b) => {
+  return a + b;
+}
+// return 생략
+const func = (a, b) => a + b;
+


구조분해할당

객체, 배열의 구조를 분해하여, 새로운 변수에 할당하는 과정이다. 객체/배열에서 값을 꺼낼때 가독성 좋게 가져올 수 있도록 한다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+
// es6 이전 (배열)
+var arr = [1, 2, 3];
+console.log(arr[0], arr[1], arr[2]);
+// es6 이후 (배열)
+const [first, second, third] = arr;
+console.log(first, second, third);
+
+// es6 이전 (객체)
+var obj = {
+  name : "user-1",
+  count: 123
+}
+console.log(obj.name, obj.count);
+
+// es6 이후 (객체)
+const {name, count} = obj;
+console.log(name, count);
+


Promise

ES6 이전에 자바스크립트의 비동기처리는 콜백함수로 이루어졌다. 하지만 비동기 함수가 많아지면서 콜백 지옥이 일어나기도 했다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+
// es6 이전
+function callbackHell(callback) {
+  setTimeout(function() {
+    ...	// do something
+   	setTimeout(function() {
+      ... // do something
+      setTimeout(function() {
+        ... // do something
+        callback();
+      }, 1000)
+    }, 1000)
+  }, 1000)
+}
+

이러한 콜백 지옥을 개션하기 위한 문법으로 다음과 같이 코드가 변경된다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+
function callbackFree(callback) {
+  const promise1 =  new Promise((resolve, reject) => {
+    setTimeout(() => {
+      ... // do something
+      resolve();
+    }, 1000)
+  })
+  
+  const promise2 = promise1.then(() => {
+   return new Promise((resolve, reject) => {
+       setTimeout(() => {
+        ... // do something
+        resolve();
+      }, 1000)
+   })
+  });
+  
+  promise2.then(() => {
+    setTimeout(() => {
+	    ... // do something
+      callback();
+    }, 1000)
+  })
+}
+
+


Class

자바스크립트에서 객체 지향 프로그래밍이 가능하도록 Class 키워드가 도입되었다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+
class Polygon {
+  constructor(height, width) {
+    this.name = 'Polygon';
+    this.height = height;
+    this.width = width;
+  }
+}
+
+class Square extends Polygon {
+  constructor(length) {
+    super(length, length);
+    this.name = 'Square';
+  }
+}
+


모듈 시스템

자바스크립트에서 코드를 분리하여 관리할 수 있게하여 재사용성과 생산성을 높이기위해 모듈 시스템이 도입되었다.

  • script 태그 사용 예시

    1
    +
    <script type="module" src="lib.mjs"></script>
    +
  • es6에서 사용 에시

    1
    +2
    +3
    +4
    +5
    +
    import compute from "commons/compute";
    +  
    +const result = compute(1,2);
    +  
    +export result;
    +
  • commonsJs에서 사용 예시

    1
    +2
    +3
    +4
    +5
    +
    const compute = require("commons/compute");
    +  
    +const result = compute(1,2);
    +  
    +module.exports = { result };
    +

require와 import의 차이점

  • require/exports
  • 구형브라우저나, 브라우저 밖에서 사용하는 CommonJS 문법
  • 파일의 어디서나 호출 가능

  • import/export
  • ES6에서 도입된 문법
  • 파일의 시작부분에서만 실행가능(import 전용 비동기 문법으로 중간에 불러올 수도 있다.)
  • 필요한 부분만 선택하여 로드 가능. 또한 require보다 성능이 우수함

  • 하나의 파일에서 두 키워드를 동시에 사용할 순 없


출처

  • https://hanamon.kr/javascript-es6-%EB%AC%B8%EB%B2%95/
This post is licensed under CC BY 4.0 by the author.

영상이 자동재생되지 않는 문제

IOS 16.5에서 video가 크기 변경이 적용되지 않는 이슈

Comments powered by Disqus.

diff --git a/posts/eventloop/index.html b/posts/eventloop/index.html new file mode 100644 index 000000000..f23f4d360 --- /dev/null +++ b/posts/eventloop/index.html @@ -0,0 +1,49 @@ + javascript 이벤트루프 | 디피의 개발일지
Posts javascript 이벤트루프
Post
Cancel

javascript 이벤트루프

이벤트 루프

javascript를 공부하다보면 아래와 같은 말을 종종 듣는다.

싱글스레드 기반으로 동작하는 자바스크립트

이벤트 루프를 기반으로 하는 싱글스레드 Node.js

정말 싱글 스레드인가? 어떻게 싱글 스레드인가? 이벤트 루프는 무엇인가? 를 간단히 알아보기 위해 자바스크립트가 동작하는 환경과 엔진에 대해 알아보자.


Javascript Engine

javascript로 작성한 코드를 해석하고 실행하는 인터프리터. 주로 웹브라우저에서 사용되지만, node.js에서는 V8 엔진이 사용된다.

구글에서 개발한 V8 엔진을 비롯해, 대부분의 자바스크립트 엔진은 다음 세 영역으로 나뉜다.

  • Call Stack
  • Task Queue(Event Queue)
  • Heap

추가적으로 Event Loop가 존재하여 Task queue에 들어가는 task들을 관리하게 된다.

image-20220209004707394


Call stack

자바스크립트는 단 하나의 call stack을 사용한다. 따라서 자바스크립트에서는 하나의 함수가 실행하면, 다른 어떤 task도 수행될 수가 없다.

자바스크립트에서는 요청이 들어올때마다 해당 요청을 순차적으로 call stack에 담아 처리한다. 메소드가 실행될 때 call stack에 새로운 프레임이 생기고, push 되고, 메소드의 실행이 끝나면 해당 프레임은 pop되는 원리이다.

예시)

1
+2
+3
+4
+5
+6
+7
+8
+9
+
function foo(b) {
+  var a = 10;
+  return a + b;
+}
+function bar(x) {
+  var y = 2;
+  return foo((x = y));
+}
+console.log(bar(1));
+
  1. bar 가 스택에 들어간다.
  2. foo 가 스택에 들어간다.
  3. foo 의 실행이 완료되고, pop된다.
  4. bar의 실행이 완료되고, pop된다.


Heap

동적으로 생성된 객체(인스턴스)는 힙에 할당된다. 대부분 구조화되지 않는 더미 같은 메모리 영역을 heap이라고 표현한다.


Task Queue(Event Queue)

자바스크립트의 런타임 환경에서 처리해야하는 task들을 임시 저장하는 대기 큐. call stack이 비어졌을 때 먼저 대기열에 들어온 순서대로 수행된다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+
function test1() {
+  setTimeout(function () {
+    console.log("first");
+  }, 0);
+  test2();
+}
+function test2() {
+  console.log("second");
+}
+test1();
+

위 코드는 다음 결과를 낸다.

1
+2
+
second;
+first;
+

자바스크립트에서 비동기로 호출되는 함수들은 call stack에 쌓이지 않고, task queue에 들어가기 때문이다. 따라서 위 작업은 다음 순서로 실행된다.

  1. setTimeout가 call stack에 쌓인다.
  2. setTimeout은 web api에서 실행될 수 있다. 따라서, setTimeout의 실행을 web API로 요청하고 setTimeout은 종료된다.
  3. 0초이기에 Web API는 콜백함수를 바로 task queue에 등록한다.
  4. 3번과 동시에, test2()가 call stack 안에 들어간다.
  5. test2()가 종료되어 pop되고, test1()도 종료된 다음 pop된다.
  6. task queue에 있던 익명함수가 빠져나와 실행된다.

이때 task queue 안에 들어있는 이벤트를 꺼내거나, 넣는 작업을 하는 것이 이벤트 루프 이다.


다음과 같은 의문이 들 수 있다.

  • 이벤트 루프는 백그라운드 스레드가 존재해서, call stack을 polling 하면서 비어있는지 확인하는 건가?
  • task queue에도 event가 있는지 확인해야할 것 같은데, 이때는 polling 으로 검사하는 건가?
  • event loop에 의해서 event queue에 있던 하나의 이벤트가 call stack에 들어간 다음에는 그 이벤트가 끝나기 전까지, 이벤트 루프는 event queue에서 이벤트를 꺼내지 않는가?
  • call stack에서 이벤트가 진행 중일 때도 이벤트루프는 어떻게 확인하나?

위 질문에 대해 MDN은 다음의 가상 코드로 답을 한다.

1
+2
+3
+
while (queue.waitForMessage()) {
+  queue.processNextMessage();
+}
+

이런 식으로 이벤트루프는 현재 실행중인 태스크가 없는지와 태스크 큐에 이벤트가 있는지 반복적으로 확인한다.

queue에 이벤트가 존재하면 while-loop 안으로 들어가서 해당하는 이벤트를 처리하거나 작업을 수행한다. 그리고 다시 queue로 돌아와 새로운 이벤트가 존재하는지 파악하는 것이다. 따라서 task queue에 있는 작업들은 한번에 하나씩 call stack으로 호출되어 처리된다.


이때 브라우저에서 task queue는 다음과 같이 세가지 큐로 나뉜다.

browser structure

  • 비동기로 처리되는 작업은 task, microtask, animationFrame으로 구분된다.
    • microtask : Promise 등
    • task (macro task) : setTimeout
    • animationFrame : UI 렌더링. requestAnimationFrame이 호출되어 브라우저 렌더링 발생
  • microtask는 task보다 먼저 작업이 처리되고, microtask가 다 비워져야 task가 수행된다. 이는 microtask가 상대적으로 작은 작업이기에 빠르게 끝내기 위해서이다.
  • microtask에서 추가한 microtask도 큐가 다 빌때까지 실행된다. task에서 추가한 task는 다음 이벤트루프까지 실행되지 않는다.

출처

https://asfirstalways.tistory.com/362

https://sculove.github.io/post/javascriptflow/

This post is licensed under CC BY 4.0 by the author.

알고리즘 개요

hoisting

Comments powered by Disqus.

diff --git "a/posts/expo-\353\260\260\355\217\254/index.html" "b/posts/expo-\353\260\260\355\217\254/index.html" new file mode 100644 index 000000000..421e0019e --- /dev/null +++ "b/posts/expo-\353\260\260\355\217\254/index.html" @@ -0,0 +1 @@ + expo 배포 | 디피의 개발일지
Posts expo 배포
Post
Cancel

expo 배포

EXPO 앱 google playstore 배포

  1. app.json 수정
  2. expo build:android -t app-bundle
    • google play app signing 이 선행되어야함.
      • https://docs.expo.dev/distribution/app-signing/
      • keystore를 구글 개발자계정에서 받을 수 있는듯?
      • https://support.google.com/googleplay/android-developer/answer/9842756
    • keystore는 generate하되, expo fetch:android:keystore를 통해 가지고 있자.
      • 업데이트를 같은 keystore로 해야함.
  3. 끝나면 .apk 또는 .aab 파일이 나옴
    • .apk 면 에뮬레이터에 넣어서 테스트함.
  4. playstore에 제출
    • 처음 제출일 경우 https://github.com/expo/fyi/blob/master/first-android-submission.md
    • 이후엔 eas 사용하면 되는듯
      • https://docs.expo.dev/distribution/uploading-apps/
  5. 업데이트
    • expo publish 하면 자동으로 업데이트됨.
    • 하지만 다음과 같은 이유로 다시 제출하고 싶을때가 있음
      • native metadata를 바꾸고 싶을때 (ex : app’s name, icon)
      • 최신 SDK 버전으로 업그레이드하고 싶을때.
    • 그럴때는 versionCode 또는 buildNumber를 바꿔서 업데이트해줘야함
This post is licensed under CC BY 4.0 by the author.
diff --git a/posts/firebase_function_firestore_storage/index.html b/posts/firebase_function_firestore_storage/index.html new file mode 100644 index 000000000..cf08db27f --- /dev/null +++ b/posts/firebase_function_firestore_storage/index.html @@ -0,0 +1,73 @@ + Firebase/Function firestore, storage 쓰기, 읽기 | 디피의 개발일지
Posts Firebase/Function firestore, storage 쓰기, 읽기
Post
Cancel

Firebase/Function firestore, storage 쓰기, 읽기

firebase cloud function에서 firestore와 storage의 이벤트트리거를 등록하는 예제는 firebase 문서에 자세히 설명되어있다.

여기서는 cloud functions에서 firestore와 storage에 접근하여 읽고 쓰는 법을 간단한 코드로 보여줄 것이다.

  • firestore
    1. firebase cloud function 가이드에 나온대로 admin을 초기화시켜준다.
    2. admin.firestore() 로 firestore 관련 함수를 호출하면 끝.
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+
const admin = require("firebase-admin");
+admin.initializeApp();
+
+...
+// 한 document 가져오기
+const doc = await admin
+         .firestore()
+         .collection("컬렉션이름")
+         .doc("document이름")
+         .get();
+// document set
+await admin
+         .firestore()
+         .collection("컬렉션이름")
+         .doc("document이름")
+         .set({
+            url: req.body.url,
+            name: req.body.name,
+            createdAt: Date.now(),
+         });
+...
+
  • storage

    1. @google-cloud/storage 에서 Storage 모듈 가져오기

    2. Storage 객체를 생성

    3. storage.bucket으로 bucket url로 bucket reference 생성

    4. 생성한 bucket reference로 file reference를 만들고, save 메소드를 통해 저장

      1
      +2
      +3
      +4
      +5
      +6
      +7
      +8
      +9
      +10
      +11
      +12
      +13
      +14
      +15
      +
      const { Storage } = require("@google-cloud/storage");
      +
      +const storage = new Storage();
      +
      +...
      +const bucket = storage.bucket("버켓 url");
      +
      +//file ref 생성
      +const file = bucket.file(`${req.body.uid}/${req.body.uuid}.png`);
      +
      +//저장
      +await file.save(screenshotBinary, {
      +	metadata: { contentType: "image/png" },
      +});
      +...
      +
This post is licensed under CC BY 4.0 by the author.

Firebase/Function Puppeteer 사용하기

15971 두 로봇

Comments powered by Disqus.

diff --git "a/posts/flex,grid\353\241\234-\353\202\250\353\212\224\352\263\265\352\260\204-\354\261\204\354\232\260\352\270\260/index.html" "b/posts/flex,grid\353\241\234-\353\202\250\353\212\224\352\263\265\352\260\204-\354\261\204\354\232\260\352\270\260/index.html" new file mode 100644 index 000000000..36f979893 --- /dev/null +++ "b/posts/flex,grid\353\241\234-\353\202\250\353\212\224\352\263\265\352\260\204-\354\261\204\354\232\260\352\270\260/index.html" @@ -0,0 +1,133 @@ + flex, grid로 남은 곳 꽉채우기 | 디피의 개발일지
Posts flex, grid로 남은 곳 꽉채우기
Post
Cancel

flex, grid로 남은 곳 꽉채우기

최근 프로젝트를 하면서, 다른 요소들의 크기는 정해져있을때 남은 한 요소의 크기를 부모 요소에서 남는 공간만큼 부여하는 부분이 많았다(그리고 앞으로도 많을 것 같다)

위 부분을 구현할때, flex와 grid를 통해 해결했어서 그것을 기록 하는 차원에서 이 글을 적어본다.

flex

만약 다음과 같이 div 엘리먼트들이 배치되어있고, 우리는 가운데 expand를 container의 남는 공간만큼 확장 시키고 싶다고 하자.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
.container {
+  display: flex;
+  background-color: burlywood;
+}
+
+.leftItem {
+  height: 100px;
+  width: 100px;
+  background-color: cadetblue;
+}
+.rightItem {
+  height: 100px;
+  width: 100px;
+  background-color: chartreuse;
+}
+.expandItem {
+  width: 100px;
+  height: 100px;
+  background-color: chocolate;
+}
+

image-20220121013951112

만약 expandItem에 width:100%;를 주면 어떻게 될까?

1
+2
+3
+4
+5
+6
+7
+
...
+.expandItem {
+  width: 100%;
+  height: 100px;
+  background-color: chocolate;
+}
+...
+

image-20220121014113322

위와같이, 확장이 된 모습을 볼 수 있다. 하지만 뭔가 첫번째 사진과 좀 다르지 않는가? left와 right가 첫번째 사진보다 작아보이지 않는가?

자바스크립트로 확인해보자. 다음과 같은 코드로 간단히 left 요소의 너비를 찾아봤다

1
+2
+3
+4
+5
+6
+
<script>
+      window.onload = function () {
+        const left = document.getElementById("left");
+        console.log(left.getBoundingClientRect());
+      };
+    </script>
+

image-20220121015024107

결과는 대략 81px. expand 엘리먼트는 다른 요소들의 너비를 침범하여 확장되었다. 이는 실제 UI를 구현할때 상당히 거슬릴 것이다.

그럼 어떤 방법으로 다른 요소를 침범하지 않고 확장이 가능할까?

flex : 1 0 auto; or flex-grow:1;

제목에서와 같이 width :100%말고 flex : 1 0 auto;를 expand 엘리먼트에 주어보자.( flex-grow: 1;로도 되나, 여기선 예시는 flex : 1 0 auto;로 들어본다 )

1
+2
+3
+4
+5
+
.expandItem {
+  flex: 1 0 auto;
+  height: 100px;
+  background-color: chocolate;
+}
+

image-20220121015303592

image-20220121015322451

자바스크립트 수행결과까지 확인해보니, 제대로 다른 요소를 침범하지 않고 확장되었다.

이와 같은 방법으로 세로 방향일때도 적용이 된다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+
.container {
+  display: flex;
+  width: 100px;
+  height: 500px;
+  flex-direction: column;
+  background-color: burlywood;
+}
+
+.leftItem {
+  height: 100px;
+  width: 100px;
+  background-color: cadetblue;
+}
+.rightItem {
+  height: 100px;
+  width: 100px;
+  background-color: chartreuse;
+}
+.expandItem {
+  flex: 1 0 auto;
+  height: 100px;
+  background-color: chocolate;
+}
+

위와 같이 컨테이너의 높이가 500px이고, 위아래 요소는 100px의 높이를 가질때, expand 엘리먼트에 flex : 1 0 auto를 똑같이 주면 아래와 같이 나온다.

image-20220121015610969

제대로 확장됨을 알 수 있다.

grid

  1. 아래와 같이 속성을 사용하면 된다.(또는 minmax 없이 auto만 사용해도 됨.)
1
+2
+3
+
display: grid;
+grid-template-columns: 100px minmax(0px, auto) 100px;
+grid-template-rows: 100px minmax(0px, auto);
+

image-20220121020823208

2번째 열의 요소들은 수평으로 확장되었고, 2번째 행의 요소들은 수직으로 제대로 확장되었다.

  1. 또는 아래와 같이, 확장하고자 하는 부분을 1fr으로 설정하고, 나머지는 auto로 설정하면 나머지는 자신들의 최소 부분만 차지하고 확장하고자하는 부분을 최대로 확장한다. (auto 대신 max-content 를 사용해도 됨)
1
+2
+
display: grid;
+grid-template-columns: auto 1fr auto;
+

주의점

이때 flex를 활용하여 확장을 할때 주의해야할 점이 있다.

바로 확장해야할 요소를 제외한 요소는 크기가 필요한만큼은 정해져 있어야 한다는 점이다. 그렇지 않으면 expand 요소는 최대한으로 커질 것으로, 그 뜻은 아래 사진과 같이 크기가 정해져있지 않은 요소의 크기는 최소로 정한다는 말이다.

image-20220121020027651

따라서 만약 다른 요소들의 크기를 어느정도는 여유있게 확보해야한다면, 크기를 정해주어야한다.

This post is licensed under CC BY 4.0 by the author.

13144 List of Unique Numbers

(CSS)다른 엘리먼트의 이벤트 발생 시, 스타일 적용법

Comments powered by Disqus.

diff --git "a/posts/flutter-(rn-\352\260\234\353\260\234\354\236\220\353\245\274-\354\234\204\355\225\234-\354\240\225\353\246\254)/index.html" "b/posts/flutter-(rn-\352\260\234\353\260\234\354\236\220\353\245\274-\354\234\204\355\225\234-\354\240\225\353\246\254)/index.html" new file mode 100644 index 000000000..d7c92ccb9 --- /dev/null +++ "b/posts/flutter-(rn-\352\260\234\353\260\234\354\236\220\353\245\274-\354\234\204\355\225\234-\354\240\225\353\246\254)/index.html" @@ -0,0 +1,131 @@ + flutter (rn 개발자를 위한 정리 1) | 디피의 개발일지
Posts flutter (rn 개발자를 위한 정리 1)
Post
Cancel

flutter (rn 개발자를 위한 정리 1)

진입점

1
+2
+3
+
main() {
+    // 항상 최상단 앱의 진입점 main() 이 있어야함.
+}
+

콘솔

1
+
print("hello world")
+

변수

  • dart 는 타입검사를 하는 언어
  • 정적 타입 검사와 런타임 타입검사를 동시에 사용하며, 변수의 값이 변수의 정적타입과 항상 일치하는지 검사함.
  • 타입 추론을 하기에 일부 타입표기는 생략가능
1
+2
+
String name = 'dart';	// 명시적 타입 선언
+var othername = 'dart';	//타입 추론
+
  • 초기화하지 않은 변수는 null값을 가짐.
1
+2
+
var name;		// null
+int x;		// null
+

null, 0 체크

  • dart에서는 bool 값 true만 true로 취급한다.
  • 따라서 null, 0 값은 그 값으로 대응 되는지 확인해야함.
1
+2
+3
+4
+5
+6
+7
+8
+
var myNull = null;
+if (myNull == null) {
+    print("is null");
+}
+var zero = 0;
+if(zero == 0) {
+    print("is 0")
+}
+

함수

  • 자바스크립트하고는 선언에서 차이가 남.
1
+2
+3
+4
+5
+6
+7
+
fn() {
+    return true;
+}
+
+bool fn() {
+    return true;
+}
+
  • 한줄 함수일때는 화살표함수 사용가능
1
+
bool fn() => true;
+

비동기 프로그래밍

  • dart에서는 Future 객체를 사용하여 비동기를 지원함

    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +14
    +15
    +16
    +17
    +18
    +19
    +20
    +
    import 'dart:convert';
    +import 'package:http/http.dart' as http;
    +  
    +class Example {
    +  Future<String> _getIPAddress() {
    +    final url = 'https://httpbin.org/ip';
    +    return http.get(url).then((response) {
    +      String ip = jsonDecode(response.body)['origin'];
    +      return ip;
    +    });
    +  }
    +}
    +  
    +main() {
    +  final example = new Example();
    +  example
    +      ._getIPAddress()
    +      .then((ip) => print(ip))
    +      .catchError((error) => print(error));
    +}
    +
  • 또 async, await로 비동기를 처리할 수 있음.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+
import 'dart:convert';
+import 'package:http/http.dart' as http;
+
+class Example {
+  Future<String> _getIPAddress() async {
+    final url = 'https://httpbin.org/ip';
+    final response = await http.get(url);
+    String ip = jsonDecode(response.body)['origin'];
+    return ip;
+  }
+}
+
+main() async {
+  final example = new Example();
+  try {
+    final ip = await example._getIPAddress();
+    print(ip);
+  } catch (error) {
+    print(error);
+  }
+}
+
This post is licensed under CC BY 4.0 by the author.

LocateC

flutter (rn 개발자를 위한 정리 2)

Comments powered by Disqus.

diff --git "a/posts/flutter-(rn-\352\260\234\353\260\234\354\236\220\353\245\274-\354\234\204\355\225\234-\354\240\225\353\246\254-2)/index.html" "b/posts/flutter-(rn-\352\260\234\353\260\234\354\236\220\353\245\274-\354\234\204\355\225\234-\354\240\225\353\246\254-2)/index.html" new file mode 100644 index 000000000..0e6deffab --- /dev/null +++ "b/posts/flutter-(rn-\352\260\234\353\260\234\354\236\220\353\245\274-\354\234\204\355\225\234-\354\240\225\353\246\254-2)/index.html" @@ -0,0 +1,231 @@ + flutter (rn 개발자를 위한 정리 2) | 디피의 개발일지
Posts flutter (rn 개발자를 위한 정리 2)
Post
Cancel

flutter (rn 개발자를 위한 정리 2)

기본

앱 생성

  1. IDE 에서 생성하는 방법
  2. 커맨드라인에서 생성하는 방법
1
+
$ flutter create <projectname>
+

앱 실행

  1. IDE에서 run 클릭
  2. 최상위 디렉토리에서 flutter run 입력

import

1
+2
+3
+4
+
import 'package:flutter/material.dart';
+import 'package:flutter/cupertino.dart'; 	// ios 위젯
+import 'package:flutter/widgets.dart';
+import 'package:flutter/my_widgets.dart';
+

hello world

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+
import 'package:flutter/material.dart';
+
+void main() {
+  runApp(
+    Center(
+      child: Text(
+        'Hello, world!',
+        textDirection: TextDirection.ltr,
+      ),
+    ),
+  );
+}
+

위젯트리

  • flutter에서 거의 모든 것이 위젯임
  • 위젯은 위젯트리라고 불리는 계층 구조로 조합됨.
  • 각 위젯은 부모의 위젯 내부에 들어가고, 부모로부터 속성을 상속 받음
  • 앱 객체 도 위젯이며, 최상위 위젯이 앱 객체임.

다음은 hellow world 위젯

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
import 'package:flutter/material.dart';
+
+void main() => runApp(MyApp());
+
+class MyApp extends StatelessWidget {
+  @override
+  Widget build(BuildContext context) {
+    return MaterialApp(
+      title: 'Welcome to Flutter',
+      home: Scaffold(
+        appBar: AppBar(
+          title: Text('Welcome to Flutter'),
+        ),
+        body: Center(
+          child: Text('Hello world'),
+        ),
+      ),
+    );
+  }
+}
+
  • StatelessWidget

    • 상태가 없는 위젯으로, 한번 만들어지면 변하지 않음
  • StatefulWidget

    • 사용자 입력이나 데이터 수신으로 상태가 동적으로 변화한다.

    • StatefulWidget에 상태 데이터를 저장하고, 그것을 트리 재구성을 통해 전달하는 State 객체가 있음.
    • 너무 많이 감싸져있는 위젯은 함수로 빼거나 작은 클래스로 분리해야한다.

재사용 가능한 컴포넌트 만들기

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+
// React Native
+class CustomCard extends React.Component {
+  render() {
+    return (
+      <View>
+        <Text> Card {this.props.index} </Text>
+        <Button
+          title="Press"
+          onPress={() => this.props.onPress(this.props.index)}
+        />
+      </View>
+    );
+  }
+}
+
+// Usage
+<CustomCard onPress={this.onPress} index={item.key} />
+
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+
// Flutter
+class CustomCard extends StatelessWidget {
+  CustomCard({@required this.index, @required this.onPress});
+
+  final index;
+  final Function onPress;
+
+  @override
+  Widget build(BuildContext context) {
+    return Card(
+      child: Column(
+        children: <Widget>[
+          Text('Card $index'),
+          FlatButton(
+            child: const Text('Press'),
+            onPressed: this.onPress,
+          ),
+        ],
+      )
+    );
+  }
+}
+    ...
+// Usage
+CustomCard(
+  index: index,
+  onPress: () {
+    print('Card $index');
+  },
+)
+    ...
+

프로젝트 구조 및 리소스

시작

  • main.dart
1
+2
+3
+4
+
// Dart
+void main(){
+ print('Hello, this is the main function.');
+}
+

프로젝트 구성

  • 초기 상태. 변경가능
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+
┬
+└ projectname
+  ┬
+  ├ android      - Android 관련 파일 포함.
+  ├ build        - iOS 및 Android 빌드 파일 저장.
+  ├ ios          - iOS 관련 파일 포함.
+  ├ lib          - 외부에서 접근할 수 있는 Dart 소스 파일 포함.
+    ┬
+    └ src        - 추가적인 소스 파일 포함.
+    └ main.dart  - Flutter 진입점이며 새로운 앱의 시작.
+                   Flutter 프로젝트를 만들 때 자동으로 생성.
+                   Dart코드 작성을 시작하는 부분임.
+  ├ test         - 자동화 테스트 파일 포함.
+  └ pubspec.yaml - Flutter 앱의 메타데이터 포함.
+                   React Native의 package.json 파일과 동일함.
+

asset 위치

  • pubspec.yaml 파일에 assets을 명시
1
+2
+3
+4
+
flutter:
+  assets:
+    - assets/my_icon.png
+    - assets/background.png
+
  • 위 파일에 명시된 상대 경로로 asset 파일의 위치가 구분됨.
  • 플러터는 런타임때에 읽어올 asset을 asset bundle 이라 불리는 특수한 아카이브에 저장한다.
    • 앱에 적합한 해상도의 이미지를 선택할 떄 asset variants 를 사용함

사용법

  • 위젯의 build 메서드 안에서 AssetImage 클래스를 사용하여 정적이미지 추가
1
+
image: AssetImage('assets/background.png'),
+

네트워크를 통한 이미지 불러오기

1
+2
+
body: Image.network(
+          'https://flutter-io.kr/images/owl.jpg',
+

패키지, 패키지 플러그인 설치

  1. pubspec.yaml 의 dependencies 부분에 패키지 이름과 버전을 추가
1
+2
+3
+4
+
dependencies:
+  flutter:
+    sdk: flutter
+  google_sign_in: ^3.0.3
+
  1. 커맨드라인에서 flutter pub get 을 사용하여 패키지 설치. IDE에서는 버튼클릭
This post is licensed under CC BY 4.0 by the author.

flutter (rn 개발자를 위한 정리 1)

flutter (rn 개발자를 위한 정리 3)

Comments powered by Disqus.

diff --git "a/posts/flutter-(rn-\352\260\234\353\260\234\354\236\220\353\245\274-\354\234\204\355\225\234-\354\240\225\353\246\2543)/index.html" "b/posts/flutter-(rn-\352\260\234\353\260\234\354\236\220\353\245\274-\354\234\204\355\225\234-\354\240\225\353\246\2543)/index.html" new file mode 100644 index 000000000..84e897472 --- /dev/null +++ "b/posts/flutter-(rn-\352\260\234\353\260\234\354\236\220\353\245\274-\354\234\204\355\225\234-\354\240\225\353\246\2543)/index.html" @@ -0,0 +1,485 @@ + flutter (rn 개발자를 위한 정리 3) | 디피의 개발일지
Posts flutter (rn 개발자를 위한 정리 3)
Post
Cancel

flutter (rn 개발자를 위한 정리 3)

Flutter 위젯

Views

View 컨테이너

  • React Native 에서는 View 가 컨테이너이고 Flexbox를 이용한 레이아웃, 스타일, 터치 핸들링, 접근성제어를 지원

  • Flutter에서는 Container나, Column, Row, Center같은 위젯 라이브러리의 핵심 레이아웃 위젯을 사용할 수 있음.

FlatList, SectionList

  • ListView : 목록의 수가 적은 경우에 가장 적합함.
  • 무거운 목록이거나 무한 스크롤 목록일때는 ListView.builder 를 사용
    • 자식들을 필요할 때만 빌드하고, 화면에 나타나야할 자식들만 빌드함
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+
// Flutter
+var data = [ ... ];
+ListView.builder(
+  itemCount: data.length,
+  itemBuilder: (context, int index) {
+    return Text(
+      data[index],
+    );
+  },
+)
+

Canvas

  • ReactNative에서는 직접적으로 지원하는 컴포넌트가 없음
  • Flutter에서는 CustomPaint, CustomPainter 클래스를 사용하여 캔버스를 그릴 수 있다.
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+
// Flutter
+class MyCanvasPainter extends CustomPainter {
+
+  @override
+  void paint(Canvas canvas, Size size) {
+    Paint paint = Paint();
+    paint.color = Colors.amber;
+    canvas.drawCircle(Offset(100.0, 200.0), 40.0, paint);
+    Paint paintRect = Paint();
+    paintRect.color = Colors.lightBlue;
+    Rect rect = Rect.fromPoints(Offset(150.0, 300.0), Offset(300.0, 400.0));
+    canvas.drawRect(rect, paintRect);
+  }
+
+  bool shouldRepaint(MyCanvasPainter oldDelegate) => false;
+  bool shouldRebuildSemantics(MyCanvasPainter oldDelegate) => false;
+}
+class _MyCanvasState extends State<MyCanvas> {
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      body: CustomPaint(
+        painter: MyCanvasPainter(),
+      ),
+    );
+  }
+}
+

Canvas on iOS

레이아웃

레이아웃 속성 정의

  • React native에서는 style props를 이용해 flexbox 속성을정의함.

    1
    +2
    +3
    +4
    +
    // React Native
    +<View
    +  style=
    +>
    +
  • flutter에서는 컨트롤 위젯 및 스타일 속성을 결합하여 설계된 위젯을 통해 레이아웃을 정의한다.

    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +14
    +15
    +16
    +17
    +18
    +19
    +20
    +21
    +22
    +
    // Flutter
    +Center(
    +  child: Column(
    +    children: <Widget>[
    +      Container(
    +        color: Colors.red,
    +        width: 100.0,
    +        height: 100.0,
    +      ),
    +      Container(
    +        color: Colors.blue,
    +        width: 100.0,
    +        height: 100.0,
    +      ),
    +      Container(
    +        color: Colors.green,
    +        width: 100.0,
    +        height: 100.0,
    +      ),
    +    ],
    +  ),
    +)
    +

위젯을 겹쳐 쌓아올리는 방법

  • React Native에서는 absolute 을 사용
  • flutter에서는 Stack을 사용함.
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+
// Flutter
+Stack(
+  alignment: const Alignment(0.6, 0.6),
+  children: <Widget>[
+    CircleAvatar(
+      backgroundImage: NetworkImage(
+        'https://avatars3.githubusercontent.com/u/14101776?v=4'),
+    ),
+    Container(
+      decoration: BoxDecoration(
+          color: Colors.black45,
+      ),
+      child: Text('Flutter'),
+    ),
+  ],
+)
+

Stack on iOS

스타일링

컴포넌트 꾸미기

  • react native 에서는 인라인 스타일링과 stylesheets.create를 이용한 스타일링 가능
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+
<View style={styles.container}>
+  <Text style=>
+    This is a sample text
+  </Text>
+</View>
+
+const styles = StyleSheet.create({
+  container: {
+    flex: 1,
+    backgroundColor: '#fff',
+    alignItems: 'center',
+    justifyContent: 'center'
+  }
+});
+
  • flutter에서는 Text 위젯은 style 속성에 TextStyle클래스를 사용할 수 있음.
    • 여러 곳에서 같은 스타일을 사용하고 싶으면 따로 빼서 적용가능
    • 다른 위젯에서도 비슷한 방식인가?
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+
var textStyle = TextStyle(fontSize: 32.0, color: Colors.cyan, fontWeight:
+   FontWeight.w600);
+	...
+Center(
+  child: Column(
+    children: <Widget>[
+      Text(
+        'Sample text',
+        style: textStyle,
+      ),
+      Padding(
+        padding: EdgeInsets.all(20.0),
+        child: Icon(Icons.lightbulb_outline,
+          size: 48.0, color: Colors.redAccent)
+      ),
+    ],
+  ),
+)
+

Icons와 Colors를 사용하는 방법은?

  • flutter에서는 material 라이브러리를 import하면 다양한 material icon컬러를 가져옴
1
+
Icon(Icons.lightbulb_outline, color: Colors.redAccent)
+
  • Icons 클래스를 사용할떄는 pubspec.yaml 파일에 uses-material-design : true 를 꼭 설정해줘야함
1
+2
+
name: my_awesome_application
+flutter: uses-material-design: true
+
  • 쿠퍼티노 아이콘 사용
1
+2
+3
+
name: my_awesome_application
+dependencies:
+  cupertino_icons: ^0.1.0
+
  • 컴포넌트 전체적인 색과 스타일을 지정하고 싶으면 ThemeData 클래스를 사용
    • MaterialApp 에서 theme 속성에 ThemeData 객체를 설정한다.
    • Colors 클래스는 material 디자인의 color paletter에 해당하는 색상을 제공
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+
class SampleApp extends StatelessWidget {
+  @override
+  Widget build(BuildContext context) {
+    return MaterialApp(
+      title: 'Sample App',
+      theme: ThemeData(
+        primarySwatch: Colors.blue,
+        textSelectionColor: Colors.red
+      ),
+      home: SampleAppPage(),
+    );
+  }
+}
+

스타일 테마 추가

  • React Native에서는 컴포넌트 공통 테마는 stylesheets에 정의한 후 컴포넌트에 사용함
  • Flutter에서는 ThemeData 클래스에 스타일을 정의하고, MaterialApp 위젯의 테마 속성에 전달함으로써 거의 모든 곳에 균일한 스타일을 적용할 수 있음.
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+
 @override
+  Widget build(BuildContext context) {
+    return MaterialApp(
+      theme: ThemeData(
+        primaryColor: Colors.cyan,
+        brightness: Brightness.dark,
+      ),
+      home: StylingPage(),
+    );
+  }
+
  • Theme는 MaterialApp 위젯을 사용하지 않아도 적용가능.
    • data 매개변수에서 ThemeData를 가져와 모든 자식 위젯에 ThemeData를 적용함.
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+
 @override
+  Widget build(BuildContext context) {
+    return Theme(
+      data: ThemeData(
+        primaryColor: Colors.cyan,
+        brightness: brightness,
+      ),
+      child: Scaffold(
+         backgroundColor: Theme.of(context).primaryColor,
+              ...
+              ...
+      ),
+    );
+  }
+

상태관리

StatelessWidget

  • 상태 변화가 필요없는 위젯.
  • 객체의 자체적인 구성정보나 위젯의 BuildContext 이외의 것에 의존하지 않을때 유용함

  • 상태가 없는 위젯의 build 메서드는 보통 3가지 상황에서만 호출됨
    • 위젯이 트리에 추가될 때
    • 위젯의 부모가 설정이 변경됐을때
    • 사용하고 있는 InheritedWidget이 변경될 때

StatefulWidget

  • setState를 호출하여 Flutter 프레임워크에게 상태가 변경됐음을 알려줌.

    • 이후 앱이 build 메서드를 다시 실행하여 변경사항을 반영함
  • 아래는 createState() 메서드를 필요로하는 StatefulWidget을 선언하는 예시

    • 위젯의 상태를 관리하는 상태객체 _MyStatefulWidgetState를 생성함
    1
    +2
    +3
    +4
    +5
    +6
    +7
    +
    class MyStatefulWidget extends StatefulWidget {
    +  MyStatefulWidget({Key key, this.title}) : super(key: key);
    +  final String title;
    +
    +  @override
    +  _MyStatefulWidgetState createState() => _MyStatefulWidgetState();
    +}
    +
  • 아래는 _MyStatefulWidgetState 라는 상태 클래스.

    • 상태가 바뀌면 setState가 새로운 toggle 값과 함께 호출됨.
    • 이후 프레임워크가 UI위젯을 다시 빌드함.
    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +14
    +15
    +16
    +17
    +18
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +26
    +27
    +28
    +29
    +30
    +31
    +32
    +33
    +34
    +35
    +36
    +37
    +38
    +39
    +40
    +41
    +42
    +43
    +44
    +45
    +46
    +47
    +48
    +49
    +50
    +51
    +
    class _MyStatefulWidgetState extends State<MyStatefulWidget> {
    +  bool showtext=true;
    +  bool toggleState=true;
    +  Timer t2;
    +
    +  void toggleBlinkState(){
    +    setState((){
    +      toggleState=!toggleState;
    +    });
    +    var twenty = const Duration(milliseconds: 1000);
    +    if(toggleState==false) {
    +      t2 = Timer.periodic(twenty, (Timer t) {
    +        toggleShowText();
    +      });
    +    } else {
    +      t2.cancel();
    +    }
    +  }
    +
    +  void toggleShowText(){
    +    setState((){
    +      showtext=!showtext;
    +    });
    +  }
    +
    +  @override
    +  Widget build(BuildContext context) {
    +    return Scaffold(
    +      body: Center(
    +        child: Column(
    +          children: <Widget>[
    +            (showtext
    +              ?(Text('This execution will be done before you can blink.'))
    +              :(Container())
    +            ),
    +            Padding(
    +              padding: EdgeInsets.only(top: 70.0),
    +              child: RaisedButton(
    +                onPressed: toggleBlinkState,
    +                child: (toggleState
    +                  ?( Text('Blink'))
    +                  :(Text('Stop Blinking'))
    +                )
    +              )
    +            )
    +          ],
    +        ),
    +      ),
    +    );
    +  }
    +}
    +

StatefulWidget 과 StatelessWidget의 모범사례

위젯을 설계할때의 고려사항

  1. 위젯이 StatefulWidget인지 StatelessWidget인지 결정하라

    • 위젯이 변화하면 Stateful 사용
    • 위젯이 final이거나 immutable이면, Stateless를 사용
  2. 어떤 객체가 위젯의 상태를 관리하는지 결정하라

    • Flutter에서 상태를 관리하는 3가지 방법
      • 위젯이 자신의 상태를 관리
      • 부모위젯이 상태를 관리
      • 혼합하여 관리
    • 원칙
      • 해당 상태가 사용자 입력이라면, 상위위젯에서 관리하는 것이 가장 좋음
        • ex : 슬라이더 위치 혹은 체크박스의 선택과 취소
      • 해당 상태가 UI와 연관이 깊으면, 해당 위젯에서 관리
        • ex : 애니메이션
      • 잘모르겠을때는 부모 위젯이 관리하도록 함
  3. StatefulWidget의 하위 클래스 및 State

    • 아래 예시는 자신이 직접 상태를 관리하는 것
    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +14
    +15
    +
    class MyStatefulWidget extends StatefulWidget {
    +  MyStatefulWidget({Key key, this.title}) : super(key: key);
    +  final String title;
    +
    +  @override
    +  _MyStatefulWidgetState createState() => _MyStatefulWidgetState();
    +}
    +
    +class _MyStatefulWidgetState extends State<MyStatefulWidget> {
    +
    +  @override
    +  Widget build(BuildContext context) {
    +    ...
    +  }
    +}
    +
  4. StatefulWidget을 위젯트리에 추가하기

    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +14
    +
    class MyStatelessWidget extends StatelessWidget {
    +  // This widget is the root of your application.
    +
    +  @override
    +  Widget build(BuildContext context) {
    +    return MaterialApp(
    +      title: 'Flutter Demo',
    +      theme: ThemeData(
    +        primarySwatch: Colors.blue,
    +      ),
    +      home: MyStatefulWidget(title: 'State Change Demo'),
    +    );
    +  }
    +}
    +
    • 위와같이 Stateless 위젯 안에 Stateful 위젯을 추가하는 방식도 통함
This post is licensed under CC BY 4.0 by the author.

flutter (rn 개발자를 위한 정리 2)

flutter (rn 개발자를 위한 정리 4)

Comments powered by Disqus.

diff --git "a/posts/flutter-(rn-\352\260\234\353\260\234\354\236\220\353\245\274-\354\234\204\355\225\234-\354\240\225\353\246\2544)/index.html" "b/posts/flutter-(rn-\352\260\234\353\260\234\354\236\220\353\245\274-\354\234\204\355\225\234-\354\240\225\353\246\2544)/index.html" new file mode 100644 index 000000000..bae53fcc1 --- /dev/null +++ "b/posts/flutter-(rn-\352\260\234\353\260\234\354\236\220\353\245\274-\354\234\204\355\225\234-\354\240\225\353\246\2544)/index.html" @@ -0,0 +1,557 @@ + flutter (rn 개발자를 위한 정리 4) | 디피의 개발일지
Posts flutter (rn 개발자를 위한 정리 4)
Post
Cancel

flutter (rn 개발자를 위한 정리 4)

Props

  • ReactNative 에서 대부분의 컴포넌트는 매겨변수나 속성을 props로 전달함

  • Flutter에서는 매개변수가 있는 생성자에서 받은 속성을 final로 표시된 지역변수나 함수에 할당함

    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +14
    +15
    +16
    +17
    +18
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +26
    +27
    +28
    +29
    +
    // Flutter
    +class CustomCard extends StatelessWidget {
    +  
    +  CustomCard({@required this.index, @required this.onPress});
    +  final index;
    +  final Function onPress;
    +  
    +  @override
    +  Widget build(BuildContext context) {
    +  return Card(
    +    child: Column(
    +      children: <Widget>[
    +        Text('Card $index'),
    +        FlatButton(
    +          child: const Text('Press'),
    +          onPressed: this.onPress,
    +        ),
    +      ],
    +    ));
    +  }
    +}
    +    ...
    +//Usage
    +CustomCard(
    +  index: index,
    +  onPress: () {
    +    print('Card $index');
    +  },
    +)
    +

로컬 저장소

  • 많은 데이터가 아니라면, 키-값 쌍 형태의 저장소인 shared_preference 플러그인으로 기본 타입(booleans, floats, int, long, string) 데이터를 읽고 쓸 수 있음

    • ios에선 NSUserDefaults, Android에선 SharedPreferences를 감싸고 있음
    • 다음과 같이 의존성을 추가한 후, import하여 사용
    1
    +2
    +3
    +4
    +
    dependencies:
    +  flutter:
    +    sdk: flutter
    +  shared_preferences: ^0.4.3
    +
    1
    +2
    +
    // Dart
    +import 'package:shared_preferences/shared_preferences.dart';
    +
  • 데이터 저장 : SharedPreferences 클래스의 setter 메소드 사용

    • setInt, setBool, setString 과 같은 방식의 다양한 기본형 타입에서 사용가능하다.
  • 데이터 읽기 : SharedPreferences 클래스의 getter 메소드 사용

    • getInt, getBool, getString 과 같은 함수 사용
1
+2
+3
+4
+5
+6
+7
+
SharedPreferences prefs = await SharedPreferences.getInstance();
+_counter = prefs.getInt('counter');
+prefs.setInt('counter', ++_counter);
+setState(() {
+  _counter = _counter;
+});
+
+

Routing

  • 플러터에서는 새로운 화면도 위젯이다.
  • Navigator 위젯을 통해 이동

스택 네비게이션

  • Route 위젯 : 앱의 화면 또는 페이지를 추상화 한 것

  • Navigator 위젯 : route를 관리하는 위젯

    • 스택을 사용하여 자식 위젯(Route 객체)들을 관리함
    • Navigator.push, Navigator.pop 같은 메서드를 제공
    • Route 목록은 MaterialApp 위젯에서 지정가능
    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +14
    +15
    +
    // Flutter
    +class NavigationApp extends StatelessWidget {
    +  // This widget is the root of your application.
    +  @override
    +  Widget build(BuildContext context) {
    +    return MaterialApp(
    +            ...
    +      routes: <String, WidgetBuilder>{
    +        '/a': (BuildContext context) => usualNavscreen(),		//Named route 지정
    +        '/b': (BuildContext context) => drawerNavscreen(),
    +      }
    +            ...
    +  );
    +  }
    +}
    +
  • Named route로 이동하기 위해, of메서드를 사용하여 Navigator 위젯의 BuildContext를 지정함.

    • Route 의 이름은 pushNamed 함수를 통해 전달하여 특정한 route로 이동함
    1
    +
    Navigator.of(context).pushNamed('/a');
    +
  • Navigator의 push 메서드로 주어진 route를 가장 가까운 context를 가진 네비게이터에 추가하고 이동할 수 있음.

    • 아래는 MaterialPageRoute 위젯이 플랫폼에 적함한 전환효과와 함께 전체 스크린을 modal route로 전환시킴.
    • 필수 매개변수로 WidgetBuilder를 넣어줘야함
    1
    +2
    +
    Navigator.push(context, MaterialPageRoute(builder: (BuildContext context)
    + => UsualNavscreen()));
    +

탭 네비게이션

  • Flutter는 drawer 및 탭 네비게이션을 위한 여러 특수 위젯을 제공함

    • TabController: TabBar와 TabBarView 사이에서 탭 선택을 조정
      • lengths : 전체 탭 개수, vsync : 프레임이 상태 변화를 할때마다 알려주기 위한 TickerProvider
    • TabBar: 탭의 수평 열을 보여줌
    • Tab: Material 디자인으로 TabBar 탭을 생성
    • TabBarView: 현재 선택된 탭에 맞는 위젯을 보여줌.
    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +
    // Flutter
    +TabController controller=TabController(length: 2, vsync: this);
    +  
    +TabBar(
    +  tabs: <Tab>[
    +    Tab(icon: Icon(Icons.person),),
    +    Tab(icon: Icon(Icons.email),),
    +  ],
    +  controller: controller,
    +),
    +
  • TickerProvider : Ticker 객체를 제공할 수 있는 클래스에 의해 구현된 인터페이스

    • Ticker : 프레임이 트리거 될때마다 알림을 받아야하는 모든 객체에서 사용할 수 있음

      • 보통 AnimationController를 통해 간접적으로 사용함

      • AnimationController는 Ticker를 얻기위해 TickerProvider가 필요함

      • State에서 AnimationController를 만들고 있다면, TickerProviderStateMixin 또는 SingleTickerProviderStateMixin 클래스를 사용하여 적절한 TickerProvide를 얻음

  • 아래 Scaffold 위젯은 새로운 TabBar 위젯을 감싸고 2개의 탭을 생성

    • TabBarView는 body 인자로 전달됨.
    • TabBar 위젯의 탭에 해당하는 모든 화면은 같은 TabController를 가지고 있는 TabBarView 위젯의 자식들임
    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +14
    +15
    +16
    +17
    +18
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +
    class _NavigationHomePageState extends State<NavigationHomePage> with SingleTickerProviderStateMixin {
    +  TabController controller=TabController(length: 2, vsync: this);
    +  @override
    +  Widget build(BuildContext context) {
    +    return Scaffold(
    +      bottomNavigationBar: Material (
    +        child: TabBar(
    +          tabs: <Tab> [
    +            Tab(icon: Icon(Icons.person),)
    +            Tab(icon: Icon(Icons.email),),
    +          ],
    +          controller: controller,
    +        ),
    +        color: Colors.blue,
    +      ),
    +      body: TabBarView(
    +        children: <Widget> [
    +          home.homeScreen(),
    +          tabScreen.tabScreen()
    +        ],
    +        controller: controller,
    +      )
    +    );
    +  }
    +}
    +

Drawer 네비게이션

  • Drawer 위젯을 Scaffold와 조합하여 material 디자인의 drawer를 만들 수 있음
    • Drawer을 Scaffold 위젯으로 감싸야함
  • Drawer 위젯은 Scaffold 의 왼쪽/오른쪽 모서리에서 슬라이트 형태로 등장하여 앱의 네비게이션 링크를 보여줌
    • Button이나 Text 또는 여러 아이템 목록을 Drawer 위젯의 자식으로 보여줄 수 있음
    • 아래 예제는 ListTile 을 자식으로 보여줌
  • Scaffold 위젯은 Drawer을 사용할 수 있을 때 Drawer로 연결되는 IconButton을 띄어주는 AppBar 위젯을 포함함.
    • 왼쪽 위에 버튼 눌러서 Drawer 가능하도록
  • Scaffold를 사용하면 edge-swipe 동작을 했을때 자동으로 Drawer가 나타남
    • 왼쪽에서 당겼을때 drawer가 나오도록
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
// Flutter
+@override
+Widget build(BuildContext context) {
+  return Scaffold(
+    drawer: Drawer(
+      child: ListTile(
+        leading: Icon(Icons.change_history),
+        title: Text('Screen2'),
+        onTap: () {
+          Navigator.of(context).pushNamed('/b');
+        },
+      ),
+      elevation: 20.0,
+    ),
+    appBar: AppBar(
+      title: Text('Home'),
+    ),
+    body: Container(),
+  );
+}
+

제스처 감지와 터치 이벤트 다루기

  • Flutter 제스처는 2개의 층이 있음
    • 첫번째 층 : 포인터의 위치와 움지기임을 표현하는 원시 포인터 이벤트(터치, 마우스, 스타일러스 동작)
    • 두번째 층 : 하나 이상의 포인터 움직임으로 만들어진 동작을 표현하는 제스처

Click/Press 리스너

  • ReactNative 에서는 Touchable 컴포넌트를 사용함

  • Flutter에서는 onPress : field 를 가진 버튼이나 터치 가능한 위젯을 사용함

    • 또는 어떤 위젯이건 GestureDetector로 감싸서 제스처 감지를 추가할 수도 있음
    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +14
    +15
    +16
    +17
    +18
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +26
    +27
    +28
    +
    // Flutter
    +GestureDetector(
    +  child: Scaffold(
    +    appBar: AppBar(
    +      title: Text('Gestures'),
    +    ),
    +    body: Center(
    +      child: Column(
    +        mainAxisAlignment: MainAxisAlignment.center,
    +        children: <Widget>[
    +          Text('Tap, Long Press, Swipe Horizontally or Vertically '),
    +        ],
    +      )
    +    ),
    +  ),
    +  onTap: () {
    +    print('Tapped');
    +  },
    +  onLongPress: () {
    +    print('Long Pressed');
    +  },
    +  onVerticalDragEnd: (DragEndDetails value) {
    +    print('Swiped Vertically');
    +  },
    +  onHorizontalDragEnd: (DragEndDetails value) {
    +    print('Swiped Horizontally');
    +  },
    +);
    +

HTTP 네트워크 요청 만들기

  • Flutter에서는 http 패키지를 사용함. 아래처럼 의존성 추가
1
+2
+3
+4
+
dependencies:
+  flutter:
+    sdk: flutter
+  http: <latest_version>
+
  • HTTP 클라이언트 지원에 dart:io 를 사용함.

    • dart:io 를 import하여 사용한다.
    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +
    import 'dart:io';
    +...
    +final url = Uri.https('httpbin.org', 'ip');
    +final httpClient = HttpClient();
    +_getIPAddress() async {
    +  var request = await httpClient.getUrl(url);
    +  var response = await request.close();
    +  var responseBody = await response.transform(utf8.decoder).join();
    +  String ip = jsonDecode(responseBody)['origin'];
    +  setState(() {
    +    _ipAddress = ip;
    +  });
    +} 
    +

Form input

TextField 위젯 사용

  • TextEditingController클래스를 사용하여 TextField 위젯을 관리함
  • 위 컨트롤러는 텍스트 필드가 수정될때마다 리스너에게 알림
  • 리스너는 사용자가 필드에 입력한 내용을 알기 위해 text와 selection 속성을 읽음
    • text 속성을 활용하여 TextField 에 있는 텍스트에 접근가능
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+
// Flutter
+final TextEditingController _controller = TextEditingController();
+      ...
+TextField(
+  controller: _controller,
+  decoration: InputDecoration(
+    hintText: 'Type something', labelText: 'Text Field '
+  ),
+),
+RaisedButton(
+  child: Text('Submit'),
+  onPressed: () {
+    showDialog(
+      context: context,
+        child: AlertDialog(
+          title: Text('Alert'),
+          content: Text('You typed ${_controller.text}'),
+        ),
+     );
+   },
+ ),
+)
+

Form

  • Form 위젯의 자식으로 TextFormField 위젯들을 가져서 구현가능
  • TextFormField 위젯에는 form이 저장될 때 실행되는 콜백을 받는 onSaved라는 매개변수가 있음
  • FormState 객체는 Form 하위에 있는 각 FormField 를 저장하고, 초기화하고, validation하기 위해 사용함
  • Form의 부모 context와 함께 Form.of를 사용하거나, Form 생성자에 GlobalKey를 넘기고 GlobalKey.currentState를 호출하여 FormState를 가져올 수 있음
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+
final formKey = GlobalKey<FormState>();
+
+...
+
+Form(
+  key:formKey,
+  child: Column(
+    children: <Widget>[
+      TextFormField(
+        validator: (value) => !value.contains('@') ? 'Not a valid email.' : null,
+        onSaved: (val) => _email = val,
+        decoration: const InputDecoration(
+          hintText: 'Enter your email',
+          labelText: 'Email',
+        ),
+      ),
+      RaisedButton(
+        onPressed: _submit,
+        child: Text('Login'),
+      ),
+    ],
+  ),
+)
+
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+
void _submit() {
+  final form = formKey.currentState;
+  if (form.validate()) {
+    form.save();
+    showDialog(
+      context: context,
+      child: AlertDialog(
+        title: Text('Alert'),
+        content: Text('Email: $_email, password: $_password'),
+      )
+    );
+  }
+}
+

Platform-specific 코드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+
// Flutter
+if (Theme.of(context).platform == TargetPlatform.iOS) {
+  return 'iOS';
+} else if (Theme.of(context).platform == TargetPlatform.android) {
+  return 'android';
+} else if (Theme.of(context).platform == TargetPlatform.fuchsia) {
+  return 'fuchsia';
+} else {
+  return 'not recognised ';
+}
+

## 디버깅

  • DevTools를 사용
    • 프로파일링, 힙검사, 위젯트리 조사, 로깅 진단, 디버깅, 실행된 코드라인 관찰, 메모리 누수 및 메모리 조각화 디버깅 지원

인앱 개발자 메뉴에 접근

  • IDE를 사용중이면, IDE 도구를 사용할 수 있음
  • flutter run을 사용했다면, 터미널 창에서 h를 눌러서 접근가능
    • 또는 다양한 터미널 단축키등이 있음 : 링크

애니메이션

  • Animation 클래스
    • 현재 값과 상태(완료 또는 해제)를 알고 있는 추상 클래스
  • AnimationController 클래스
    • 애니메이션을 앞뒤로 재생하거나, 중지, 특정 값으로 설정 하는 등의 모션을 커스터마이징 가능

간단한 fade-in 애니메이션

  • AnimationController 객체를 만들고, 기간을 지정

    • 기본적으로 AnimationController는 특정 기간 동안 0.0 ~ 1.0 범위에서 값이 선형적으로 증가함

    • 일반적으로 초당 60 FPS로, 기기에서 새로운 프레임을 보여줄 준비가 됐을 때마다 새로운 값을 생성함

  • AnimationController를 정의할 때, vsync 객체를 넘겨줘야함

    • 그래야 화면이 꺼져있을때 애니메이션에 불필요한 리소스를 소비하지 않음

    • 클래스 정의에 TickerProviderStateMixin을 추가하여 상태 저장객체를 vsync로 사용할 수 있음

    • AnimationController 에는 생성자에 vsync 인수를 사용하여 만들어진 TickerProvider가 필요함

  • Tween은 시작값과 끝 값 사이의 보간 또는 입력 범위에서 출력 범위까지의 매핑을 표현함

    • 애니메이션과 함께 Tween 객체를 사용하기 위해, Tween 객체의 animate 메서드를 호출한 뒤 수정하고 싶은 Animation 객체로 넘김
  • 아래 예제에서는 FadeTransition 위젯을 사용하고, opacity 속성을 animation 객체에 매핑시킴

    • 애니메이션을 시작하기 위해 controller.forward()를 사용함
    • fling() 이나 repeat()를 사용하여 다른 동작도 가능
    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +14
    +15
    +16
    +17
    +18
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +26
    +27
    +28
    +29
    +30
    +31
    +32
    +33
    +34
    +35
    +36
    +37
    +38
    +39
    +40
    +41
    +
    // Flutter
    +import 'package:flutter/material.dart';
    +  
    +void main() {
    +  runApp(Center(child: LogoFade()));
    +}
    +  
    +class LogoFade extends StatefulWidget {
    +  _LogoFadeState createState() => _LogoFadeState();
    +}
    +  
    +class _LogoFadeState extends State<LogoFade> with TickerProviderStateMixin {
    +  Animation animation;
    +  AnimationController controller;
    +  
    +  initState() {
    +    super.initState();
    +    controller = AnimationController(
    +        duration: const Duration(milliseconds: 3000), vsync: this);
    +    final CurvedAnimation curve =
    +    CurvedAnimation(parent: controller, curve: Curves.easeIn);
    +    animation = Tween(begin: 0.0, end: 1.0).animate(curve);
    +    controller.forward();
    +  }
    +  
    +  Widget build(BuildContext context) {
    +    return FadeTransition(
    +      opacity: animation,
    +      child: Container(
    +        height: 300.0,
    +        width: 300.0,
    +        child: FlutterLogo(),
    +      ),
    +    );
    +  }
    +  
    +  dispose() {
    +    controller.dispose();
    +    super.dispose();
    +  }
    +}
    +

카드에 스와이프 애니메이션을 추가하는 법

  • Dismissble 위젯을 사용하여 자식 위젯을 감쌈
1
+2
+3
+4
+5
+6
+7
+8
+9
+
child: Dismissible(
+  key: key,
+  onDismissed: (DismissDirection dir) {
+    cards.removeLast();
+  },
+  child: Container(
+    ...
+  ),
+),
+

React Native 와 Flutter 사이에 상응하는 위젯 목록

This post is licensed under CC BY 4.0 by the author.

flutter (rn 개발자를 위한 정리 3)

react 작동원리부터 tailwindcss 사용까지

Comments powered by Disqus.

diff --git "a/posts/flutter-\352\263\265\353\266\200/index.html" "b/posts/flutter-\352\263\265\353\266\200/index.html" new file mode 100644 index 000000000..00d79d2b4 --- /dev/null +++ "b/posts/flutter-\352\263\265\353\266\200/index.html" @@ -0,0 +1,3 @@ + flutter 첫 걸음 | 디피의 개발일지
Posts flutter 첫 걸음
Post
Cancel

flutter 첫 걸음

1단계 : Starter Flutter app

  • void main() => runApp(MyApp()); 처럼 화살표 함수 사용가능
    • 한줄 함수에 화살표를 사용한다.
  • 최상단 앱 : StatelessWidget을 상속받아 앱 자체를 위젝으로 만든다. Flutter에서는 정렬, 여백, 레이아웃 등 모든 것이 위젯임.
  • Scaffold 위젯은, app bar, title, body 속성을 기본으로 제공함.
  • 위젯은 build() 를 오버라이딩하여 하위 위젯을 어떻게 표현할지 명시 가능

2단계 : 외부 패키지 사용

설치법

  1. pubspec.yaml 의 dependencies 목록에 패키지를 추가하고, 상단의 pub get을 클릭
  2. flutter pub add english_words 와 같이 설치가능

3단계 : Stateflu 위젯 추가하기

  • stateless 위젯은 변경 불가능 => 모든 값이 final이어야함.

  • stateful 위젯은 변경가능

    • StatefulWidget 클래스가 State 클래스의 인스턴스를 생성해야함. StatefulWidget 클래스 자체는 변하지 않지만, State 클래스는 위젯의 수명동안 상태를 유지함.

4담계 : 무한 스크롤 ListView 생성

  • dart 에서는 식별자 앞에 밑줄을 붙이면 private 적용이 된다.

    1
    +
    final _suggestions = <WordPair>[]
    +
This post is licensed under CC BY 4.0 by the author.

2629 양팔저울

Yourlist

Comments powered by Disqus.

diff --git a/posts/generator/index.html b/posts/generator/index.html new file mode 100644 index 000000000..cf3a233f0 --- /dev/null +++ b/posts/generator/index.html @@ -0,0 +1,69 @@ + generator 문법 | 디피의 개발일지
Posts generator 문법
Post
Cancel

generator 문법

Generator 문법

  • 자바스크립트의 기능이며 Redux-saga의 핵심 기능이다.
  • 함수를 특정 구간에 멈춰놓고, 원할때 다시 돌아가게 할 수 있음. 또 반환을 여러번 할수 있다.

예시

1
+2
+3
+4
+5
+6
+7
+
function* generate() {
+  yield 1;
+  yield 2;
+  yield 3;
+  return 4;
+}
+const generator = generate();
+
  • 제너레이터 함수를 호출 하면, 제너레이터 객체를 반환한다.
  • 이 객체에 있는 메서드인 .next()를 호출하면 yield 한 값을 반환하고 코드의 흐름을 멈춤.
1
+2
+3
+4
+5
+
generator.next()		-> {value : 1, done :false}
+generator.next()		-> {value : 2, done :false}
+generator.next()		-> {value : 3, done :false}
+generator.next() 		-> {value : 4, done :true}
+generator.next()		-> {value : undefined, done :false}
+
  • 아래와 같이 값을 중간에 받을 수도 있다.
1
+2
+3
+4
+5
+
function* sumGenerator() {
+  let a = yield;
+  let b = yield;
+  yield a + b;
+}
+
1
+2
+3
+4
+
generator.next()		-> {value : undefined, done :false}
+generator.next(1)		-> {value : undefined, done :false}
+generator.next(2)		-> {value : undefiend, done :false}
+generator.next()		-> {value : 3, done :true}
+
  • 아래와 같이 next에 넣은 값을 통해 모니터링이 이루어 질수도 있음
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
function* watchGenerator() {
+  while (true) {
+    const action = yield;
+    if (action.type === "type1") {
+      console.log("type 1");
+    }
+    if (action.type === "type2") {
+      console.log("type 2");
+    }
+  }
+}
+
1
+2
+
generator.next()					-> {value : undefined, done : false}
+generator.next({type:'type1'})		-> "type 1"
+
This post is licensed under CC BY 4.0 by the author.

Decision Tree를 이용한 서울 미세먼지 예측 모델

redux-saga concepts

Comments powered by Disqus.

diff --git a/posts/git/index.html b/posts/git/index.html new file mode 100644 index 000000000..be4272665 --- /dev/null +++ b/posts/git/index.html @@ -0,0 +1 @@ + Git | 디피의 개발일지
Posts Git
Post
Cancel

Git

Git

GIT의 개발 목표

  • 빠른 속도
  • 단순한 구조
  • 비선형적인 개발(수천 개의 동시다발적인 브랜치)
  • 완벽한 분산(DVCS)
  • 대형 프로젝트에도 유용할 것

Inside of Git

Git은 기본적으로 파일시스템의 스냅샷을 저장한다.(커밋 당시의 GIT 디렉터리의 모든 파일 정보를 저장) 또한 파일 및 스냅샷을 해시하여 바뀐 버전인지 아닌지 빠르게 체크함

이후 스냅샷들의 크기가 커지면, 주기적으로 git gc(garbage collection)을 통해 delta를 만듦

gitignore

.gitignore 에서는 아래와 같은 glob 패턴을 따름

*.a : 확장자가 .a인 파일을 무시

!lib.a : lib.a 는 무시하지 않도록 함

/TODO : 현재 디렉토리에 있는 TODO 파일은 무시하고, subdir/TODO 처럼 하위 디렉터리에 있는 파일은 무시하지 않음

build/ : build/ 폴더에있는 모든 파일 무시

doc/*.txt : doc/notes.txt는 무시하고, doc/subdir/notes.txt는 무시하지 않음

doc/**/*.pdf : doc 디렉터리 아래의 모든 pdf 파일 무시

Github flow

사용법

  1. main 브랜치는 언제든 배포가능한 상태로

    • 가장 최신의 stable 상태로 배포되는 브랜치.
    • 이 브랜치에 대해서는 엄격한 role를 주어 사용한다.
  2. 새로운 일을 시작하기 위해 브랜치는 main에서 딴다면, 어떤 일을 하는지 이름에 명확히 작성한다.

    • 새로운 기능을 추가하거나 버그를 해결하기 위한 브런치의 이름은 자세하게 어떤 일을 하고 있는지에 대해 작성하여야한다.
    • github 페이지에서 보면 어떤 일이 진행되고있는지 확실이 알수있도록
  3. 원격지 브런치로 수시로 push한다.

    • 항상 자신히 하고 있는 일을 올려 다른 사람들도 확인할 수 있도록 해준다.
    • 로컬에 문제가 생겨 작업하던 부분이 없어져도 원격지의 소스를 받아서 작업할 수 있도록 해준다.
  4. 피드백이 필요할때, 머지 준비가 완료되었을때는 pull request를 생성한다

    • pull request는 코드 리뷰를 도와주는 시스템이다.
    • 그렇기에 이것을 이용하여 자신의 코드를 공유하고, 리뷰를 받을 수 있도록 한다. 머지 준비가 완료되어 main 브런치로 반영을 요구하여도 좋다.
  5. 기능에 대한 리뷰와 사인이 끝난 후 main으로 머지한다.

    • 곧장 product로 반영이 될 기능이기에 충분한 논의 후 반영하도록 한다.
  6. main으로 머지되고 푸시 되었을 때는 즉시 배포되어야한다.

    • 자동화 기법등을 활용하여 즉시 배포한다.

장점

  • 브런치 전략이 단순하다.
  • github 사이트에서 제공하는 기능을 모두 사용하여 작업을 진행한다.
  • 코드 리뷰를 자연스럽게 할 수 있다.
  • CI가 필수적이며, 배포는 자동으로 진행할 수 있다.

단점

  • CI와 배포 자동화가 되어있지 않은 시스템에서는 사람이 관련된 업무를 진행한다.
  • 많은 것이 올라오면 작업에 부담이 된다.

출처

https://www.slideshare.net/ky200223/git-89251791

https://ujuc.github.io/2015/12/16/git-flow-github-flow-gitlab-flow/

This post is licensed under CC BY 4.0 by the author.

MVC 패턴

MVVM, MVP 패턴

Comments powered by Disqus.

diff --git a/posts/hoisting/index.html b/posts/hoisting/index.html new file mode 100644 index 000000000..9d2ad22de --- /dev/null +++ b/posts/hoisting/index.html @@ -0,0 +1,43 @@ + hoisting | 디피의 개발일지
Posts hoisting
Post
Cancel

hoisting

호이스팅은 변수를 끌어올리는 것. var로 선언된 모든 변수 선언을 hoist한다. hoist란 변수의 정의가 그 범위에 따라 선언할당으로 분리되는 것을 의미한다. 즉, 함수 내의 선언들을 모두 끌어올려서 해당 함수 유효 범위의 최상단에 선언하는 것

자바스크립트에서 변수의 선언은 끌어올려진다. 다음의 코드를 보자.

1
+2
+3
+4
+5
+6
+
function getX() {
+  console.log(x); // undefined;
+  var x = 100;
+  console.log(x); // 100
+}
+getX();
+

다른 언어의 경우 첫번째 console.log 에서 오류가 발생했을 것이다. 하지만 자바스크립트에서는 변수의 선언이 최상위로 끌어올려지기때문에, undefined가 출력된다. 즉 작동순서에 맞게 코드를 재구성하면 다음과 같다.

1
+2
+3
+4
+5
+6
+7
+
function getX() {
+  var x;
+  console.log(x);
+  x = 100;
+  console.log(x);
+}
+getX();
+

이때 할당 구문은 런타임 과정에서 이루어지기 때문에 hoist 되지 않는다.


함수 선언식과 표현식

호이스팅은 함수에서도 발생한다. 하지만 함수 선언식이냐 함수 표현식이냐에 따라 동작이 달라진다.

  • 선언식 : function 함수명() {} 와 같이 함수명을 포함한 함수 선언 형태

    • 호이스팅 되어 선언문 전에 사용 가능
    1
    +2
    +3
    +4
    +
    foo();
    +function foo() {
    +  console.log("hello");
    +}
    +
  • 표현식 : var 함수명 = function () {}와 같이 익명함수를 변수에 할당하는 방법

    • var 변수에는 접근이 되나, 함수 부여는 원래 위치에서 됨. 따라서 함수 부여 전에 사용 시 함수가 아니라는 오류가 남.
    1
    +2
    +3
    +4
    +
    foo(); // Uncaught TypeError: foo is not a function
    +var foo = function () {
    +  console.log("hello");
    +}
    +


let, const

let과 const는 hoist는 되지만 선언 전 사용은 불가능하다.

image-20220209020517822


출처

https://crushonit.tistory.com/95

https://github.com/JaeYeopHan/Interview_Question_for_Beginner/tree/master/JavaScript

This post is licensed under CC BY 4.0 by the author.

javascript 이벤트루프

closure

Comments powered by Disqus.

diff --git a/posts/index.html b/posts/index.html new file mode 100644 index 000000000..5038baf35 --- /dev/null +++ b/posts/index.html @@ -0,0 +1,11 @@ + + + + Redirecting… + + + + +

Redirecting…

+ Click here if you are not redirected. + diff --git a/posts/index/index.html b/posts/index/index.html new file mode 100644 index 000000000..7f85646fb --- /dev/null +++ b/posts/index/index.html @@ -0,0 +1 @@ + DB index | 디피의 개발일지
Posts DB index
Post
Cancel

DB index

##

인덱스란?

DBMS에서 데이터베이스 테이블의 모든 데이터를 검색하여 데이터를 찾기에는 시간이 오래 걸리기에, 데이터의 칼럼의 값과 그 데이터가 저장된 레코드의 주소를 키와 값의 쌍으로 만든 것.

DBMS의 인덱스는 항상 정렬된 상태를 유지하기 때문에, 원하는 값을 탐색하는데는 빠르지만, 추가/삭제/수정에는 쿼리문 실행 속도가 느려진다.(작업 후 다시 정렬해야하기에)

결과적으로 DBMS의 인덱스는 데이터 저장 성능을 희생하고, 읽기 성능을 높이는 기능이다. 따라서 모든 컬럼을 대상으로 인덱스를 생성하면 데이터 저장 성능이 떨어지고, 인덱스의 크기가 비대해지기에 역효과가 난다.


Index 의 성능과 고려해야할 사항

인덱스가 많아지면 SELECT는 빨라지지만, INSERT/DELETE/UPDATE는 느려진다. 이유는 인덱스에서 mutation 작업시 다음과 같은 일이 발생하기 떄문이다.

  • INSERT: 새로운 데이터에 대한 인덱스를 추가함. 이후 다시 데이터 정렬.
  • DELETE: 삭제하는 데이터의 인덱스를 사용하지 않는다는 표시를 함
  • UPDATE: 기존의 인덱스를 사용하지 않음 처리하고, 갱신된 데이터에 대해 인덱스를 추가함

UPDATE, DELETE 작업에서 기존의 인덱스를 그대로 두기에, mutation 작업이 빈번히 발생하면 테이블 로우는 10개인데 인덱스는 100개인 상황이 발생할 수 있다.

또한 데이터의 형식에 따라 인덱스를 만들면 효율적이고 만들면 비효율적인 데이터의 형식이 존재한다. 예를들어, 이름, 나이, 성별 세 가지의 필드가 있을때, 이름에 대해서는 인덱스가 효율적이고 나이,성별에 대해서는 그렇지 않다. 왜냐하면, 데이터의 범위가 작기때문에 겹치는 인덱스가 많아지고 되고, 따라서 추가적으로 원하는 데이터를 다시 검색해야하기 때문이다.


Index 자료구조

B +- Tree 인덱스 알고리즘

일반적으로 사용되는 알고리즘. 칼럼의 값을 변형하지 않고, 원래의 값을 이용해 인덱싱하는 알고리즘이다.

Hash 인덱스 알고리즘

컬럼의 값으로 해시 값을 계산하여 인덱싱하는 알고리즘으로 매우 빠른 검색을 지원함. 하지만 값을 변경하여 인덱싱하므로, 값의 일부만으로 검색하고자 할때는 해시 인덱스를 사용할 수 없음.

주로 메모리 기반의 데이터베이스에서 많이 사용함.

B 트리를 자주 사용하는 이유

SELECT 질의의 조건에는 부등호 (<>) 연산도 포함됨. 해시 인덱스를 사용하면 이러한 부등호 연산에 문제가 생긴다. 왜냐하면 해시는 동등(=) 연산에 특화되어있기 때문이다.


클러스터드 인덱스

물리적으로 인접한 장소에 있는 데이터들을 동시에 조회하는 경우가 많으므로(spatial locality), 인접한 것들을 묶어 저장하는 방식의 인덱스

이때 클러스터드 인덱스는 테이블의 primary key에 대해서만 적용된다. 즉 primary key가 비슷한 레코드끼리 묶어 저장한 것을 클러스터드 인덱스라고 표현함. 클러스터드 인덱스에서는 primary key에 의해 레코드의 저장 위치가 결정되며 primary key가 변경되면 레코드의 물리적인 저장위치 또한 변경되어야한다. 따라서 클러스터드 인덱스에서는 primary key를 신중하게 결정해야한다.

클러스터드 인덱스는 테이블 당 한개만 생성할 수 있음(primary key에 대해 적용되기에). non 클러스터드 인덱스는 테이블당 여러개의 인덱스를 만들 수 있음


Composite index

여러개의 필드로 구성하는 인덱스. 이때 인덱스로 설정하는 필드의 속성이 중요하다.

title, author 이 순서로 인덱스를 설정한다면 title 을 search 하는 경우, index 를 생성한 효과를 볼 수 있지만, author 만으로 search 하는 경우, index 를 생성한 것이 소용이 없어진다. 따라서 SELECT 질의를 어떻게 할 것인가가 인덱스를 어떻게 생성할 것인가에 대해 많은 영향을 끼치게 된다.


This post is licensed under CC BY 4.0 by the author.

딥링크

영상이 자동재생되지 않는 문제

Comments powered by Disqus.

diff --git a/posts/integration-test/index.html b/posts/integration-test/index.html new file mode 100644 index 000000000..c8d364ef2 --- /dev/null +++ b/posts/integration-test/index.html @@ -0,0 +1 @@ + 통합 테스트 | 디피의 개발일지
Posts 통합 테스트
Post
Cancel

통합 테스트

통합 테스트

상향식 통합 테스트

프로그램의 하위 모듈에서 상위모듈로 통합하면서 테스트하는 기법.

  1. 하위 모듈을 클러스터로 결합
  2. 더미 모듈인 드라이버 작성
  3. 통합된 클러스터 단위로 테스트
  4. 테스트 완료 후 클러스터는 프로그램 구조의 상위로 이동해 결합하고, 드라이버는 실제 모듈로 대체 됨.


하향식 통합 테스트

프로그램의 상위모듈에서 하위모듈로 통합하면서 테스트하는 기법

  1. 주요 제어 모듈은 작성된 프로그램을 사용. 주요 제어 모듈의 종속 모듈은 Stub으로 대체
  2. DFS, BFS로 하위 모듈인 Stub을 한번에 하나씩 실제 모듈로 교체함.
  3. 모듈이 통합 될때마다 테스트 실시
  4. 새로운 오류가 발생하지 않음을 보증하기 위해 회귀 테스트 실시


혼합식 통합 테스트

하위 수준에서는 상향식 통합, 상위 수준에서는 하향식 통합을 사용해 최적의 테스트를 지원함.

샌드위치 식 통합 테스트 방법



출처

https://m.blog.naver.com/wook2124/222108726160

This post is licensed under CC BY 4.0 by the author.

럼바우 분석기법

전위식/후위식

Comments powered by Disqus.

diff --git a/posts/ios-16-5-video/index.html b/posts/ios-16-5-video/index.html new file mode 100644 index 000000000..f83d506ce --- /dev/null +++ b/posts/ios-16-5-video/index.html @@ -0,0 +1,109 @@ + IOS 16.5에서 video가 크기 변경이 적용되지 않는 이슈 | 디피의 개발일지
Posts IOS 16.5에서 video가 크기 변경이 적용되지 않는 이슈
Post
Cancel

IOS 16.5에서 video가 크기 변경이 적용되지 않는 이슈

Spec

  • Swiper (ver 4.5.1) 안에 <video> 태그가 slide로 포함되어있음.

  • 현재 가장 가운데 있는 slide 안의 video는 크기를 키우고, slide가 옆으로 이동하면 다시 원래 사이즈로 돌아가도록 함

Issue

  • IOS 16.5 safari에서는 다음과 같이 가운데 slide 안의 video 크기가 정상적으로 확대되지 않음

원인

  • 가운데 video 확대를 위한 코드는 다음과 같이 slide-active 상태일때 스타일을 적용하여 크기를 변경해주는 로직이다.

    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +
    .swiper .swiper-slide video {
    +    width: 173px;
    +    height: 375px;
    +}
    +  
    +.swiper .swiper-slide-active video {
    +    width: 238px !important;
    +    height: 418px !important;
    +}
    +
    • 가운데 slide에 정상적으로 swiper-slide-active 클래스가 적용되면 video 태그의 크기가 정상적으로 커져야했고, 개발자도구로 확인했을떄 문제가 발생했던 기기에서도 제대로 클래스가 적용된 것을 확인할 수 있었다. 하지만 이유는 알수없지만 ios 16.5 safari에서는 video 태그의 크기변화를 잡아내지 못하여 화면에 적용되지 않은 것이다.

해결

다음과 같이 swiper 이벤트 중 slideChangeTransitionStart가 발동될 떄 명시적으로 style을 변경하고, 약간의 타이머를 주어 1px 크게 변경하여 브라우저가 크기 변화를 인식할 수 있도록 하였다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+
slideChangeTransitionStart : function() {
+	slides?.forEach(function(item) { 
+		if(item.slide.classList.contains("swiper-slide-active")) {
+			if(window.isIOS) {
+        if(window.innerWidth >= 1024) {
+       	 activeVideo.style.setProperty("width", "236px", "important");
+     	   activeVideo.style.setProperty("height", "516px", "important");
+        } else {
+     	   activeVideo.style.setProperty("width", "238px", "important");
+     	   activeVideo.style.setProperty("height", "418px", "important");
+        }	
+        
+				setTimeout(() => {
+					if(window.innerWidth >= 1024) {
+						activeVideo.style.setProperty("width", "237px", "important");
+						activeVideo.style.setProperty("height", "517px", "important");
+					} else {
+						activeVideo.style.setProperty("width", "239px", "important");
+						activeVideo.style.setProperty("height", "419px", "important");
+					}
+				}, 30)
+			}
+		} else {
+				if(window.isIOS) {
+					if(window.innerWidth >= 1024) {
+						item.video.style.setProperty("width", "187px", "important");
+						item.video.style.setProperty("height", "405px", "important");
+					} else {
+						item.video.style.setProperty("width", "187px", "important");
+						item.video.style.setProperty("height", "328px", "important");
+					}
+          
+          setTimeout(() => {
+            if(window.innerWidth >= 1024) {
+              item.video.style.setProperty("width", "188px", "important");
+              item.video.style.setProperty("height", "406px", "important");
+            } else {
+              item.video.style.setProperty("width", "188px", "important");
+              item.video.style.setProperty("height", "329px", "important");
+            }
+          }, 30)
+				}
+			}
+		})
+},
+

이후 문제없이 동작함을 확인하였다.

This post is licensed under CC BY 4.0 by the author.

es6에서 도입된 문법

React 새로고침/뒤로가기 막기

Comments powered by Disqus.

diff --git a/posts/ipv6/index.html b/posts/ipv6/index.html new file mode 100644 index 000000000..6ba7f5833 --- /dev/null +++ b/posts/ipv6/index.html @@ -0,0 +1 @@ + IPv6 | 디피의 개발일지
Posts IPv6
Post
Cancel

IPv6

IPv6

  • 128비트 사용.

  • Multicast 대신에 Broadcast를 함.

  • ICMPv6

    • ARP, IGMP 기능흡수
      • ARP : IP주소를 MAC 주소로 변환하는 프로토콜
  • IPv4 에서 IPv6로의 전환 정책

    • Dual stack : 두가지 다 지원하도록 하여, 두가지 주소를 모두 사용할 수 있도록 하는 것
    • tunneling : 종단 사이에 IPv4를 쓰는 홉이 있을때, 그 중간 단계에서만 IPv6를 터널링으로 IPv4로 감싸 통신
    • header translation : 종단에서만 IPv4를 사용하면, IPv6를 해석하여 IPv4로 변경한 다음에 전달
This post is licensed under CC BY 4.0 by the author.

deadlock 발생 조건

view

Comments powered by Disqus.

diff --git a/posts/java-checked-vs-unchecked-exception/index.html b/posts/java-checked-vs-unchecked-exception/index.html new file mode 100644 index 000000000..d26347aaa --- /dev/null +++ b/posts/java-checked-vs-unchecked-exception/index.html @@ -0,0 +1,109 @@ + checked vs unchecked exception | 디피의 개발일지
Posts checked vs unchecked exception
Post
Cancel

checked vs unchecked exception

[java] checked vs unchecked exception

자바에서 프로그램에 이상이 있을 때 던져지는 Throwable은 Error와 Exception이 있다.

throwable

  • Error : 시스템이 비정상적인 상황에 있다는 것을 의미. 시스템 레벨에서 발생하는 심각한 오류이기 때문에 개발자가 미리 예측하거나 처리할 수 없다.
    • ex) OutOfMemoryError, ThreadDeath
  • Exception : 개발자들이 만든 애플리케이션 코드에서 예외가 발생했다는 것을 의미한다.
    • Checked Exception : 반드시 예외처리해야하는 예외. 스프링 트랜잭션 Rollback이 진행되지 않음
      • Ex) IOException, SQLException
    • Unchecked Exception : 예외 처리해주지 않아도 되는 예외. 스프링 트랜잭션 Rollback 진행
      • ex) NullPointerException, IllegalArgumentException

그림에서 보이듯이 Unchecked Exception은 RuntimeException을 상속받는다. 하지만 Checked Exception은 상속받지 않는다. 이것이 두 Exception을 구분하는 중요한 포인트이다.


checked vs unchecked Exception

반드시 처리해줘야하는가

  • Checked exception : 반드시 명시적으로 처리해야하는 예외이다. 따라서 try-catch로 처리하든, throws로 호출한 메서드로 예외를 날리지 않으면 컴파일 오류가 발생한다.
  • unchecked exception : 명시적인 예외처리가 필요없다. 따라서 예외처리를 해주지 않아도 상관없다.

스프링 트랜잭션 Rollback 여부

  • Checked exception : 예외가 날아가도 rollback이 진행되지 않는다. 예외를 감지할 수 있으니 개발자에게 복구를 맡기기 때문이다.(이는 스프링이 EJB 관습을 따르기 때문이라고 한다.)
  • unchecked exception : 예외 발생 시 rollback이 진행된다.
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+
@Service
+@RequiredArgsConstructor
+@Transactional
+public class MemberService {
+  private final MemberRepository memberRepository;
+
+  // (1) RuntimeException 예외 발생
+  public Member createUncheckedException() {
+    final Member member = memberRepository.save(new Member("yun"));
+    if (true) {
+      throw new RuntimeException();
+    }
+    return member;
+  }
+
+  // (2) IOException 예외 발생
+  public Member createCheckedException() throws IOException {
+    final Member member = memberRepository.save(new Member("wan"));
+    if (true) {
+      throw new IOException();
+    }
+    return member;
+  }
+}
+


checked exception 처리 전략

스프링 트랜잭션 Rollback 관련

checked exception이 날라가도 스프링은 트랜잭션 Rollback을 하지 않는다. 개발자에게 맡기기 때문이다. 실제로 Transaction rollback을 적용할 때 쓰이는 @Transactional 애노테이션의 rollbackFor 옵션의 기본값은 다음과 같다.

1
+
@Transactional(rollbackFor = {RuntimeException.class, Error.class})
+

하지만 현실적으로 개발자가 처리해줄 수 없는 경우도 있다. 이럴 때는 다음과 같은 방법이 있다.

  • try-catch로 잡은 후, RuntimeException으로 날리기
    • 이때는 왜 exception이 발생하였는지 명확하게 보내는 것이 좋다.
  • @Transactional(rollbackFor={Exception.class, Error.class})로 변경하여 모든 예외를 대산으로 롤백하기.
  • RollbackRuleAttribute 를 이용해 롤백 규칙 추가하기

2,3번째 방법을 사용하면 checked exception에서도 롤백을 진핼할 수 있으나, 스프링 기본 설정을 건드려야한다. 이렇게 기본 설정을 건드는 것은 향후 문제가 될 수 있다. 따라서 웬만하면 unchecked exception으로 바꿔 날리는 것이 좀 더 안전하며, 명확한 exception으로 바꿔 보낸다면 더 좋은 설계가 된다고 생각한다

unchecked exception으로 바꿔 날리기

Checked exception을 throws로 상위 메서드도 throws로 날려야하고, 그 상위 메서드도 throws로 날려야하는 문제가 발생한다. 이는 좋지 않은 패턴이다. 따라서 반드시 예외는 처리 후 더욱 구체적인 unchecked exception으로 날려야한다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+
// throws 지옥
+public void root() throws IOException {
+  aaa();
+}
+
+public void aaa() throws IOException {
+  bbb();
+}
+
+public void bbb() throws IOException {
+ 	throw new IOException();
+}
+
+// 처리 후 
+public void root() {
+  aaa();
+}
+
+public void aaa() {
+  bbb();
+}
+
+public void bbb() {
+  try {
+ 		throw new IOException();
+  } catch(IOException e) {
+    throw new JsonDeserializeFailed(e.getMessage());
+  }
+}
+


출처

  • https://cheese10yun.github.io/checked-exception/
  • https://jangjjolkit.tistory.com/m/3
This post is licensed under CC BY 4.0 by the author.

HPA

PathPattern과 servletPath

Comments powered by Disqus.

diff --git "a/posts/java-equals-hashcode-\353\214\200\354\262\264/index.html" "b/posts/java-equals-hashcode-\353\214\200\354\262\264/index.html" new file mode 100644 index 000000000..1ceb269e7 --- /dev/null +++ "b/posts/java-equals-hashcode-\353\214\200\354\262\264/index.html" @@ -0,0 +1,37 @@ + equals(), hashCode() | 디피의 개발일지
Posts equals(), hashCode()
Post
Cancel

equals(), hashCode()

equals()와 hashCode() 메서드는 모든 자바 객체의 부모인 Object 클래스에 정의되어있다. 따라서 모든 자바 객체는 equals()와 hashCode() 메서드를 가지고 있다.

equals()

현재 객체와 파라미터로 들어온 객체가 같은 지 검사하기 위해 사용한다. 기본적으로는 두 객체의 메모리 주소가 같아야 동일한 객체가 된다.

1
+2
+3
+
public boolean equals(Objecet obj) {
+  return (this == obj);
+}
+

이러한 equals 객체를 오버라이드하여 필드값이 같은 객체를 같은 객체라고 판단하게 할 수 있다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+
@AllArgsConstructor
+@Getter
+public class User {
+  private String name;
+  
+  @Override
+  public boolean equals(Object obj) {
+    if(obj instanceof User) {
+      return this.getName().equals(obj.getName()); 
+    } else {
+      return false;
+    }
+  }
+}
+

hashCode()

런타임 중에 객체의 유일한 integer 값을 반환한다. Object 클래스에서는 heap에 저장된 객체의 메모리 주소를 반환하도록 되어있다.

1
+
public native int hashCode();
+

native 키워드

  • 메소드가 JNI(Java Native Interface)라는 native code를 이용해 구현되었음을 의미
  • native는 메소드에만 적용가능한 제어자로, C or C++ 등 Java가 아닌 언어로 구현된 부분을 JNI를 통해 Java에서 이용하고자 할 때 사용
  • 일반 개발자는 사용할 수 없다.
  • hashCode에서는 HashTable과 같은 자료구조를 사용할 때 데이터가 저장되는 위치를 결정하기 위해 사용한다.


equals() 와 hashCode()의 관계

동일한 객체는 동일한 메모리 주소를 갖는다는 것을 의미하므로, 동일한 객체는 동일한 해시태그를 가져야한다. 그렇기 때문에 만약 우리가 equals() 메서드를 오버라이드한다면, hashCode() 메서드도 오버라이드 되어야한다. equals()와 hashCode()는 아래와 같은 관계를 만족해야한다.

  • Java 프로그램을 실행하는 동안 equals()에 사용된 정보가 수정되지 않았다면, hashCode()는 항상 동일한 정수값을 반환해야한다.
  • 두 객체가 equals()에 의해 동일하다면, 두 객체의 hashCode() 값도 일치해야한다.
  • 두 객체가 equals()에 의해 동일하지 않다면, 두 객체의 hashCode() 값도 일치하지 않아도 된다.


출처

https://mangkyu.tistory.com/101

https://kwonnam.pe.kr/wiki/java/equals_hashcode

This post is licensed under CC BY 4.0 by the author.

Content-Disposition

junit - spy

Comments powered by Disqus.

diff --git a/posts/java-junit-Spy/index.html b/posts/java-junit-Spy/index.html new file mode 100644 index 000000000..32b30b4d9 --- /dev/null +++ b/posts/java-junit-Spy/index.html @@ -0,0 +1,29 @@ + junit - spy | 디피의 개발일지
Posts junit - spy
Post
Cancel

junit - spy

Mock 객체와는 달리 객체의 특정 메서드만 stub으로 대체할 수 있는 방법을 제공한다.

다음과 같이 만약 테스트 대상 객체의 특정한 메서드를 stub 처리하고 싶을 때 사용한다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+
@ExtendWith(MockitoExtension.class)
+public class BoardServiceTest{  
+  @Spy
+  private BoardService boardService;
+  
+  @Test
+  public void 인증_실패시_예외발생() {
+    // boardService의 인증을 담당하는 메서드를 stub 처리함
+    doReturn(false).when(boardService).checkAuthorization(anyLong(), anyString());
+
+    // boardService의 update 메서드는 정상적으로 호출한 후, 예외를 뱉는지 확인
+    assertThrows(ApiException.class, () -> boardService.updateArticle(any()));
+  }
+}
+
This post is licensed under CC BY 4.0 by the author.

equals(), hashCode()

json 응답 시 특정 필드 빼고 보내기

Comments powered by Disqus.

diff --git "a/posts/java-lombok-\354\202\254\354\232\251\354\213\234-\354\243\274\354\235\230\354\240\220/index.html" "b/posts/java-lombok-\354\202\254\354\232\251\354\213\234-\354\243\274\354\235\230\354\240\220/index.html" new file mode 100644 index 000000000..d2e20241b --- /dev/null +++ "b/posts/java-lombok-\354\202\254\354\232\251\354\213\234-\354\243\274\354\235\230\354\240\220/index.html" @@ -0,0 +1,81 @@ + lombok 사용시 주의점 | 디피의 개발일지
Posts lombok 사용시 주의점
Post
Cancel

lombok 사용시 주의점

lombok은 @Getter, @Setter 같은 애노테이션 기반으로 Getter/Setter 메서드를 자동으로 생성해주는 편리한 라이브러리이다.

하지만 편리함에 남용하는 애노테이션들이 있다.

lombok 사용 시 주의해야할 애노테이션

@AllArgsConstructor

클래스에 존재하는 모든 필드에 대한 생성자를 자동으로 생성한다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
@AllArgsConstructor
+public class User {
+  private String name;
+  private String userId;
+  
+  // 자동 생성
+  public User(String name, String userId) {
+    this.name = name;
+    this.userId = userId;
+  }
+}
+

이때 생성자의 파라미터 순서는 필드가 선언된 순서와 같다. 여기서 문제가 발생할 수 있다. 만약 위 예시에서 개발자가 name과 userId의 순서를 변경한다면, lombok은 생성자의 파라미터 순서도 바꾼다. 이때 만약 다음과 같은 메서드가 애플리케이션 어딘가에서 사용중이었다면 어떠한 오류도 발견되지 않고 넘어갈 것이다.

1
+
User tempUser = new User("name", "userId");
+


@RequiredArgsConstructor

클래스의 final 필드에 대한 생성자를 생성한다. @AllArgsConstructor와 같은 문제를 가지고 있다.

하지만 빈 주입으로 사용할 경우엔 스프링에서 객체를 생성하므로 크게 신경 안써도 된다.


@EqualsAndHashCode

equals 메서드와 hashcode 메서드를 생성한다. equals 메서드로 두 객체의 필드들이 서로 같은 값을 가지는지 검사하고, hashcode 메서드로 실제로 같은 객체인지 검사한다.

편리하지만, Mutable 객체에 아무런 파라미터 없이 그냥 사용하면 문제가 된다. 객체를 Set에 저장한 뒤 필드 값을 변경하면 hashCode가 변경되면서 이전에 저장한 객체를 찾을 수 없는 문제가 발생하기 때문이다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+
@EqualsAndHashCode
+public class User {
+  private String name;
+  private String userId;
+  
+  public void setName(String name) {
+    this.name = name;
+  }
+}
+  
+User userInfo = new User("Park", "qwer1234");
+  
+Set<User> userSet = new HashSet<>();
+userSet.add(userInfo); // Set에 객체 추가
+  
+System.out.println("변경전 : " + userSet.contains(userInfo)); // true
+  
+userInfo.setName("Shin"); // price 값 변경
+System.out.println("변경후 : " + userSet.contains(userInfo)); // false    
+

Set에서는 동일한 객체임을 검사할 때 hashCode() 메서드를 사용하는데, equals()로 같은 객체는 hashCode()로도 같아야하므로 필드 값이 변경될 경우 hachCode도 달라지기 때문이다.

따라서 mutable 객체에서는 다음과 같이 동등성 비교에 필요한 필드를 명시하는 형태로 사용한다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+
@EqualsAndHashCode(of={"userId"})
+public class User {
+  private String name;
+  private String userId;
+  
+  public void setName(String name) {
+    this.name = name;
+  }
+}
+

하지만 막상 개발을 하다보면 immutable 필드를 대상으로만 equals, hashCode를 만들기는 어렵다. 최소한 꼭 필요하고 일반적으로 변하지 않는 필드에 대해서만 만들도록 노력해야한다.


@Value

immutable 클래스를 만들어주는 조합 애노테이션으로, @EqualsAndHashCode, @AllArgsConstructor가 포함된다.

@EqualsAndHashCode는 immutable 클래스이므로 문제가 되지 않지만, @AllArgsConstructor는 위에서 언급한 문제가 있다.


@Data

@Getter, @Setter, @RequiredArgsConstructor, @EqualsAndHashCode, @ToString를 포함하는 조합 애노테이션이다.

위에서 언급한 문제들을 가지고 있다.


출처

https://velog.io/@rosa/Lombok-%EC%A7%80%EC%96%91%ED%95%B4%EC%95%BC-%ED%95%A0-annotation

https://kwonnam.pe.kr/wiki/java/lombok/pitfallhttps://bkjeon1614.tistory.com/657

This post is licensed under CC BY 4.0 by the author.

json 응답 시 특정 필드 빼고 보내기

RestTemplate

Comments powered by Disqus.

diff --git "a/posts/java-\352\270\260\353\263\270/index.html" "b/posts/java-\352\270\260\353\263\270/index.html" new file mode 100644 index 000000000..d4f771dff --- /dev/null +++ "b/posts/java-\352\270\260\353\263\270/index.html" @@ -0,0 +1,953 @@ + 자바 기본 | 디피의 개발일지
Posts 자바 기본
Post
Cancel

자바 기본

Eclipse 프로젝트 생성

new Java project를 하면 여러 선택지가 나옴.

  • Project layout

    • use project folder as root for sources and class files
      • 빌드 전 소스코드와 빌드 후 생성된 결과물이 root에 한번에 생김
    • Create separate folders for sources and class files
      • 빌드 전 소스코드와 빌드 후 생성된 결과물을 다른 폴더에 저장
  • Module

    • Create module-info.java file

      • 서로 다른 프로젝트 a,b에 대해 a에서 선언한 클래스를 b에서 불러와 사용할 수 있도록 하는 파일

        1
        +2
        +3
        +4
        +
        // export
        +module common.widget {
        +    exports com.logicbig;    // 외부에 노출하고싶은 패키지
        +}
        +
        1
        +2
        +3
        +4
        +5
        +
        // import
        +module data.widget {
        +    requires common.widget;    // 사용할 패키지
        +    requires java.sql;
        +}
        +


Hello world

  1. src 아래 HelloWorldApp.java 파일 생성

  2. 아래와 같이 작성

    1
    +2
    +3
    +4
    +5
    +
    public class HellowWorldApp {
    +	public static void main(String[] args) {
    +		System.out.println("Hellow World!!");
    +	}
    +}
    +
    • 클래스명은 파일명과 같아야함.
    • 실행하는 파일에서, 파일명과 같은 클래스명안에, 항상 main 함수는 있어야함.
  3. Run 을 눌러 실행


프로그래밍 구성

자료형

  • 원시타입
    • 정수형 : int, long
    • 소수형 : float, double
    • 문자형 : char (‘’ 로 묶음)
    • boolean
  • 클래스타입
    • String : “” 로만 묶어야함. 안에 ““를 입력하고 싶으면 \ 사용
      • .length()
      • .replace(a, b) : a문자열을 찾아 b로 변경
      • .equals() : 같은지 확인

숫자와 연산

  • Math 클래스
    • 다양한 연산 메소드, 상수 제공

캐스팅

  • 앞에 (자료형) 을 붙여서 함

    1
    +2
    +
    int e = (int) 1.1;
    +double b = (double) e;
    +
  • 정수 -> String

    1
    +
    String f = Integer.toString(1);
    +

배열

  • 선언
    1. 리터럴 : String[] bear = {"kloud", "Cass", "Guiness"};
    2. String[] bear = new String[3];
  • 이차원배열

    1. 리터럴

      1
      +2
      +3
      +4
      +5
      +
      String[][] users = {
      +    {"egoing", "1111"},
      +    {"jinhuck", "2222"},
      +    {"youbin", "3333"}
      +}
      +
    2. new

      1
      +
      String[][] users = new String[3][2];
      +
  • 동적배열 : List 사용

반복문

  • for

    • 일반적 사용
    1
    +2
    +3
    +4
    +5
    +
    double[] dividendRates = { 0.5, 0.3, 0.2 };
    +double[] dividend = new double[3];
    +for (int i = 0; i < dividendRates.length; i++) {
    +	dividend[i] = income * dividendRates[i];
    +}
    +
    • 배열에서 가져오기
    1
    +2
    +3
    +4
    +5
    +
    int[] score = { 78, 70, 65, 98, 58 };
    +int sum = 0;
    +for (int i : score) {
    +	sum += i;
    +}
    +
  • while : 다른 언어와 같음.


프로그래밍 시작하기

입력과 출력

  • 입력 다이얼로그

    1
    +2
    +3
    +
    import javax.swing.JOptionPane;
    +...
    +String id = JOptionPane.showInputDialog("Enter a Id");
    +
  • Scanner

    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +
    import java.util.Scanner;
    +...
    +Scanner in = new Scanner(System.in);
    +int a = in.nextInt(); // int형 입력 및 리턴
    +float b = in.nextFloat();
    +boolean c = in.nextBoolean();
    +String d = in.next();		// String 형 입력 및 리턴 (공백을 기준)
    +String e = in.nextLine();	// String 형 입력 및 리턴 (개행을 기준)
    +

arguments & parameter

  • 가변인자
    • void sum(String a, String...str) 와 같이 작성하여, 여러개 받을 수 있도록 하는거.
    • 내부적으로는 배열로 처리한다.
  • main 함수의 args 에 값 넣고 실행하는 방법
    1. Run 옆에 화살표 클릭
    2. Run configuration 클릭
    3. ` Arguments` 클릭
    4. “JAVA APT 508” “123.0” 과 같이 넣기. 공백으로 구분.

직접 컴파일

  1. 컴파일 하고자하는 파일이 있는 곳으로 이동.

  2. javac [파일이름].java 하면 .class 파일 생성

  3. 클래스 파일 있는 곳으로 이동

  4. java [파일이름] 하면 실행됨

    • .class 확장자를 붙이지 않음

    • 또는 .java 파일을 직접 실행시켜도 된다. 이때는 확장자를 붙임

직접 컴파일 : 라이브러리 사용

  1. javac -cp ".;lib" OkJavaGoInHome.java

    • -cp : --class-path 라는 의미. 구분자를 통해 컴파일할 폴더를 연결 가능
    • .;lib : ; 는 구분자. . (현재폴더)와 lib 를 컴파일 하라는 뜻.
  2. 실행도 비슷하게 java -cp ".;lib" OkJavaGoInHome 으로 실행.

직접 컴파일 : arguments

  1. java OkJavaGoInHomeInput "JAVA APT 507" 15.0

자바용어

  • 패키지, 클래스, 메소드, 변수

    • 패키지 : 비슷한 성격의 클래스를 모은것
      • java.lang : import 없이 사용가능
    • 클래스 : 서로 연관된 변수와 메소드를 모아 이름을 붙인 것
  • 인스턴스 : 클래스를 컴퓨터 상에서 실체화 한 것

    • PrintWriter p1 = new PrintWriter("result1.txt")
  • 상속

  • throws : 메소드 옆에 붙여 어떤 에러를 던질 수 있는지를 표시

    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +14
    +15
    +16
    +17
    +18
    +19
    +20
    +
    public static void main(String[] args) {
    +    Test test = new Test();
    +    try {
    +        test.test("1", "r");
    +    } catch(NumberFormatException e) {
    +        System.out.println("입력하신 값은 숫자가 아닙니다.");
    +    }
    +}
    +
    +
    +class Text {
    +    public void test(String a, String b) throws NumberFormatException {
    +        try {
    +            int sum = Integer.parseInt(a) + Integer.parseInt(b); // 숫자가 아닐시 NumberFormatException 발생
    +            System.out.pringln("합은 " + sum);
    +        } catch(NumberFormatException e) {
    +            throw e;
    +        }
    +    }
    +}
    +

클래스

  • 생성자 : public + 클래스이름으로 선언

    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +
    public class AccountingMethodApp {
    +	public double valueOfSupply;
    +	public double vatRate;
    +	public double expenseRate;
    +
    +	public AccountingMethodApp(double valueOfSupply, double VatRate, double expenseRate) {
    +		this.valueOfSupply = valueOfSupply;
    +		this.vatRate = VatRate;
    +		this.expenseRate = expenseRate;
    +	}
    +    ...
    +
  • static : 정적 멤버 선언.(static 변수 : 정적필드, static 메소드 : 정적 메소드)

    • 객체(인스턴스)에 소속된 멤버가 아니라 클래스에 고정된 멤버.

    • 클래스로더가 클래스를 로딩해서 메소드 메모리 영역에 적재할 떄, 클래스별로 관리한다.

    • 정적멤버는 heap이 아닌 static 영역에 할당됨. 프로그램이 종료될 때까지 할당된 상태이므로, static을 남발할 시 프로그램 성능에 악영향을 끼침

    • 인스턴스를 생성하지 않아도 호출 가능하다.

      1
      +2
      +3
      +4
      +5
      +6
      +7
      +8
      +9
      +10
      +11
      +12
      +13
      +14
      +15
      +16
      +17
      +18
      +19
      +20
      +21
      +22
      +23
      +24
      +25
      +26
      +27
      +28
      +29
      +30
      +31
      +32
      +33
      +34
      +35
      +36
      +
      public class AccountingMethodApp {
      +	public static double valueOfSupply;
      +	public static double vatRate;
      +	public static double expenseRate;
      +
      +	public static void print() {
      +		System.out.println("Value of supply : " + valueOfSupply);
      +		System.out.println("VAT : " + getVAT());
      +		System.out.println("Total : " + getTotal());
      +		System.out.println("Expense : " + getExpense());
      +		System.out.println("Income : " + getIncome());
      +	}
      +	public static double getIncome() {
      +		return valueOfSupply - getExpense();
      +	}
      +	public static double getExpense() {
      +		return valueOfSupply * expenseRate;
      +	}
      +	public static double getTotal() {
      +		return valueOfSupply + getVAT();
      +	}
      +	public static double getVAT() {
      +		return valueOfSupply * vatRate;
      +	}
      +}
      +
      +
      +public class Test {
      +    public static void main(String[] args) {
      +        AccountingMethodApp.valueOfSupply = 10000.0;
      +        AccountingMethodApp.vatRate = 0.5;
      +        AccountingMethodApp.expenseRate =0.1;
      +
      +        AccountingMethodApp.print();
      +    }
      +}
      +
    • static 메소드에서는 static 메소드와 변수만 참조가능하다.


제어문

비교

  • 일반적인 상황에서는 == 등의 비교연산자 사용

  • String은 equals() 메소드 사용.

    • == 로 비교시 메모리 주소를 비교. .equals()로 비교해야 내용을 비교한다.

    • 이유

      • 원시데이터 타입일 경우, 변수가 선언되면 stack에 공간이 할당되고, 그 곳에 실제 값이 들어가게 된다. 그래서 == 연산자는 변수가 가리키는 값을 비교한다.

      • 하지만 String과 같이 java.lang.Object 에서 파생된 클래스들은 원시 데이터 타입이 아니다. new로 생성되면 heap에 새로운 공간이 할당되어 값을 저장하고, 변수는 그 값이 저장된 메모리 주소를 가리키게 된다. 따라서 클래스를 ==로 비교하면 메모리 주소를 비교하게 되는 것이다.

      • 하지만 문자열 리터럴로 String을 생성한 경우, 이미 같은 문자열을 생성한 적이 있다면 새로 메모리 공간을 할당하지 않고 새로운 변수는 기존 문자열이 저장된 메모리의 주소를 가리키게 된다. 따라서 == 연산시 true가 나온다. 그래도 내용을 비교하려면 일단 .equals()로 비교하자.

      1
      +2
      +3
      +4
      +5
      +6
      +7
      +8
      +9
      +10
      +11
      +12
      +13
      +14
      +15
      +16
      +17
      +18
      +19
      +20
      +21
      +22
      +23
      +24
      +25
      +
      // 결과 : False
      +String a = new String("1234");
      +String b = new String("1234");
      +if (a == b) {
      +	System.out.println("TRUE");
      +} else {
      +	System.out.println("False");
      +}
      +
      +
      +// 결과 : TRUE (같은 메모리 주소이기에)
      +String c = "1234";
      +String d = "1234";
      +if (c == d) {
      +	System.out.println("TRUE");
      +} else {
      +	System.out.println("False");
      +}
      +
      +// 결과 : False (다른 메모리 주소이기때문)
      +if (a == c) {
      +	System.out.println("TRUE");
      +} else {
      +	System.out.println("False");
      +}
      +

instanceof

  • 객체가 클래스의 인스턴스냐.

    a instanceof A

  • 다형성에 따라, 자식 클래스의 객체 instanceof 부모 클래스 하면 true가 나온다. 반대는 false


메소드

접근제어자

  • public : 클래스 외부에서 접근 가능
  • private : 클래스 외부에서 접근 불가능
  • protected : 같은 패키지 내에 있거나, 상속받은 경우에만 접근 가능함.
  • package-private : 패키지 내에서만 접근 가능


객체지향프로그래밍

클래스(형식)

  • 한 파일에 여러개를 넣을 수 있지만, public 클래스는 파일이름과 같은 클래스에 하나만 생성가능.
  • 자바의 소스코드를 실행할 때, 소스코드 파일명과 동일한 public 클래스를 컴파일해서 그 클래스의 main 메소드를 실행하는 걸로 약속 되어있다.
  • 소스코드를 컴파일할때 그 안에 들어있는 클래스는 따로따로 .class 파일로 컴파일된다.
    • 한 파일 안에 여러 개의 클래스가 있으면, 각각 따로 .class 파일이 나오도록 컴파일 된다는 것

생성자와 this

  • 생성자 : 리턴형을 없앤 메소드 형식. 접근제어자는 public이어야함.

    1
    +2
    +3
    +4
    +5
    +6
    +
    class Print {
    +	public String delimiter = "";
    +	public Print(String delimiter) {
    +		this.delimiter = delimiter;
    +	}
    +    ...
    +
  • this

    • 인스턴스를 가리키는 예약어.

OOP

  • https://seongil-shin.github.io/posts/oop/

상속

extends

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+
class Cal {
+	public int sum(int v1, int v2) {
+		return v1 + v2;
+	}
+}
+class Cal3 extends Cal{
+	public int minus(int v1, int v2) {
+		return v1 - v2;
+	}
+}
+

override

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+
class Cal3 extends Cal{
+	@Override
+    public int sum(int v1, int v2) {
+		System.out.println("Cal3!!!");
+		return v1 + v2;
+	}
+
+    // overloading
+    public int sum(int v1, int v2, int v3) {
+        return v1 + v2 + v3;
+    }
+}
+
  • 이름, 매개변수가 같은 메소드의 구현만 다르게 하는 것.
  • @Override 어노테이션을 붙이는 것이 좋다.
  • vs overload
    • 이름은 같으나 매개변수가 다른 메소드.
    • 같은 클래스 내에서도 선언가능
    • 생성자도 이와 같이 오버로딩 가능

this super

  • super : 부모 클래스를 가리키는 키워드.
    • 자식클래스에서 super를 이용하여 접근 권한이 부여된 부모클래스의 변수와 메소드에 접근할 수 있다.
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+
class Cal {
+	public int sum(int v1, int v2) {
+		return v1 + v2;
+	}
+}
+class Cal3 extends Cal{
+	@Override
+	public int sum(int v1, int v2) {
+		System.out.println("Cal3!!!");
+		return super.sum(v1, v2);
+	}
+}
+
  • super() 를 통해 부모 클래스의 생성자 호출가능

    • 자식 클래스에서 생성자가 없으면 암묵적으로 부모 클래스의 생성자가 호출됨

    • 하지만 아래와 같이 인자를 받는 생성자만 부모클래스에 있다면, 자식 클래스에서도 생성자를 명시적으로 호출하여 부모 클래스의 생성자를 호출해야한다.

      • super() 는 인자가 없는 생성자를 호출하는 것이므로.
      • 마찬가지로 어떤 클래스에 인자를 받는 생성자만 있는데, 인자를 넣지않고 인스턴스를 생성하면 컴파일 오류가 난다.
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+
class Cal {
+	int v1, v2;
+	public Cal(int v1, int v2) {
+		this.v1 = v1;
+		this.v2 = v2;
+    }
+	public int sum() {
+		return this.v1 + this.v2;
+	}
+}
+
+class Cal2 extends Cal {
+	int v3;
+	public Cal2(int v1, int v2, int v3) {
+		super(v1, v2);
+		this.v3 = v3;
+    }
+	@Override
+	public int sum() {
+		return super.sum() + this.v3;
+	}
+}
+


Java 인터페이스

인터페이스

  • 앞으로 만들 클래스의 메소드 규격을 선언할 수 있음

    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +
    interface Calculable {
    +	int sum(int v1, int v2);
    +}
    +class RealCal implements Calculable {
    +    @Override
    +	public int sum(int v1, int v2) {
    +		return v1 + v2;
    +	}
    +}
    +
    • 이떄도 @Override 어노테이션은 들어간다.
  • 여러개의 인터페이스를 동시에 적용할 수 있음

    1
    +2
    +3
    +4
    +5
    +6
    +7
    +
    interface Calculable {
    +	int sum(int v1, int v2);
    +}
    +interface Printable {
    +	void print();
    +}
    +class RealCal implements Calculable, Printable {
    +
  • interface 내부에서 변수를 정의할 수 있음. 다만 이때는 반드시 초기화 되어야함. 인터페이스를 적용한 클래스는 변수를 다시 대입할 수 없음

    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +14
    +15
    +16
    +17
    +
    interface Calculable {
    +	double PI = 3.14;
    +	int sum(int v1, int v2);
    +}
    +class RealCal implements Calculable {
    +	public int sum(int v1, int v2) {
    +		return v1 + v2;
    +	}
    +}
    +
    +public class InterfaceApp {
    +	public static void main(String[] args) {
    +		RealCal c = new RealCal();
    +		System.out.println(c.sum(2, 1));
    +		System.out.println(c.PI);
    +	}
    +}
    +
  • interface도 상속이 가능

    1
    +2
    +3
    +4
    +5
    +6
    +7
    +
    interface Calculable {
    +	double PI = 3.14;
    +	int sum(int v1, int v2);
    +}
    +interface Printable extends Calculable {
    +    void print();
    +}
    +

다형성

  • 클래스의 인스턴스를 변수로 선언할 떄, 해당 클래스의 데이터타입으로 선언하지 않고 부모 클래스나 인터페이스를 데이터 타입으로 선언할 수 있다. 이러한 특성을 다형성이라고 부른다.

    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +14
    +15
    +16
    +
    interface Calculable {
    +	double PI = 3.14;
    +	int sum(int v1, int v2);
    +}
    +interface Printable {
    +	void print();
    +}
    +class RealCal implements Calculable, Printable {
    +	public int sum(int v1, int v2) {
    +		return v1 + v2;
    +	}
    +
    +	public void print() {
    +		System.out.println("this is RealCal!!!");
    +	}
    +}
    +
    • RealCal 인스턴스를 Calculable 타입으로 선언하면, Printable 메소드를 사용할 수 없다. 반대도 마찬가지.
  • 자식 클래스의 객체 instanceof 부모 클래스 하면 true가 나온다.

추상클래스

  • 인터페이스의 일부 메소드를 클래스 내에서 구현시켰을때 사용.

  • 구현하지 않은 메소드의 리턴값 앞과 클래스 앞에 abstract를 붙여야한다

    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +
    interface Calculable {
    +	double PI = 3.14;
    +
    +	int sum(int v1, int v2);
    +	int minus(int v1, int v2);
    +}
    +
    +abstract class RealCal implements Calculable {
    +	public int sum(int v1, int v2) {
    +		return v1 + v2;
    +	}
    +	public abstract int minus(int v1, int v2);   // 메소드는 없어도 되긴함
    +}
    +

enum

  • 상수의 집합. class 선언시 class 대신에 enum을 적어주면 된다.
  • 상수를 적을 땐, 타입이나 값을 지정할 필요없음.
  • 상수들의 이름만 콤마로 구분하고 가장 마지막에 세미콜론을 넣어주자.
  • Enum의 부모는 무조건 java.lang.Enum 따라서 다른 것을 상속받으면 안됨.

final

  • 클래스, 메소드, 변수에 선언할 수 있음.
  • 상속, override, 변경할 수 없다는 의미. const 같은 거.
  • 변수에서는 더 이상 변경할 수 없다는 의미
    • 인스턴스, 클래스 변수에 final를 쓸땐, 선언과 동시에 초기화 해야함.
    • 매개변수, 지역변수에 final을 쓸 땐, 굳이 초기화하지 않아도 된다.(지역변수의 경우 첫 초기화시 값이 고정됨.)


예외

예외 처리

  • try - catch로 가능

    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +14
    +15
    +16
    +
    try {
    +	System.out.println(2);
    +	System.out.println(scores[3]); //ArrayIndexOutOfBoundsException
    +	// 여기까지 실행 -> catch(ArrayIndexOutOfBoundsException e)문으로 이동
    +	System.out.println(3);
    +	System.out.println(2/0); //ArithmeticException
    +	System.out.println(4);
    +} catch(ArithmeticException e) {
    +	System.out.println("잘못된 계산이네요.");
    +} catch(ArrayIndexOutOfBoundsException e) {
    +	System.out.println("없는 값을 찾고 계시네요 ^^");
    +} catch(Exception e) {
    +	System.out.println("알수 없는 에러");
    +} finally {
    +    System.out.println("무조건 실행")
    +}
    +
    • catch는 위에서부터 먼저 검사하여, 에러의 타입이 처음 맞는 곳에 들어간다.
    • 일치하는 곳이 없으면 throw 됨.
    • Exception 클래스는 모든 에러클래스의 부모로, 마지막 catch 문에 Exception을 추가하면 그 곳에 들어간다.
    • Exception 클래스는 Throwable 클래스를 상속받고 있어, Throwable 클래스의 메소드도 사용가능하다.
    • finally 로 try가 끝날시, 또는 catch가 끝날 시 실행될 코드를 작성할 수 있다.

uncheked Exception vs checked Exception

  • unchecked exception : 컴파일은 되지만, runtime 중에 발생할 수 있는 exception. RuntimeException 클래스로부터 상속된 예외
  • cheked Exception : try-catch로 잡지 않으면 컴파일이 안되는 에러. Throwable를 상속받은 클래스 중 RuntimeException을 제외한 모든 에러와 예외들.

try-with-resource

  • Java SE7 부터 추가된 구문

  • 자원을 잡고, 놓아주는 과정을 try-catch-finally로 하면 복잡하기에 이를 간단히 해주는 구문

  • 아래와 같이 try 문에 괄호를 추가하여 그 안에 사용할 자원을 정의한다.

    • 자원은 여러개 넣을 수 있으며, 세미콜론(;)으로 구분한다. 마지막 객체에는 넣지 않음.
    • 전체 try문이 종료되면 생성된 인스턴스는 자동으로 종료되기에 명시적으로 close를 이용하지 않아도 된다.
    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +14
    +15
    +16
    +17
    +18
    +19
    +20
    +21
    +22
    +
    // 전
    +try {
    +	f = new FileWriter("data.txt");
    +	f.write("Hello");
    +} catch(IOException e) {
    +	e.printStackTrace();
    +} finally {
    +	if (f != null) {
    +		try {
    +			f.close();
    +		} catch (IOException e) {
    +			e.printStackTrace();
    +        }
    +	}
    +}
    +
    +// 후
    +try (FileWriter f = new FileWriter("data.txt")) {
    +	f.write("Hello");
    +} catch (IOException e) {
    +	e.printStackTrace();
    +}
    +

throw

  • 예외를 상위로 미루어 처리하는 것.

    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +14
    +
    public void test(String a, String b) throws NumberFormatException {
    +    int sum = Integer.parseInt(a) + Integer.parseInt(b);
    +    System.out.println("합 : " + sum);
    +}
    +
    +public void test(String a, String b) throws NumberFormatException {
    +    try {
    +        int sum = Integer.parseInt(a) + Integer.parseInt(b);
    +        System.out.println("합 : " + sum);
    +    } catch(NumberFormatException e) {
    +        System.out.println("숫자형 문자가 아닙니다.")
    +        throw e;
    +    }
    +}
    +

예외처리 전략

  • 커스텀 예외 클래스를 만들때는, 꼭 try-catch로 묶어줄 필요가 있을 때만 Exception 클래스를 확장한다.
  • 일반적으로 실행시 예외를 처리할 수 있는 경우에만 RuntimeException을 확장한다.
  • catch 블록을 공백으로 놔두지 않는다.


익명클래스

다음과 같은 경우 사용함

  • 프로그램내에서 일시적으로 사용되어야하는 객체
  • 재사용성이 없고, 확장성을 활용하는 것이 유지보수에서 더 불리할 때

문법

1
+2
+3
+
부모 클래스 [필드|변수] = new 부모클래스(매개값,...) {
+
+};
+

구현법

  • 필드의 초기값
  • 로컬변수의 초기값
  • 매개변수의 매개

예시

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+
class Insect {
+	void attack(){
+		System.out.println("곤충은 공격을 한다");
+	}
+}
+
+public class Anonymous {
+	//★★방법 1 : 필드에 익명자식 객체를 생성
+	Insect spider1 = new Insect(){
+		String name = "무당거미";
+
+		void cobweb(){
+			System.out.println("사각형으로 거미줄을 친다.");
+		}
+		@Override
+		void attack() {
+			System.out.println(name + " 독을 발사한다.");
+		}
+	};
+
+	//★★방법2 : 로컬변수의 초기값으로 대입
+	void method1(){
+		Insect spider2 = new Insect(){
+			String name = "늑대거미";
+
+			//거미줄을 치다.
+			void cobweb(){
+				System.out.println("육각형으로 거미줄을 친다.");
+			}
+			@Override
+			void attack() {
+				System.out.println(name + " 앞니로 문다.");
+			}
+		};
+		//로컬변수이기 때문에 메서드에서 바로 사용
+		spider2.attack();
+
+        // 아래 참고. 접근 불가능
+        spider2.cobweb();
+	}
+
+	//★★방법3 : 익명객체 매개변수로 대입
+	void method2(Insect spider){
+		spider.attack();
+	}
+}
+

참고

  • 익명자식객체에서 새롭게 정의된 필드와 메소드는 자식객체 레벨에서만 사용되기 때문에 외부에서는 사용할 수 없음
  • 익명객체를 받아준 변수는 부모타입의 클래스이기 때문에 부모레벨에서 정의된 필드나 메서드만 사용이 가능

자바의 신

패키지

  • import 문을 통해 패키지 사용 가능
  • 자바파일이 컴파일 될 때, import 된 클래스도 같이 컴파일 된다.
  • import로 다른 패키지 접근하기.
    • import (루트디렉토리제외) 경로.클래스이름; //한 클래스만 참조
    • import (루트디렉토리제외) 경로.*; //마지막 폴더 아래 모든 클래스 참조

String

  • StringBuffer : Thread safe, 속도 느림, 여러 쓰레드가 그 변수를 접근할 일이 있을때.
  • StringBuilder : Thread unsafe, 속도 빠름, 하나의 메소드 안에서 선언하고 더할 경우.
    • 문자열을 합칠때, StringBuilder의 append 메소드를 사용하면 String에서 + 연산자를 사용할때보다 속도가 빠르다.

제네릭

  • 클래스, 메소드를 다양한 타입에서 사용할 수 있도록 해주는 것

    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +14
    +15
    +16
    +17
    +18
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +26
    +27
    +28
    +29
    +30
    +31
    +32
    +33
    +34
    +
    // 클래스에서
    +public class GenericDTO<T> implements Serializable {
    +	private T object;
    +	public void setObject(T obj) {
    +		this.object = obj;
    +	}
    +	public T getObject() {
    +		return object;
    +    }
    +}
    +//호출
    +GenericDTO<String> dto1 = new GenericDTO<String>();
    +GenericDTO<StringBuffer> dto2 = new GenericDTO<StringBuffer>();
    +
    +
    +//메소드에서
    +//제네릭 메소드
    +public <T> T genericMethod(T c, T addValue) {
    +		System.out.println(addValue);
    +		System.out.println(c);
    +		return c;
    +}
    +//Bounded Generic 메소드
    +public <T extends Car> void genericMethod(WildcardGeneric<T> c, T addValue) {
    +	c.setWildcard(addValue);
    +	T value = c.getWildcard();
    +	System.out.println(value);
    +}
    +//두 개이상의 제네릭 메소드
    +public <S, T extends Car> void genericMethod(WildcardGeneric<T> c, T addValue, S another) {
    +	c.setWildcard(addValue);
    +	T value = c.getWildcard();
    +	System.out.println(value);
    +}
    +
  • <?> : 와일드카드

    • 매개변수에서 사용할 때, 어떤 타입이 올지 모르는 경우 사용

      1
      +
      public void wildcardStringMethod(WillcardGeneric<?> c);
      +
  • <? extends Car>

    • Car나 Car 자손들만 들어갈 수 있음.
  • <? super T>

    • T나 T 조상만 들어갈 수 있음.

자료형

  • Collection 인터페이스
    • List : 동적으로 변하는 배열. 하위 클래스의 부모
      • ArrayList : 확장가능한 배열. Thread safe 하지 않음
      • Vector : 확장 가능한 배열. Thread safe 함.
      • Stack : LIFO. Vector 확장
      • LinkedList : 한 데이터가 자신의 앞 뒤에 무엇이 있는지 알고 있음.
    • Set : 중복 방지. 원하는 값이 있는지 확인하는게 주임무
      • HashSet : 순서가 전혀 필요없는 데이터를 해쉬 테이블에 저장함. 성능이 가장 좋음
      • TreeSet : red-black tree. Hash보단 느림
      • LikedHashSet : 연결된 목록 타입으로 구현된 해시테이블에 데이터를 저장. 저장된 순서에 따라 값이 정렬되며 성능이 가장 느림.
    • Queue : FIFO
      • PriorityQueue
      • Deque : 맨앞, 맨뒤에 값을 넣거나 뺄 수 있음
  • Map 인터페이스 : key-value로 이루어져있음.
    • HashMap : key-velue로 저장만 함.
    • TreeMap : 값을 저장하면서 키를 정렬한다.
    • LinkedHashMap
    • Hashtable

// 이후로는 키워드만

쓰레드

  • 한 코어에 하나의 쓰레드 실행
  • Runnable 인터페이스, Thread 클래스로 생성가능

파일 IO

  • File, Files
    • File 클래스 : 파일 및 파일 경로정보 포함. 심볼릭링크 같이 유닉스계열에서 사용가능한 기능을 제공하지 않음
    • Files 클래스 : JDK 7 이상. 모든 메소드가 static이라 별도의 객체 생성이 없어 더 편리하다.
  • InputStream, OutputStream
    • Stream은 byte를 처리하기 위한 것. 네트워크를 처리할 때 많이 사용한다.
  • Reader, Writer
    • char 기반의 문자열을 처리하기 위한 클래스.
    • 일반적인 텍스트 에디터로 볼 수 있는 파일들을 위한 클래스.
  • 텍스트 파일 읽기, 쓰기
    • FileReader, BufferedReader : 텍스트 파일 읽기
    • FileWriter : 텍스트 파일 쓰기
  • Scanner
    • 텍스트 기반 기본 자료형이나 문자열 데이터를 처리하기 위한 클래스
    • 사용자로부터 입력을 받을 수 있다.

Serializable 인터페이스

  • 개발자가 만든 클래스가 파일에 읽거나 쓸 수 있도록 하거나, 다른 서버로 보내거나 받을 수 있도록하려면 반드시 구현해야함.
  • 인터페이스를 구현한 다음에 serialVersionUID를 지정하는 걸 권장받음. 해당 클래스의 버전을 명시하는 것.
    • UID가 다른 클래스는 이름이 같은 클래스여도 다른 클래스로 인식.
    • 별도로 지정하지 않으면 컴파일시 자동 지정됨.
  • 객체 저장 : ObjectOutputStream 클래스 사용
  • 객체 읽기 : ObjectInputStream 클래스 사

자바 NIO

  • 스트림이 아닌 채널과 버퍼를 사용함
    • 채널 : 물건을 중간에처 처리하는 도매상
    • 버퍼 : 도매상한테 물건을 사고, 소비자에게 파는 소매상

네트워크

  • TCP 통신
    • Socket 클래스
      • 데이터를 보내는 쪽(클라이언트)에서 객체를 생성하여 사용함.
      • 데이터를 받는 쪽(서버)에서 클라이언트 요청을 받으면, 요청에 대한 socket 객체를 사용하여 데이터를 처리함.
      • Socket 객체를 직접 생성해야함.
    • SeverSocket 클래스
      • 서버에서 사용하는 클래스. 객체는 클라이언트 요청이 생기면 Socket 객체를 생성하여 전달해줌.
      • 생성자와 2개의 메소드만 알아도 네트워크 프로그래밍이 가능하다.
      • 클라이언트에서 접속을 하면 Socket 객체를 생성함.
  • UDP 통신

    • DatagramSocket 클래스
      • 보내는 역할과 받는 역할 모두 수행할 수 있음.
    • DatagramPacket 클래스
      • 스트림을 사용하지 않고 데이터를 주고 받음.
  • Http 요청
    • URL 클래스 : 아주 간단한 요청 처리가능. 별로 추천하진 않음
    • Apache의 HttpComponents : 일반적으로 많이 사용함


자바 버전별

Java 7

  • 숫자표현방법

    • 진수
      • 2진수 : 0b
      • 8진수 : 0
      • 16진수 : 0x
    • 천단위 언더바 : 1_000_000
  • switch 문에서 String 사용가능

  • 제네릭을 쉽게 사용할 수 있는 Diamond

    1
    +
    GenericDTO<String> dto1 = new GenericDTO<>();
    +
    • 이렇게 뒤는 생략가능하게 하는 거
  • 예외 처리시 다중 처리가능

    1
    +2
    +3
    +
    catch(IllegalArgumentException | FileNotFoundException | NullPointException e){
    +	e.printStackTrace();
    +}
    +
    • 위와 같이 다중으로 에러를 받을 수 있음.
  • fork/join 관련 클래스

    • cpu 멀티코어를 쉽게 사용하기 위해 만들어짐. 여러개로 나누어 계산한 후 결과를 모은다.
    • 쓰레드 신경 안써도 JVM이 자동으로 처리하고, worker의 개수는 CPU의 개수만큼 증가한다.
  • NIO2

    • 파일의 속성, 심볼릭 링크까지 처리가능. 어떤 파일이 변경되었는지 확인할 수 있는 WatchService 클래스 제공
    • File을 대체하는 클래스 : java.nio.file 에 있음
      • Paths
      • Files : 기존 File 클래스를 보완.
      • FileSystems : 파일시스템에 대한 정보를 처리
      • FileStore : 파일을 저장하는 디바이스, 파티션, 볼륨 등에 대한 정보를 처리
    • SeekableByteChannel(random access) : 바이트기반 채널을 처리
    • NetworkChannel 및 MulticastChannel
    • AsynchronousFileChannel : 비동기 처리 파일 채널
  • JDBC 4.1 : SQL Query를 쉽게 수행할 수 있는 RowSet의 객체를 쉽게 만들어주는 클래스 포함

  • TransferQueue : 메세지처리에 사용

  • Objects 클래스 : Object와 같은 static한 메소드 제공. 넘어온 객체가 null이어도 예외가 발생하지 않음

Java8

  • Lambda 표현식

    • 익명클래스의 가독성을 올리기 위해 사용됨.
    • 구현해야할 함수가 하나일때만 사용가능. (default, static 제외)
    • js 화살표함수처럼 사용.
    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +14
    +15
    +16
    +17
    +18
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +26
    +27
    +28
    +
    // 이전
    +private void calculateClassic() {
    +	Calculate claculateAdd = new Calculate() {	//Calculate 는 Functional 인터페이스
    +		@Override
    +		public int operation(int a, int b) {
    +			return a+b;
    +		}
    +	};
    +	System.out.println(calculateAdd.operation(1,2));
    +}
    +
    +// 이후
    +private void calculateLambda() {
    +	Calculate calculateAdd = (a,b) -> a+b;
    +	System.out.println(calculateAdd.operation(1,2));
    +}
    +
    +// 이전
    +new Thread(new Runnable() {
    +    @Override
    +    public void run() {
    +    	System.out.println("Thread run");
    +    }
    +}).start();
    +
    +// 이후
    +new Thread(()->System.out.println("Thread run")).start();
    +// Thread 생성자로는 Runnable을 넣어줘야하므로 이렇게 해줘도 알아서 Runnable로 들어감.
    +
  • Functional 인터페이스 : java.util.function 패키지에 속함. 추상메서드가 오직 하나인 인터페이스를 뜻하므로 람다식과 호흡이 좋음.

    • Predicate : test()라는 메소드 존재. 두 개의 객체를 비교할 때 사용하고 boolean을 리턴함.
    • Supplier : get() 메소드 존재. 리턴값은 generic으로 선언된 타입을 리턴함
    • Consumer : accept()라는 매개변수를 하나 갖는 메소드가 존재. 리턴값은 없음.
    • Function : apply()라는 하나의 매개변수를 갖는 메소드 존재. 리턴값 존재.
    • UnaryOperator : apply()라는 하나의 매개변수를 갖는 메소드가 있음. 리턴값 존재.
    • BinaryOperator : apply()라는 두 개의 매개변수를 갖는 메소드가 있음. 리턴값 존재.
  • Stream : 연속된 정보를 처리할 때 사용함.

    • 배열에는 사용할 수 없지만, 컬렉션에는 사용가능. 따라서 배열을 List로 바꾼다음 사용.
    • .stream() : 스트림 생성. 컬렉션을 스트림 객체로 변환함. 변환 후 중개, 중단 연산 사용
    • 중개연산 : filter(), map(), flatMap(), sorted()
    • 종단연산 : forEach(), toArray(), anyMatch(), allMatch(), nonMatch(), findFirst(), any(), reduce(), collector()
    • 메소드 참조. 데이터가 바로 메소드의 매개변수로 들어가도록 하는 법
      • ` .forEach(System.out::println), .forEach(ContainingClass::staticMethodName)`
  • Optional : null이 올 수 있는 값을 감싸기 위해 사용. 값이 null이더라도 NPE이 발생하지않음

    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +14
    +15
    +16
    +17
    +18
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +26
    +27
    +28
    +29
    +30
    +31
    +32
    +33
    +34
    +35
    +36
    +
    // Java8 이전
    +List<String> names = getNames();
    +List<String> tempNames = names != null
    +    ? names
    +    : new ArrayList<>();
    +
    +// Java8 이후
    +List<String> nameList = Optional.ofNullable(getNames())
    +    .orElseGet(() -> new ArrayList<>());
    +
    +// Java8 이전
    +public String findPostCode() {
    +    UserVO userVO = getUser();
    +    if (userVO != null) {
    +        Address address = user.getAddress();
    +        if (address != null) {
    +            String postCode = address.getPostCode();
    +            if (postCode != null) {
    +                return postCode;
    +            }
    +        }
    +    }
    +    return "우편번호 없음";
    +}
    +public String findPostCode() {
    +    // 위의 코드를 Optional로 펼쳐놓으면 아래와 같다.
    +    Optional<UserVO> userVO = Optional.ofNullable(getUser());
    +    Optional<Address> address = userVO.map(UserVO::getAddress);
    +    Optional<String> postCode = address.map(Address::getPostCode);
    +    String result = postCode.orElse("우편번호 없음");
    +
    +    // 그리고 위의 코드를 다음과 같이 축약해서 쓸 수 있다.
    +    String result = user.map(UserVO::getAddress)
    +        .map(Address::getPostCode)
    +        .orElse("우편번호 없음");
    +}
    +
    • 결과가 null이 될 수 있으며, null에 의해 오류가 발생할 가능성이 매우 높을 때 반환값으로만 사용
  • 인터페이스의 기본 메소드

    1
    +2
    +3
    +4
    +5
    +
    public interface Vehicle {
    +    public default void doSomething(int n) {
    +        System.out.println("doSomething(Vehicle)");
    +    }
    +}
    +
    • 위와 같이 default 키워드와 함께 인터페이스의 디폴트메소드 선언가능
    • 다중 implements 시 같은 메소드가 default라면,
      • 컴파일 오류 발생. override 해줘야힘
    • 상속, implements 시 같은 메소드가 구현되어있다면
      • 상속 메소드가 사용됨.
  • 날짜 관련 클래스 추가

    • 불변 클래스 추가 : java.time.ZonedDateTime, java.time.LocalDate
    • java.time.format.DateTimeFormatter : thread safe하고 빠른 클래스
  • 병렬배열 정렬

    • Arrays 클래스 : 배열을 처리하기에 좋은 여러 static 메소드를 가지고 있음.
      • binarySearch(), fill(), sort() 등
    • parallelSort() : Fork-Join 프레임웍이 내부적으로 사용됨. 여러 쓰레드로 수행되어 속도는 더 빠르다.
  • StringJoiner : 순차적으로 나열되는 문자열을 처리할 때 사용함.


출처

https://www.boostcourse.org/cs128

자바의 신

https://mangkyu.tistory.com/70

https://limkydev.tistory.com/226

This post is licensed under CC BY 4.0 by the author.

useEffect 올바른 사용

리액트 리팩토링

Comments powered by Disqus.

diff --git "a/posts/java-\354\273\244\354\212\244\355\205\200-annotation/index.html" "b/posts/java-\354\273\244\354\212\244\355\205\200-annotation/index.html" new file mode 100644 index 000000000..adaacc557 --- /dev/null +++ "b/posts/java-\354\273\244\354\212\244\355\205\200-annotation/index.html" @@ -0,0 +1,59 @@ + 커스텀 Annotation | 디피의 개발일지
Posts 커스텀 Annotation
Post
Cancel

커스텀 Annotation

Annotation

애노테이션은 Java 5 부터 등장한 기능으로, 사전적의미는 “주석”이지만, 클래스나 메서드 등 타켓에 라벨을 붙여준다. 비즈니스 로직에는 영향을 주지는 않지만, 해당 타켓의 연결 방법이나 소스 코드의 구조를 변경할 수도 있다. 애노테이션은 소스코드에 메타데이터를 삽입하는 것이기 때문에 잘 이용하면 구독성뿐만 아니라 체계적인 소스코드 구성에도 도움을 준다.

1
+2
+3
+
// 애노테이션 예시
+@Service
+public class ArticleService
+


커스텀 Annotation

커스텀 애노테이션은 메타 애노테이션을 사용하여 다음과 같은 구조를 가진다.

1
+2
+3
+4
+5
+6
+
@Target({ElementType.[적용대상]})
+@Retention(RetentionPolicy.[정보유지되는 대상])
+public @interface [어노테이션명] {
+  public 타입 elementName() [default ]
+  ...
+}
+
  • @Target@Retention 등과 같은 메타 애노테이션으로 애노테이션의 성질을 정의한다.

  • 애노테이션이 가질 수 있는 필드 타입은 enum, String, 원시형, 원시형의 배열만 사용할 수 있다.

  • 다음처럼 다른 애노테이션을 추가할 수도 있다.

    1
    +2
    +3
    +4
    +5
    +
    @Target({ElementType.[적용대상]})
    +@Retention(RetentionPolicy.[정보유지되는 대상])
    +@Service
    +@RequiredArgsConstructor
    +public @interface [어노테이션명]
    +

메타 애노테이션

  • @Retention : 애노테이션의 라이프 사이클을 정의하는 애노테이션으로, 애노테이션이 언제까지 메모리 상에 살아있을 건지 정의
    • RetentionPolicy.SOURCE : 컴파일 전까지만 유효
    • RetentionPolicy.CLASS : 컴파일러가 클래스를 참조할 때까지 유효
    • RetentionPolicy.RUNTIME : 컴파일 이후 런타임 때도 JVM에 의해 참조 가능함
  • @Target : 애노테이션을 적용할 위치 선택
    • ElementType.PACKAGE : 패키지 선언
    • ElementType.TYPE : 타입 선언
    • ElementType.ANNOTATION_TYPE : 어노테이션 타입 선언
    • ElementType.CONSTRUCTOR : 생성자 선언
    • ElementType.FIELD : 멤버 변수 선언
    • ElementType.LOCAL_VARIABLE : 지역 변수 선언
    • ElementType.METHOD : 메서드 선언
    • ElementType.PARAMETER : 전달인자 선언
    • ElementType.TYPE_PARAMETER : 전달인자 타입 선언
    • ElementType.TYPE_USE : 타입 선언
  • @Documented : 해당 애노테이션이 붙은 대상을 javadoc에 포함시킴
  • @Inherited : 애노테이션의 상속을 가능하게 함
  • @Repeatable : Java8부터 지원하며, 연속적으로 애노테이션을 선언할 수 있게함

속성 정의

애노테이션을 정의할 땐 마치 인터페이스처럼 다양한 속성을 정의할 수 있다.

1
+2
+3
+4
+5
+6
+
public @interface MyAnnotation {
+  public String name() default "";
+  public int age() default 20;
+  public String value() default "";
+    ...
+}
+

이 중에서 value라는 이름을 가진 속성은 특별한 속성으로, 애노테이션을 사용할 때 아무런 속성이름 없이 하나의 값만 사용하면 자동으로 value 속성에 할당된다.

1
+2
+3
+4
+
// 여러 속성을 부여할 때
+@MyAnnotation(name="Shin", age=20, value="this will be assigned to value")
+// 한가지 값만 넣으면 value에 할당된다.
+@MyAnnotation("this will be assigned to value")
+


자바 리플랙션

어플리케이션 실행시 커스텀 애노테이션을 사용한 곳과 지정한 값들을 얻어오려면 자바 리플랙션을 사용해야한다.

1
+2
+3
+4
+5
+
Method method = MyClass.class.getMethod("doThis"); //자바 리플렉션 getMethod로 메서드 doThis를 얻어온다
+Annotation[] annotations = method.getDeclaredAnnotations(); //메서드에 선언된 어노테이션 객체를 얻어온다
+
+//메서드 doThat에 선언된 MyAnnotation의 어노테이션 객체를 얻어온다
+Annotation annotation = MyClass.class.getMethod("doThat").getAnnotation(MyAnnotation.class);
+


출처

https://www.nextree.co.kr/p5864/

https://jeong-pro.tistory.com/234

https://velog.io/@potato_song/Java-%EC%96%B4%EB%85%B8%ED%85%8C%EC%9D%B4%EC%85%98-%EC%BB%A4%EC%8A%A4%ED%85%80-%EC%96%B4%EB%85%B8%ED%85%8C%EC%9D%B4%EC%85%98-%EB%A7%8C%EB%93%A4%EA%B8%B0

This post is licensed under CC BY 4.0 by the author.

STOMP

Apache Kafka

Comments powered by Disqus.

diff --git "a/posts/java-\355\201\264\353\236\230\354\212\244-\354\264\210\352\270\260\355\231\224-\353\270\224\353\241\235/index.html" "b/posts/java-\355\201\264\353\236\230\354\212\244-\354\264\210\352\270\260\355\231\224-\353\270\224\353\241\235/index.html" new file mode 100644 index 000000000..67b199578 --- /dev/null +++ "b/posts/java-\355\201\264\353\236\230\354\212\244-\354\264\210\352\270\260\355\231\224-\353\270\224\353\241\235/index.html" @@ -0,0 +1,83 @@ + 클래스 초기화 블록 | 디피의 개발일지
Posts 클래스 초기화 블록
Post
Cancel

클래스 초기화 블록

클래스 초기화 블록

어떤 클래스에 static 변수를 추가했더니 github copilot이 다음과 같은 문장을 추천해주었다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+
public class Crypto {
+
+	private static CryptoProperties cryptoProperties;
+
+  // github copilot suggestion
+	static {
+		cryptoProperties = new CryptoProperties();
+	}
+	...
+

처음보는 문법이기에 조사해보았더니 클래스 초기화 블록이라는 단어를 접할 수 있었다.


클래스 초기화 블록

클래스의 멤버변수를 초기화 하는 방법으로는 다음과 같은 방법들이 있다.

  1. 생성자

  2. 필드 초기화

    1
    +2
    +3
    +4
    +
    public class Location {
    +  int x = 4;
    +  int y = 6;
    +}
    +
  3. 초기화 블록

이 중에서 필드 초기화는 간결하지만 문법상 한줄짜리 구문만 초기화 할 수 있다는 단점이 있다. 이 단점을 보완하기 위해 나온 것이 초기화 블록이다.

초기화 블록은 다음과 같이 사용한다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
public class Location {
+  int x;
+  int y;
+  // 초기화 블록
+  {
+    x = 5;
+    x += 10;
+    y = 12;
+    y -= 8;
+  }
+}
+

위와 같이 초기화 블록이 선언되어있으면, 필드 초기화와 같이 인스턴스를 생성할 자동으로 호출된다. 또한 이 초기화 블록은 할당만을 공간이 아니라 다음과 같이 할당문이 아닌 실행문을 사용할 수도 있다.

1
+2
+3
+4
+5
+6
+7
+8
+
public class Location {
+  int x;
+  int y;
+  // 초기화 블록
+  {
+    System.out.println("초기화 블록 호출")
+  }
+}
+

static 초기화 블록

초기화 블록은 static 멤버변수를 초기화할 때도 사용할 수 있다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+
public class Location {
+  static int x;
+  static int y;
+  // 초기화 블록
+  static {
+    x = 5;
+    y = 10;
+  }
+}
+

위와 같이 일반 초기화 블록 앞에 static 키워드를 붙이면 된다.

This post is licensed under CC BY 4.0 by the author.

pinpoint

Valid vs Validatedated

Comments powered by Disqus.

diff --git a/posts/js-memory-leak/index.html b/posts/js-memory-leak/index.html new file mode 100644 index 000000000..bee807ca4 --- /dev/null +++ b/posts/js-memory-leak/index.html @@ -0,0 +1,289 @@ + Javscript 메모리 누수 방지 및 성능 개선 | 디피의 개발일지
Posts Javscript 메모리 누수 방지 및 성능 개선
Post
Cancel

Javscript 메모리 누수 방지 및 성능 개선

Javscript 메모리 관리 이해하기

Garbage collector

자바스크립트 엔진은 더이상 사용하지 않을 메모리를 놓아주기 위해 garbage collector를 사용한다. garbage collector(GC)는 앱에서 더이상 사용하지 않을 객체를 찾아내고 삭제한다. 따라서 GC는 앱의 object와 변수를 계속 모니터링하고 어떤 것이 여전히 referenced 되고 있는지 트랙킹한다. 그러다 Object가 더이상 쓰이지 않으면 마킹하고 삭제하여 메모리를 놓아준다.

GC가 사용하는 이 기법은 mark and sweep이다.

  1. 아직 사용중인 모든 Object를 마킹함 (mark)
  2. heap을 검사하고, 마킹되지 않은 object는 삭제함. (sweep)

이 작업은 주기적으로 수행되며, heap 의 사이즈가 작더라도 수행된다.

Stack vs Heap

자바스크립트의 메모리를 얘기하면 stackheap이 주역이다.

stack은 함수 실행 동안 필요한 데이터를 저장한다. 빠르고 효율적이지만 공간은 한정적이다. 함수가 실행되면 자바스크립트 엔진이 그 함수의 변수와 파라미터를 스택에 push하고, 함수가 return 되면 pop 한다. 이렇게 stack은 빠르고 효율적으로 메모리를 관리한다.

반면에 heap은 앱의 생명주기 동안 데이터를 저장하는데 사용한다. stack에 비해 다소 느리고 덜 정리되어있지만 공간은 크다. Heap은 object, array 등 복잡한 데이터 구조를 여러번 접근하기 위해 사용된다.


Common causes of Memory Leaks

순환참조(Circular reference)

가장 흔한 케이스 중 하나다. 2개 이상의 object가 서로를 참조하고 있을 때 발생한다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+
let object1 = {};
+let object2 = {};
+
+// create a circular reference between object1 and object2
+object1.next = object2;
+object2.prev = object1;
+
+// do something with object1 and object2
+// ...
+
+// set object1 and object2 to null to break the circular reference
+object1 = null;
+object2 = null;
+

위 예제에서는 마지막에 object1object2를 null로 설정하여 순환 참조를 깨려고 했지만, {next: refenceToObject2}, { prev: refenceToObject1}는 각각 메모리에 남아서 서로를 참조하고 있다. 즉 null로 할당하는 것은 object1, object2에 할당된 reference를 지운것 뿐이고, 순환참조된 obejct들 자체는 메모리에 계속 남아있다. 따라서 GC는 순환 참조를 꺨 수 없다. (하지만 최신 자바스크립트 엔진에서는 GC가 발전하여 메모리 회수를 한다.)

이러한 경우에는 delete 키워드를 사용한 manual memory management가 필요하다. delete를 사용하여 순환참조를 만들어내는 property를 삭제하는 것이다.

1
+2
+
delete object1.next;
+delete object2.prev;
+

또다른 방법은 WeakMap, WeakSet을 사용하는 방법이다. 이 두개는 object와 variable에 대한 약한 참조를 만들 수 있게 한다.

Event Listeners

엘리먼트에 Event Listener를 붙이면, listener function의 reference가 생긴다. 그리고 이 reference는 GC가 메모리를 풀어주는걸 막는다. 이는 앨리먼트가 더이상 필요없어져도 listener function이 제거되지 않으므로 memory leak의 원인이 된다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+
let button = document.getElementById("my-button");
+
+// attach an event listener to the button
+button.addEventListener("click", function() {
+  console.log("Button was clicked!");
+});
+
+// do something with the button
+// ...
+
+// remove the button from the DOM
+button.parentNode.removeChild(button);
+

이를 해결하기 위해선 필요없어진 event listener는 삭제해야한다.

1
+2
+3
+4
+5
+6
+
button.removeEventListener("click", function() {
+  console.log("Button was clicked!");
+});
+
+// 전부 삭제
+button.removeAllListeners();
+

Global variables

전역 변수를 생성하면, 그 변수는 모든 코드에서 접근이 가능하다. 따라서 더 이상 필요한지 아닌지 판단하기가 어렵다. 이는 memory leak로 이어진다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
// create a global variable
+let myData = {
+  largeArray: new Array(1000000).fill("some data"),
+  id: 1
+};
+
+// do something with myData
+// ...
+
+// set myData to null to break the reference
+myData = null;
+

위 코드의 마지막에 myDatanull을 할당하였지만, 전역 변수이기에 모든 코드에서 접근 가능하다. 따라서 더이상 필요한지 아닌지 판단하기 어렵고, 이는 memory leak로 이어진다.

이를 해결하기 위해선 Function Scoping을 사용해야한다. 함수를 생성하고 그 안에서 지역변수로 변수를 생성하는 방법이다. 그러면 그 변수는 오직 그 함수에서만 접근 가능하고, 더이상 필요한지 아닌지 판단하기 쉽다. 즉 필요없어지면 GC에 의해 청소된다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+
function myFunction() {
+  let myData = {
+    largeArray: new Array(1000000).fill("some data"),
+    id: 1
+  };
+
+  // do something with myData
+  // ...
+}
+myFunction();
+

다른 방법은 var 대신에 let, const를 사용하는 방법이다. let, const는 block-scoped이므로 선언된 block 외에선 사용이 불가능하니 GC에 의해 제거될수 있다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+
{
+  let myData = {
+    largeArray: new Array(1000000).fill("some data"),
+    id: 1
+  };
+
+  // do something with myData
+  // ...
+}
+

그외 이유들

  • DOM 노드 참조
    • DOM 노드를 메모리에 저장하면, 저장한 노드의 자식 노드까지 모두 참조가 유지되어 실제 DOM에는 없는데도 메모리 상에선 유지될 수 있다.
  • 클로저 : 사용하지 않는 클로저는 함수에 의해 계속 참조되어있는 상태이므로, 메모리에 계속 남아있어 메모리 누수의 원인이 될 수 있다.


Best Practices for Manual Memory Management

Using weak references

WeakMap, WeakSet 을 사용하여 object와 variable에 대한 weak reference를 만들 수 있다. Weak reference는 일반적인 reference와는 달리 GC가 object에 의해 사용되는 메모리를 free up 하는 걸 막지 않는다. 이러한 특성은 WeakMapWeakSet을 Circular reference를 해결하는데 사용할 수 있도록 한다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+
let object1 = {};
+let object2 = {};
+
+// create a WeakMap
+let weakMap = new WeakMap();
+
+// create a circular reference by adding object1 to the WeakMap
+// and then adding the WeakMap to object1
+weakMap.set(object1, "some data");
+object1.weakMap = weakMap;
+
+// create a WeakSet and add object2 to it
+let weakSet = new WeakSet();
+weakSet.add(object2);
+
+// in this case, the garbage collector will be able to free up the memory
+// used by object1 and object2, since the references to them are weak
+

위에서 순환참조 때 봤던 예시와 비슷하게 서로 참조하고 있더라도, 이번엔 WeakMap을 사용하여 순환참조가 일어나도 메모리 free up을 막지 않는다.

WeakMap은 약한 참조이므로, key object가 WekMap에 존재하더라도 GC에 의해 메모리를 회수할 수 있도록 해준다. 다른 말로, WeakMap은 key object가 다른 코드에서 접근이 불가능해지면, GC가 key object가 차지하고 있던 메모리를 회수하는 걸 막지않는다. 다음 예제에서 objA, objB는 함수 호출이 끝나면 scope에서 벗어나므로, GC가 메모리를 회수할 수 있다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+
function createCircularReference() {
+  let objA = {};
+  let objB = {};
+
+  let weakMap = new WeakMap();
+  weakMap.set(objA, objB);
+  weakMap.set(objB, objA);
+}
+
+createCircularReference();
+

WeakSet도 비슷하게 WeakSet이 가지고 있는 object를 GC가 회수할 수 있다. WeakSet은 objects의 집합을 GC의 메모리 회수를 막는 거 없이 저장할 수 있는 방법이다. 하지만 WeakSet은 순환 참조에 직접 사용할 수 있는 것은 아니다. key-value를 저장하지 않기 때문이다. 오직 object를 저장만하고 그들 사이에 관계를 만들진 않는다.

Using Garbage Collector API

GC API를 사용하는 방법도 있다. GC를 수동적으로 동작하게 하고, 현재 heap의 상태를 알 수 있다. 따라서 memory leak나 성능 이슈를 디버깅하는데도 도움을 준다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+
let object1 = {};
+let object2 = {};
+
+// create a circular reference between object1 and object2
+object1.next = object2;
+object2.prev = object1;
+
+// manually trigger garbage collection
+gc();
+

gc()를 실행하여 GC를 수동적으로 실행하면 설령 아직 참조되고 있더라도 object에 의해 사용되고 있던 메모리를 비운다.

gc()는 모든 자바스크립트 엔진에서 지원되지는 않는다. 그리고 엔진에 따라 동작이 다르다. 또한 성능에 영향을 줄 수 있기에 신중하게 필요할 떄만 사용해야한다.

Using heap snapshots and profilers

Javascripts는 heap snapshotprofilers를 제공하여 어플리케이션이 어떻게 메모리를 사용하고 있는지 알려준다.

Heap Snapshot은 현재 heap 상태의 스냅샷을 찍어주고, 그것을 분석하여 어떤 object가 가장 많은 메모리를 사용하고 있는지 알 수 있게 해준다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+
// Start a heap snapshot
+let snapshot1 = performance.heapSnapshot();
+
+// Do some actions that might cause memory leaks
+for (let i = 0; i < 100000; i++) {
+  myArray.push({
+    largeData: new Array(1000000).fill("some data"), 
+    id: i
+  });
+}
+
+// Take another heap snapshot
+let snapshot2 = performance.heapSnapshot();
+
+// Compare the two snapshots to see which objects were created
+let diff = snapshot2.compare(snapshot1);
+
+// Analyze the diff to see which objects are using the most memory
+diff.forEach(function(item) {
+  if (item.size > 1000000) {
+    console.log(item.name);
+  }
+});
+

위 예시에서는 루프 실행 전후로 스냅샷을 찍고, 두 스냅샷을 비교하여 어떤 object가 메모리를 많이 차지하는지 알 수 있게 해준다.

Profiler는 어플리케이션의 성능을 트랙킹하고, 메모리 사용이 많은 area를 인식한다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+
let profiler = new Profiler();
+
+profiler.start();
+
+// do some actions that might cause memory leaks
+for (let i = 0; i < 100000; i++) {
+  myArray.push({
+    largeData: new Array(1000000).fill("some data"), 
+    id: i
+  });
+}
+
+profiler.stop();
+
+let report = profiler.report();
+
+// analyze the report to identify areas where memory usage is high
+for (let func of report) {
+  if (func.memory > 1000000) {
+    console.log(func.name);
+  }
+}
+

heap snapshotprofilers은 모든 엔진과 브라우저에서 지원되는 것은 아니다.


출처

https://itnext.io/javascript-memory-management-how-to-avoid-common-memory-leaks-and-improve-performance-c018dbbca954#9f04

This post is licensed under CC BY 4.0 by the author.

Typescript 5.0

typescript 조건부타입(extends)

Comments powered by Disqus.

diff --git "a/posts/js-\354\275\224\355\205\214-\353\214\200\353\271\204/index.html" "b/posts/js-\354\275\224\355\205\214-\353\214\200\353\271\204/index.html" new file mode 100644 index 000000000..7602243f4 --- /dev/null +++ "b/posts/js-\354\275\224\355\205\214-\353\214\200\353\271\204/index.html" @@ -0,0 +1,17 @@ + JS 코테 대비 메소드 정리 | 디피의 개발일지
Posts JS 코테 대비 메소드 정리
Post
Cancel

JS 코테 대비 메소드 정리

문법

  • for … of : 반복가능한 객체 (Array, String, Set)
  • for … in : 객체. 키를 뽑아냄.


Set

  • Set.prototype.add(value)
  • Set.prototype.clear()
  • Set.prototype.delete(value)
  • Set.prototype.forEach((value, key, set) => {})
    • 값, 키, set을 받음
  • Set.ptototype.has(value)


Map

  • Map.prototype.set(key, value)

  • Map.prototype.get(key)

  • Map.prototype.delete(key)

  • Map.prototype.has(key)

  • Map.prototype.forEach((value, key, map) => {})


Array

  • Array.prototype.pop()
    • 마지막 요소를 제거하고 그 요소를 반환함.
  • Array.prototype.reverse()
    • 배열을 반전시킴
  • Array.prototype.shift()
    • 배열의 첫번째 요소를 제거하고, 제거된 요소를 반환함.
  • Array.prototype.unshift()
    • 배열의 맨 앞쪽에 추가하고, 새로운 길이를 반환함.
  • Array.prototype.slice(begin?, end?)
    • begin부터 end까지 잘라서 반환. end가 length보다 길면 length까지만 반환
  • Array.prototype.sort(compareFunction)
    • compareFunction 반환값
      • < 0 : a를 b보다 낮은 색인으로 정렬
      • = 0 : 변경하지 않음
      • > 0 : b를 a보다 낮은 색인으로 정렬
  • Array.prototype.splice(start, deleteCount?, item1?, ….)
    • start부터 deleteCount만큼 삭제하고, 해당 위치에 item…을 추가
    • 원본을 변경시킴.


String

  • String.prototype.includes(word)
    • string 안에 word가 존재하는지 검사
  • String.prototype.slice(start, end?)
    • start 부터 end까지 잘라서 반
  • reverse : str.split(“”).reverse().join(“”);


Number

  • Number.MAX_VALUE

  • Number.parseInt(string)


Math

  • Math.ceil()
  • Math.floor()
  • Math.trunc() : 소수부분을 제거하고 정수부분을 반환. (음수, 양수 상관없이)


Object

  • Object.entries(object)

    • [키, value] 형태로 배열을 반환

      1
      +2
      +3
      +4
      +5
      +6
      +7
      +8
      +
      const object1 = {
      +  a: 'somestring',
      +  b: 42
      +};
      +    
      +for (const [key, value] of Object.entries(object1)) {
      +  console.log(`${key}: ${value}`);
      +}
      +



lower_bound, upper_bound 원리

lower_bound

  • mid의 값이 k 미만일때 : [mid + 1, end] 를 탐색
  • 그 외 : mid가 lower bound가 될 수 있으므로, 결과 변수에 기존값과 mid를 비교해 더 작은 값을 저장하고, lower bound가 될 수 있는 더 작은 값이 있는지 [start, mid - 1]를 탐색한다.

upper_bound

  • mid의 값이 k 이하일때 : [mid + 1, end] 탐색
  • 그 외 : mid가 upper_bound 가 될 수 있으므로, 결과 변수에 기존 값과 mid를 비교해 더 작은 값을 저장하고, 더 작은 구간 [start, mid - 1]을 탐색한다.
This post is licensed under CC BY 4.0 by the author.

학습 내용 요약

React에서 Key를 사용하는 이유

Comments powered by Disqus.

diff --git a/posts/junction_portfolio/index.html b/posts/junction_portfolio/index.html new file mode 100644 index 000000000..108d3288c --- /dev/null +++ b/posts/junction_portfolio/index.html @@ -0,0 +1 @@ + Junctions/Seoul 2021 해커톤 | 디피의 개발일지
Posts Junctions/Seoul 2021 해커톤
Post
Cancel

Junctions/Seoul 2021 해커톤

Junctions/Seoul 2021 해커톤

  • Junction 은 핀란드에서 시작된 국제 해커톤으로, 2박 3일동안 진행된다.
  • Autocrypto, Microsoft, SIA, AWS game tech 4개의 기업이 파트너로 참가하고, 참가자들은 이 4개의 기업 중 하나를 자신이 참가할 track으로 선택하여 참여한다.
  • 수상은 track 별로 1,2,3등과 전체 우승으로 나뉜다. track 별 수상자는 기업의 관련자가 뽑으며, 전체 우승은 참가자들의 투표로 결정된다.

역할 및 결과

  • 우리팀 Hippy는 AWS game tech track을 선택하였고, 나는 프론트엔트 뷰 역할을 맡았다.
  • 좋은 팀원들과, 모두의 노력 끝에 track 1등을 차지하였다.

image-20210524174313618

뷰 화면 링크 (시연영상)

https://drive.google.com/file/d/10euUsveJwcJRgnblyzvXjSq3CRyzONDm/view?usp=sharing

배운점

  • 디자이너분들과 협력하는 경험을 쌓았다.
    • 디자이너분들은 우리가 보지 못하는 세세한 부분까지 캐치가 가능하며, 사소한 모양 하나하나에 디자인 컨셉이 담겨있어 그 부분을 사소하다고 무시하면 안된다는 것을 깨달았다.
    • 기술적으로 혹은 cost 문제로 구현이 힘든 부분들은, 같이 토의를 하며 문제를 해결해나가는 경험을 쌓았다.
  • lottie animation, figma, trello 등 툴들의 사용법을 익혔다.
  • 처음으로 MVP 모델을 적용하여 컨셉을 이해하였다.

———–이하 서비스 소개

서비스 소개

  • 보이스 채팅 기능이 제공되지 않는 게임에 보이스채팅 기능을 제공하는 웹 서비스.
  • 사용자는 웹 페이지에 접속 후, 플레이 하고자 하는 게임을 클릭 후, 닉네임을 입력하면 된다. 방에 입장 후 기다리면, 같은 게임 방에 들어온 사람들끼리 자동적으로 연결되도록 한다.

구현

  • 게임을 접속했는지 확인하고, 같은 팀원들끼리 묶는 데는 게임사에서 제공하는 API를 통해 구현하였다.
  • 이후 보이스채팅을 연결하는 것은, AWS Chime 을 사용하여 소켓통신을 통해 구현하였다.
This post is licensed under CC BY 4.0 by the author.

로티(lottie) 애니메이션 적용

1039 교환

Comments powered by Disqus.

diff --git a/posts/junction_seoul_2021_review/index.html b/posts/junction_seoul_2021_review/index.html new file mode 100644 index 000000000..68ddfb64b --- /dev/null +++ b/posts/junction_seoul_2021_review/index.html @@ -0,0 +1 @@ + JunctionXSeoul 2021 후기 | 디피의 개발일지
Posts JunctionXSeoul 2021 후기
Post
Cancel

JunctionXSeoul 2021 후기

결과

image-20210524174313618

AWS game tech 트랙 우승!

후기

해커톤 첫 참여였지만 좋은 팀원들과 함께해서 트랙 우승을 이루어내 기뻤다.

front-end 참여하여 메인 기능과는 보다는 디자이너님이 만들어주신 뷰 구현에 힘을 쏟았다.

배운 것도 많았고, 더 공부할 것도 많이 보였다.

  • 배운 것

    • tool 관련
      • trello 기본 사용법
      • figma 사용법
      • slack 을 통한 협업
      • lottie animation, json을 이용한 색상 및 동작 변형
    • front-end 라이브러리
      • emotion 기초 사용법
      • redux-saga
      • react-helmet
    • front-end 모델
      • MVP 모델 기초
    • css
      • gradation, gif 삽입
  • 더 공부할 것
    • front-end
      • redux-saga
      • emotion
      • MVP 모델
  • 고칠점
    • 피곤해도 그것을 표정에 드러내지 않기
      • 2일째 새벽에 밤을 새느라 상당히 피곤하였는데, 그것이 표정에 드러났는지 디자이너분이 변경을 요청할때 미안해하셨다. 그때문에 더 좋은 작품을 만들 수 있는 기회를 잃었을 수도 있다.
    • 아직 잘 모르는 것에 대해서 할 수 있는지 없는지 그 자리에서 밝히지 말자.
      • 요구사항을 구현할 수 있을지 없을지 최소한의 구글링도 하지 않고 대답하지 말자.
      • 난 이제까지 확실한 의사표현이 좋다고 생각하여 잘 모르는 건 대체로 할 수 없다고 하였으나, 만약 그리 급하지 않다면 한번 찾아보고 말씀드리겠다고 대답하자.

첫 해커톤이라 참여하기 전엔 긴장도 많이 됐고, 부담도 느꼈지만 좋은 결과를 얻은 덕분인지 해커톤의 즐거움을 알게 되었다.

다음에는 backend로 참여하여 좋은 성과를 얻고 싶다.

This post is licensed under CC BY 4.0 by the author.

16928 뱀과 사다리게임

CSS position 에 관하여

Comments powered by Disqus.

diff --git a/posts/lottie_animation/index.html b/posts/lottie_animation/index.html new file mode 100644 index 000000000..0d7a3bb02 --- /dev/null +++ b/posts/lottie_animation/index.html @@ -0,0 +1,21 @@ + 로티(lottie) 애니메이션 적용 | 디피의 개발일지
Posts 로티(lottie) 애니메이션 적용
Post
Cancel

로티(lottie) 애니메이션 적용

lottie 애니메이션

Junction 해커톤을 하며 lottie 애니메이션을 접하게 되었다.

간단히 코드로 불러올 수 있으며, json 파일로 불러올 경우 색상 변경 등의 커스터마이징도 가능하여 활용도가 높다.

불러오는 법(react 기준)

  1. npm install @lottiefiles/react-lottie-player

  2. 다음 코드 삽입

    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +
    <Player
    +  src={fireVideo}
    +  background="transparent"
    +  speed="1"
    +  style=
    +  renderer="svg"
    +  loop
    +  controls
    +  autoplay
    +></Player>
    +

    이때 src 에는 로컬에 있는 json 파일 또는 링크가 들얼 수 있다.

색상 커스터마이징 방법

  1. https://lottiefiles.com/tools/json-editor 에 접속하여 원하는 로티 애니메이션의 json 파일을 붙여넣는다.

  2. Toggle source/editor 를 클릭한다.

  3. ADBE Vector Graphic - Stroke 를 검색하고, 같은 객체에 들어있는 k 배열을 찾는다. image-20210524174313618
  4. 0~2번째 원소는 각각 RGB 값을 담당한다. 계산방법은 다음과 같다

    #123456 코드가 있으면, 이것을 앞에서부터 2개씩 끊고 다음과 같이 계산

    12 -> 1 * 16 + 2 -> 18 / 255 = 0.07058823529

    34 -> 3 * 16 + 4 -> 52 / 255 = 0.20392156862

    56 -> 5 * 16 + 6 -> 86 / 255 = 0.33725490196

  5. 위에서 계산한 코드를 앞에서부터 차례대로 넣으면 색상 커스터마이징 완료
This post is licensed under CC BY 4.0 by the author.
diff --git a/posts/network/index.html b/posts/network/index.html new file mode 100644 index 000000000..510ad10ed --- /dev/null +++ b/posts/network/index.html @@ -0,0 +1 @@ + network 개요 | 디피의 개발일지
Posts network 개요
Post
Cancel

network 개요

HTTP의 GET과 POST 비교

GET

  • 데이터가 HTTP Request Message의 헤더부분의 url에 담겨서 전송됨. url의 끝에 ? 뒤에 데이터를 붙여 요청.
  • 데이터의 크기가 제한적임.
  • 데이터가 url에 노출되므로 보안에 약함

POST

  • 데이터가 HTTP Requst Message의 바디부분에 담김
  • 데이터 크기가 커도 됨. GET 방식보다 보안적임.

어디에 무엇을 적용할까?

  • 서버에서 데이터를 가져와서 보여줄때
    • GET 방식이 적합함
  • 서버의 값이나 상태 등을 변경할때
    • POST 방식이 적합함.
  • Caching이 필요할때
    • GET 방식이 적합함. GET 방식의 요청은 브라우저에서 Caching 할 수 있기때문에.
  • Caching이 이뤄지면 안될때
    • POST 방식이 적합

TCP 3-way Handshake

SYN 패킷, ACK 패킷

SYN : Synchronize sequence number

ACK : acknowledgement

TCP 헤더에는 Code bit(flag bit)가 존재함. 이부분을 총 6비트로 이루어져있고, 각 bit는 Urg-ACK-PSH-RST-SYN-FIN 순서로 의미를 가지고 있다. 해당 위치의 비트로 패킷의 의미를 나타내는 것이다.

연결 성립

image-20220124010027316

  1. 클라이언트가 서버에 접속을 요청하는 SYN(a) 패킷을 보냄
  2. 서버는 SYN(a)를 받고, 요청을 수락한다는 ACK(a+1)와 SYN(b)이 설정된 패킷을 발송함
  3. 클라이언트는 ACK(a+1)와 SYN(b) 패킷을 받고, ACK(b+1)를 서버로 보내면 연결이 성립됨.

연결 해제

image-20220124010203287

  1. 클라이언트가 연결을 종료하겠다는 FIN 플래그를 전송함
  2. 서버는 FIN을 받고, 알았다는 ACK 를 보냄.
  3. 서버는 남은 데이터를 모두 보내고, 다 보냈으면 FIN 플래그를 클라이언트로 전송함
  4. 클라이언트는 FIN 플래그를 받고, 알았다는 ACK를 보냄
  5. 서버는 ACK를 받고 소켓을 close함.
  6. 클라이언트는 아직 서버로부터 받지 못한 데이터가 있을 수도 있으니, 일정시간동알 세션을 남겨놓고 잉여패킷을 기다림

3way인 이유

TCP 연결은 양방향성이므로, 서로의 연결이 제대로 됐는지 확인해야한다.

따라서 1. 클라이언트가 서버로 요청함. 2. 서버가 확인하고 클라이언트로 요청함. 3. 클라이언트가 확인함.

이렇게 3 과정이 필요하다.

why randomized Sequence number

처음 클라이언트에서 SYN 패킷을 보낼때 Sequence number에는 랜덤한 숫자가 담겨짐. 초기 sequence number를 ISN이라고 함.

이때 랜덤하게 초기 ISN을 설정하는 이유는 클라이언트와 서버가 연결을 맺을때 사용하는 포트는 유한 범위 내에서 사용하고 시간이 지남에 따라 재사용되기때문이다. 따라서 두 통신 호스트가 과거에 사용된 포트번호 쌍을 사용하는 가능성이 존재한다. 서버측은 패킷의 SYN을 보고 패킷을 구분하게 되는데 난수가 아닌 순차적은 number가 전송된다면 이전의 connection으로부터 오는 패킷으로 인식할 수 있다. 이러한 문제가 발생할 가능성을 줄이기 위해 난수로 ISN을 설정하는 것이다.

TCP와 UDP의 비교

UDP(User Datagram Protocol)

  • 비연결형 프로토콜.
  • IP 데이터그램을 캡슐화하여 보내는 방법과 연결설정을 하지 않고 보내는 방법을 제공함.
  • 흐름제어, 오류제어, 손상된 세그먼트 재전송을 하지 않음. 포트를 사용하여 IP 프로토콜에 인터페이스를 제공하는 작업만 함. 클라이언트는 요청/응답이 손실되면 timeout되고 다시 시도하면 된다.

  • 코드가 간단하며 TCP 처럼 초기설정이 요구되지 않아 적은 메시지로 통신이 가능하다.
  • 사용 하는 곳 : DNS
    • 클라이언트는 호스트네임이 포함된 UDP 패킷을 DNS로 보내고, DNS는 해당하는 IP주소를 포함한 UDP 패킷을 응답함.
    • 사전에 설정이 필요없고, 이후 해제도 필요없다.

TCP(Transmission Control Protocol)

  • 연결형 프로토콜
  • 신뢰성과 순차적인 전달을 보장함.
  • 송신자와 수진자 모두가 소켓이라고 부르는 종단점을 생성하여 연결된다.
  • TCP에서의 연결은 3-way-handshake를 통해 이루어진다.
  • TCP 연결 종류
    • 전이중(full-duplex) : 전송이 양방향으로 동시에 일어날 수 있음을 의미
    • 점대점(point to point) : 각 연결이 정확히 2개의 종단점을 가지고 있음을 의미.
  • 멀티 캐스팅이나 브로드캐스팅 지원은 안함.

HTTP와 HTTPS

HTTP의 문제점

  • HTTP는 평문 통신(암호화안된 통신)이기 때문에 도청이 가능하다.
  • 통신 상대를 확인하지 않기때문에 위장이 가능하다.
  • 완전성을 증명할 수 없기 때문에 변조가 가능하다.

TCP/IP 는 도청가능한 네트워크이다

TCP/IP 구조의 통신은 통신경로 상에서 엿볼 수 있다. 중간에 패킷을 수집하면 된다. 이때 HTTP는 평문통신이기에 패킷이 유출되면 메시지의 의미를 파악할 수 있고, 이때 도청이 이루어진다.

보완

  1. 통신 자체를 암호화한다.
    • SSL(Secure Socket Layer)TLS(Transport Layer Security)라는 다른 프로토콜을 조합함으로써 HTTP 통신 자체를 암호화할 수 있다. 여기서 특히 SSL을 조합한 HTTP를 HTTPS 라고 부른다.
  2. 콘텐츠를 암호화
    • HTTP 메세지에 포함된 컨텐츠만 암호화하는 방법
    • 서로 간의 해독방법이 필요하다.

통신 상대를 확인하지 않기때문에 위장이 가능함

HTTP에 의한 통신에는 상대가 누구인지 확인하는 처리가 없기때문에, 누구든지 요청을 보낼 수 있다. 따라서 IP주소나 포트번호의 제한이 없는한 요청이 오면 서버는 무엇이든 응답을 반환한다. 여기서 문제가 발생한다.

  1. 요청을 보낸 곳의 웹서버가 원래 의도한 응답을 보내야하는 웹서버인지 확인할 수 없음
  2. 응답을 반환한 곳의 클라이언트가 원래 의도한 요청을 보낸 클라이언트인지 확인할 수 없음
  3. 통신하고 있는 상대가 접근이 허가 된 상대인지 확인 불가능
  4. 어디에서 누가 요청했는지 확인할 수 없음
  5. 의밍벗는 요청도 수신함 -> DDos 공격에 취약

보완

SSL로 해결할 수 있다. SSL은 상대를 확인하는 수단으로 증명서를 제공하고 있다. 증명서는 신뢰가능한 제 3자가 발행하는 것이기에 서버나 클라이언트가 실재하는 사실을 증명한다. 이 증명서를 이용함으로써 통신 상대가 내가 통신하고자하는 서버임을 나타내고, 이용자는 개인정보 누설의 위험성이 줄어든다. 또 클라이언트는 이 증명서로 본인확인을 하고, 웹사이트 인증에서도 이용할 수 있다.

완전성을 증명할 수 없기 때문에 변조가 가능하다

송신측에서 보낸 내용과 수신측에서 받은 내용이 일치하다는 것을 보장할 수 없다. 따라서 누가 중간에서 변조하더라도 이사실을 알 수 없다. 이와 같이 중간에서 요청/응답을 빼앗아 변조하는 것을 중간자 공격이라고 한다.

보완

MD5, SHA-1 등의 해시 값을 확인하는 방법과 디지털 서명을 확인하는 방법이 존재하지만 확실하지는 않음

SSL 에는 인증이나 암호화, 다이제스트 기능이 제공되기에, 확실히 방지된다.

HTTPS

HTTP에 SSL을 더한 것으로, HTTP 통신하는 소켓부분을 SSL이나 TLS라는 프로토콜로 대체하는 것뿐이다. HTTP는 원래 TCP와 직접 통신했지만, HTTPS 에는 SSL이 TCP와 통신하게 된다.

HTTPS는 SSL을 사용하여 암호화와 증명서, 안전성 보호를 이용할 수 있게 된다.

SSL에서는 공통키 암호화 방식과 공개키 암호화 방식을 혼합한 암호 시스템을 사용한다. 공통키를 공개키 암호화 방식으로 교환한 다음에 다음부터는 공통키 암호를 사용하는 방식이다.

모든 웹 사이트에서 HTTPS를 사용해야할까?

HTTPS 통신은 암호화 통신이기에 HTTP보다 많은 리소스를 요구한다. 하지만 최근 하드웨어의 발달로 속도저하가 거의 일어나지 않으며, HTTP 2.0을 사용한다면 오히려 HTTPS가 HTTP보다 빠르게 동작한다.

따라서 최근에는 모든 웹페이지에서 HTTPS를 적용하는 방향으로 바뀌어가고 있다.

DNS round robin 방식

DNS Round Robin

  • DNS에서 레코드 정보를 조회하는 시점에서 트래픽을 분산하는 방법
  • 부하 분산을 위한 서버를 여러대 두고, 각 서버마다의 공인 IP 주소를 가진다. 그리고 사용자가 도메인으로 접속하면 DNS에서는 하나의 공인 IP를 반환하여 분산하는 방법이다.
  • 라운드로빈 DNS를 통해 반환된 공인 IP 리스트를 처리하는 표준 절차가 정해져있지 않기 때문에 여러 단점이 존재한다.

DNS Round Robin 방식의 문제점

  1. 서버의 수만큼 공인 IP 주소가 필요함

    • 부하 분산을 위해 서버의 대수를 늘리기 위해서는 그만큼의 공인 IP가 필요하다.
  2. 균등하게 분산되지 않음

    • 모바일 접속에서 특히 문제가 될 수 있는데, 모바일 접속은 캐리어 게이트 웨이라는 프록시 서버를 경유한다. 프록시 서버에서는 이름 변환결과가 일정시간동안 캐싱되므로 같은 프록시 서버를 경유하는 접속은 항상 같은 서버로 접속된다.
    • PC 웹 브라우저에서도 DNS 질의 결과를 캐싱하기 때문에 균등하게 부하분산 되지 않는다.
    • DNS 레코드의 TTL을 짧게 설정함으로써 어느정도 해소가 되지만, TTL에 따라 캐시를 해제하는 것은 아니므로 반드시 주의가 필요하다.
  3. 서버가 다운되도 확인불가

    • DNS 서버는 웹 서버의 부하나 접속 수 등의 상황에 따라 질의결과를 제어할 수 없다. 따라서 서버가 다운된 상황에서도 이를 검출하지 못하고 유저에게 제공한다.
    • 따라서 무중단 서비스에서는 라운드로빈 DNS를 사용하면 안된다.

따라서 DNS Round Robin은 부하분산을 위한 방법이지 다중화 방법은 아니므로 다른 방법과 조합해서 관리할 필요가 있다.

Round Robin 방식을 기반으로 단점을 해소하는 DNS 스케쥴링 알고리즘들

  1. Weighted Round Robin(WRR)
    • 각각의 웹 서버에 가중치를 가미해서 분산 비율을 변경한다.
    • 가중치가 큰 서버일수록 빈번하게 선택되므로 처리능력이 높은 서버는 가중치를 높게 설정하는 것이 좋다.
  2. Least Connection
    • 접속 클라이언트 수가 가장 적은 서버를 선택한다.
    • 로드밸런서에서 실시간으로 connection 수를 관리하거나 각 서버에서 주기적으로 알려주는 것이 필요하다.

웹 통신의 큰 흐름

사용자가 브라우저에서 특정 URL를 입력했을때 일어나는 일들.

In 브라우저

  1. url에 입력된 값을 브라우저 내부에서 결정된 규칙에 따라 그 의미를 조사한다.
  2. 조사된 의미에 따라 HTTP Request 메시지를 만든다.
  3. 만들어진 메시지를 웹 서버로 전송한다.
    • 이때 브라우저는 메시지를 네트워크에 송출하는 기능이 없으므로, OS에 의뢰하여 메시지를 전달한다.
    • 단, OS에 송신을 의뢰할 때는 도메인명이 아니라, ip주소로 메시지를 받을 대상을 지정해야하는데 이때 DNS 서버를 조회해야한다.

In 프로토콜 스택, LAN 어댑터

  1. 프로토콜 스택(OS에 내장된 네트워크 제어용 소프트웨어)이 브라우저로부터 메시지를 받는다.
  2. 브라우저로부터 받은 메시지를 패킷 속에 저장한다.
  3. 패킷에 수신처 주소 등의 제어정보를 덧붙인다.
  4. 패킷을 LAN 어댑터에 넘긴다.
  5. LAN 어댑터는 다음 Hop의 MAC 주소를 붙인 프레임을 전기신호로 변환시킨다.
  6. 신호를 LAN 케이블로 송출시킨다.

프로토콜 스택은 통신 중 오류가 발생했을때, 이 제어정보를 사용하여 고쳐보내거나, 각종 상황을 조절하는 등 다양한 역할을 하게 된다.

In 허브, 스위치, 라우터

  1. LAN 어댑터가 송신한 프레임은 스위칭 허브를 경유하여 인터넷 접속용 라우터에 도착한다.
  2. 라우터는 패킷을 프로바이더(통신사)에게 전달한다.
  3. 인터넷으로 들어가게 된다.

In 액세스 회선, 프로바이더

  1. 패킷은 인터넷 입구에 있는 액세스 회선(통신회선)에 의해 POP(Point of Presence, 통신사용 라우터)까지 운반된다.
  2. POP을 거쳐 인터넷의 핵심부로 들어가게 된다.
  3. 수많은 고속 라우터 사이로 패킷이 목적지를 향해 흘러가게 된다.

In 방화벽, 캐시서버

  1. 패킷은 인터넷 핵심부를 통과하여 웹 서버측의 LAN에 도착한다.
  2. 방화벽이 도착한 패킷을 검사한다.
  3. 패킷이 웹 서버까지 가야하는지 가지 않아도 되는지를 판단하는 캐시서버가 존재한다.
    • 서버까지 가지 않아도 될 경우, 자기 내부에 있는 캐싱 된 데이터를 바로 보낸다.

In 웹서버

  1. 패킷이 물리적인 웹 서버에 도착하면, 웹 서버의 프로토콜 스택은 패킷을 추출하여 메시지를 복원하고, 웹 서버 애플리케이션에 넘긴다.
  2. 메시지를 받은 웹 서버 어플리케이션은 요청 메시지에 따른 데이터를 응답 메시지에 넣어 클라이언트로 회송한다.
  3. 왔던 방식대로 클라이언트로 돌아간다.

출처

https://github.com/JaeYeopHan/Interview_Question_for_Beginner/tree/master/Network

https://asfirstalways.tistory.com/356

https://judo0179.tistory.com/127

This post is licensed under CC BY 4.0 by the author.
diff --git a/posts/next-caching/index.html b/posts/next-caching/index.html new file mode 100644 index 000000000..0049fb4e2 --- /dev/null +++ b/posts/next-caching/index.html @@ -0,0 +1,45 @@ + next.js caching | 디피의 개발일지
Posts next.js caching
Post
Cancel

next.js caching

next.js app router의 caching에 관해 공부한 내용입니다.

Overview

next.js 에서 제공하는 caching mechanism에는 다음과 같은 것들이 있다.

MechanismWhatWherePurposeDuration
Request MemoizationReturn values of functionsServerRe-use data in a React Component treePer-request lifecycle
Data CacheDataServerStore data across user requests and deploymentsPersistent (can be revalidated)
Full Route CacheHTML and RSC payloadServerReduce rendering cost and improve performancePersistent (can be revalidated)
Router CacheRSC PayloadClientReduce server requests on navigationUser session or time-based

next.js는 가능한 많은 캐싱을 적용하려고 하고, 따라서 따로 opt-out하지 않는한 정적으로 렌더링되고, 요청은 캐싱된다. 각 mechanism은 다음 도식처럼 동작한다.

Diagram showing the default caching behavior in Next.js for the four mechanisms, with HIT, MISS and SET at build time and when a route is first visited.


Request Memoization

React에서 확장한 fetch API가 자동으로 같은 URL과 옵션으로 보내진 요청의 결과를 기억한다. 따라서 한 route를 렌더링할때 같은 요청이 여러번 발생하면, 딱 한번만 요청을 보내고 그 결과를 여러 곳에서 사용한다.

  • Duration : React Component tree의 렌더링이 종료될때까지만 유지. 렌더링 종료시 초기화됨.
  • Revalidating : server requests 사이에서 공유되지않으므로 revalidating할 이유가 없음
  • Opting-out : fetch API에 AbortController signal을 줄 수 있음. 예시
  • 기타
    • next.js가 아닌 React 기능임
    • fetch 함수의 GET method에서만 동작함
    • React Component tree에서만 동작하므로, route handler에서는 동작하지 않음.
    • fetch를 쓸 수 없는 환경에서는 React cache function을 사용할 수 있음.


Data Cache

여러 server requests와 deployments 사이에서 공유되는 캐시로, next.js에서 확장한 fetch API를 사용하면 적용된다. fetch APIcache, next.revalidate 옵션으로 설정할 수 있으며 자세한 내용은 fetch API references에서 볼 수 있다.

  • Duration : 따로 revlidate하거나 opt-out하지 않는한 계속 유지된다.
  • Revalidating
    • Time-based Revalidation : 특정한 시간마다 revalidate 할 수 있음.
      • 동작방식 : { next: { revalidate: 60 } } 일 때,
        • 최초 요청 시 : data source로 API 요청 후, 결과를 캐싱함.
        • 60초 이전 재요청 : 캐시된 데이터 리턴
        • 60초 이후 재요청 : 캐시된 데이터(stale 상태) 리턴, 새로운 데이터 fetch 후 결과 캐싱. 요청이 실패할 경우 이전 데이터(stale)를 계속 유지함.
    • On-demand Revalidation : 원할때 revalidate 할 수 있음.
      • 동작방식
        • 최초 요청 시 : data source로 API 요청 후, 결과를 캐싱함.
        • on-demand revalidation 시 : 캐시된 데이터를 PURGE 시킴
        • 재요청 시 : data source로 API 요청 후, 결과를 캐싱함.
  • Oping-out
    • fetch API 옵션 : { cache: 'no-store' }와 같이 fetch API 옵션으로 Data Cache를 사용하지 않을 수 있음.
    • Route Segment Config options : 특정 route segment를 기준으로 opt-out 할 수 있음. 해당 route segment에 포함된 모든 data request에 적용된다.
      • export const dynamic = 'force-dynamic'
      • export const revalidate = 0
  • 기타
    • { cache : 'no-store'}와 같이 캐시를 사용하지 않도록 설정해도 Request Memoization은 일어난다. (리액트 동작이기에)


Full Route Cache

next.js는 빌드는 각 route를 렌더링하고 결과를 캐싱한다. 따라서 매 요청마다 렌더링하지 않고 캐시된 route를 응답하여 페이지 로딩을 빠르게 마칠 수 있게한다.

동작방식

  1. React Rendering on the Server
    • next.js는 React API를 사용하여 렌더링을 오케스트레이션한다. 이때 각 route segments와 Suspense baoundaries를 chunks로 나눈다.
    • 각 chunk는 두 단계로 렌더링된다.
      1. React가 Server Component를 React Server Component Payload라는 스트리밍에 최적화된 특수한 데이터 포맷으로 렌더한다.
      2. next.js는 React Server Component Payload와 Client Component JS instruction을 사용하여 HTML을 서버에서 렌더링한다.
    • 이로써 작업을 캐싱하거나 응답을 보내기 전에 모든 것이 렌더링될 때까지 기다리지 않고, 작업 완료 시 응답을 스트리밍 할 수 있다.
    • 참고) React Server Component Payload
      • 렌더된 React Server Components tree의 압축 이진 표현. 클라이언트에서 browser’s DOM을 업데이트하기 위해 사용된다.
      • 아래 것들을 포함
        • Server Components의 렌더링 결과
        • Client Component가 렌더되고 참조될 위치에 대한 placeholder
        • Server Component에서 Client Component로 내려줄 props
  2. Next.js Caching on the Server (Full Route Server)
    • 기본적으로 next.js는 렌더링 결과 (React Server Component Payload, HTML)을 캐싱한다. 빌드타임 또는 revalidation 시 생성되는 결과 캐싱
  3. React Hydration and Reconciliation on the Client : 요청 시 클라이언트에서의 동작
    1. HTML 렌더링. 상호작용 불가능
    2. React Server Component Payload를 사용하여 Client와 렌더된 Server Component tree를 조정하고 DOM을 업데이트한다.
    3. JS instruction으로 Client Components를 hydration하고 상호작용을 가능하게 한다.
  4. Next.js Caching on the Client (Router Cache)
    • React Server Component Payload는 클라이언트에 Router Cache로서 저장된다.
    • Router Cache는 navigation 시 이전 방문했던 페이지를 빠르게 렌더링하거나, 다음 route를 prefetching한다.
  5. Subsequent Navigations
    • prefetching이나 subsequent navigation 도중 next.js는 React Server Component Payload가 Router Cache에 있는지 검사하고, 있으면 새로운 요청을 server로 보내지 않는다.

Static and Dynamic Rendering

route가 빌드타임에 캐시 되는지 안되는지는 그 route가 정적인지 동적으로 렌더링되는지에 달려있다. Static route는 기본적으로 캐시되며, dynamic route는 요청시 렌더링되고 캐시되지 않는다.

기타

  • Duration : 기본적으로 영구적이며 여러 request에서 재사용된다.
  • Invalidations
    • Revalidating Data : Data Cache를 revalidaing하면 컴포넌트를 서버에서 리렌더링하고, 새로운 렌더링 결과를 캐싱하여 Router Cache도 invalidation 된다.
    • Redeploying : Data cache와는 달리, Full Route Cache는 deployments 사이에서 유지되지 않는다.
  • Opting out
    • Dynamic Function 사용 시 Full Route Cache는 opt-out 되고 요청시 렌더링된다. Data Cache는 유지된다.
    • dynamic = 'force-dynamic', revalidate=0 route segment config 옵션 사용
      • Full Route Cache, Data Cache 모두 opting-out 한다.
      • Router Cache는 클라이언트 동작이므로 유지된다.
    • Opting out Data Cache : fetch API를 캐시하지 않으면 Full Route Cache도 opt-out 된다. 매번 새로운 요청을 보내기 때문이다.


Router Cache

next.js가 클라이언트 사이드에서 가지고 있는 in-memory 캐시이다. React Server Component Payload를 저장하며 각 route segments 별로 분리되어있다. 유저가 routes 사이를 이동할 떄마다 prefetch되거나 방문한 route segment를 저장한다. browser의 bfcache와는 다른 것이지만 비슷한 결과를 낸다.

  • Duration : 브라우저 임시 메모리에 저장되며 다음 두 가지 요인에 의해 duration이 결정된다.
    • Session : Navigation 사이에서 유지되지만, 새로고침 시 사라진다.
    • Automatic Invalidation Period : 개별 segment의 캐시는 특정 시간 이후에 invalidation 된다.
      • 동적 렌더링 결과물 : 30초
      • 정적 렌더링 결과물 : 5분
      • prefetching 된 동적 렌더링 : 5분까지 저장하도록 설정가능
  • Invalidation
    • Server Action에서
    • On-demand revalidaing 사용 : revalidatePath, revalidateTag
    • cookies.set, cookies.delete 사용 (쿠키를 사용하는 route가 stale 되는 것들 방지하기 위해)
    • 클라이언트에서 : router.refresh를 사용하면 Router Cache가 전부 clear 됨
  • Opting out : Router Cache는 opt-out이 불가능하다. prefetch의 경우는 <Link> 컴포넌트에서 props로 opt-out할 수 있지만 방문한 페이지에 대한 캐시는 여전히 남는다.


Cache Interactions

Data Cache and Full Route Cache

  • Data Cache를 opt-out하거나 revalidating 하면 Full Route Cache도 마찬가지로 적용된다.
  • Full Route Cache를 opt-out 한다고 Data Cache는 opt-out 되지 않는다. 한 route 안에 Data Cache가 적용된 요청과 opt-out 된 요청이 공존할 수 있다.

Data Cache and Client-side Router Cache

  • Route Handler에서 Data Cache를 revalidating하여도 Router Cache가 바로 invalidate 되지 않는다. Router Handler는 특정 route와 묶여있지 않기 때문이다. Router Cache는 유저가 새로고침하거나 Automatic Invalidation Period가 지날때까지 유지된다.
  • 즉각적으로 Router Cache를 무효화시키고 싶으면, Server Action에서 revalidatePath, revalidateTag를 사용할 수 있다.


APIs

각 next.js API가 영향을 끼치는 캐시정리

APIRouter CacheFull Route CacheData CacheReact Cache
<Link prefetch>Cache   
router.prefetchCache   
router.refreshRevalidate   
fetch  CacheCache
fetch options.cache  Cache or Opt out 
fetch options.next.revalidate RevalidateRevalidate 
fetch options.next.tags CacheCache 
revalidateTag RevalidateRevalidate 
revalidatePath RevalidateRevalidate 
const revalidate Revalidate or Opt outRevalidate or Opt out 
const dynamic Cache or Opt outCache or Opt out 
cookiesRevalidateOpt out  
headers, useSearchParams, searchParams Opt out  
generateStaticParams Cache  
React.cache   Cache
unstable_cache (Coming Soon)    


현재 존재하는 이슈

route segment config로 opt-out해도 캐시가 적용되는 이슈

현재 공식문서에 따르면 다음 두 설정은 Data Cache과 Full Route Cache를 opt-out 한다고 한다.

  • const dynamic = 'force-dynamic'
  • const revalidate = 0

하지만 다음과 같은 이슈를 찾을 수 있었다.

revalidate = 0을 주어도 캐시가 적용된다는 이슈이다. 해결방법으로는 다음 두가지가 제시되었지만, 공식적인 답변은 없었다.

  • 데이터 변경 후 router.refresh()
  • <Link> 대신 <a> 사용

route segment config와 fetch API의 revalidate 설정 충돌 문제

next.js 공식문서에 따르면, revalidate 속성은 한 route에서 가장 낮은 값으로 설정된다고 한다. 그리고 위 설명에서 revalidate = 0은 data cache를 opt-out 한다고 하였으니 route segment config로 revalidate =0을 설정하면, fetch API에서 revalidate : 60을 설정해도 data cache가 무시될 줄 알았다.

하지만 실제로 다음과 같은 코드로 적용해본 결과 data cache가 opt-out 되지 않았다. 캐시가 계속 이루어지고 있었다.

  • 코드
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+
// app/layout.js
+export const dynamic = "force-dynamic"
+
+export default function Layout({ children }) {
+   return (
+      <div>
+         {children}
+      </div>
+   )
+}
+
1
+2
+3
+4
+5
+6
+7
+8
+9
+
// app/post/page.js
+export default function Page() {
+   const posts = await fetch("http://localhost:8080/api/post", { next : {revalidate:60} });  
+   return (
+      <div>
+         {posts.map(p => <div>{p.content}</div>)}
+      </div>
+   )
+}
+
  • next.js 콘솔 결과물
1
+2
+3
+
 GET /post 200 in 281ms
+ │
+ ├──── GET http://localhost:8080/api/post 200 in 7ms (cache: HIT)
+

이것이 next.js의 의도된 동작인지, 이슈인지는 좀 더 살펴봐야할 것 같다.

This post is licensed under CC BY 4.0 by the author.

next.js props drilling

next.js 최적화과정

Comments powered by Disqus.

diff --git a/posts/next-props-drilling/index.html b/posts/next-props-drilling/index.html new file mode 100644 index 000000000..78af23acb --- /dev/null +++ b/posts/next-props-drilling/index.html @@ -0,0 +1,567 @@ + next.js props drilling | 디피의 개발일지
Posts next.js props drilling
Post
Cancel

next.js props drilling

Data sharing (Solving props drilling)

next.js 13.4 이상 App router 기준으로 작성하였습니다.

props drilling 예시

next.js에서 주로 발생하는 props drilling 형태는 다음과 같습니다.

  • 먼저 필요한 데이터를 서버컴포넌트에서 fetch 합니다. API 노출 방지, 최적화 등 다양한 이유로 서버 컴포넌트(서버 사이드)에서 API를 호출합니다.
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
// Server Component A
+import ServerComponentB from "./ServerComponentB"
+
+export default async function ServerComponentA() {
+  const initialCount = await fetch(...);
+  
+  return <>
+     <h3>I got {initialCount}</h3>
+     <ServerComponentB initialCount={initialCount} />
+  </>
+}
+
  • 데이터를 fetch한 서버컴포넌트에서 하위 서버컴포넌트로 데이터를 넘겨줍니다. ServerComponentB에서는 initialCount가 필요없지만, 하위 컴포넌트에서 필요하기에 받고, 넘겨줍니다.
1
+2
+3
+4
+5
+6
+
// Server Component B
+import ServerComponentC from "./ServerComponentC"
+
+export default function ServerComponentB({ initialCount }) {
+  return <ServerComponentC initialCount={initialCount}/>
+}
+
  • ServerComponentC에서는 initialCount가 필요합니다. 필요한 작업을 수행 후 하위 컴포넌트를 불러옵니다. 그리고 그 하위 컴포넌트에도 initialCount가 필요하기에 props로 받은 데이터를 넘겨줍니다.
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
// Server Component C
+import ClientComponent from "./ClientComponent"
+
+export default function ServerComponentC({ initialCount }) {
+  return (
+    <div>
+      <h3>initial count is : {initialCount}</h3>
+    	<ClientComponent initialCount={initialCount}/>
+  	</div>
+  )
+}
+
  • 최종적으로 최하위 컴포넌트에 도달하였습니다.
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+
// Client Component
+'use client'
+
+import {useState} from "react"
+
+export default function ClientComponent({initialCount}) {
+  const [count, setCount] = useState(initialCount);
+  
+  return (
+    <>
+      {count}
+      <button onClick={() => setCount(prev => prev + 1)}>+1</button>
+      <button onClick={() => setCount(prev => prev - 1)}>-1</button>
+    </>
+  )
+}
+

이처럼 상위 컴포넌트에 있는 데이터를 하위 컴포넌트에서도 가져야하고 이것을 props로 넘겨줄때, props drilling이 발생합니다. 중간에 ServerComponentB에서는 initialCount가 필요없었지만 하위 컴포넌트에서 필요하기에 받고 넘겨줘야했습니다.

만약 일반적인 React 앱처럼 클라이언트 컴포넌트로만 구성되어있다면 글로벌 상태관리 라이브러리(redux, recoil..)나, Context API를 사용할 수 있습니다. 하지만 서버컴포넌트에서는 상태를 사용할 수 없기에 위와 같은 방법은 사용하기 어렵습니다.

여기서는 서버컴포넌트가 있는 구조에서 props drilling 문제 없이 데이터를 공유할 수 있는 방법을 작성하였습니다.


Server Component -> Server Component

next.js에서 기본으로 제공해주는 기능인 Automatic fetch() Requst Deduping을 사용할 수 있습니다. next.js 공식문서에서의 설명에 따르면, 서버에서 fetchGET한 데이터는 rendering pass 동안 캐시된다고 합니다. 그리고 이 캐시는 server request 생명주기동안 유지되며 렌더링 과정이 끝날때까지 지속된다고 합니다.

따라서 서버컴포넌트끼리는 별도의 data sharing 기법이 필요하지 않고, 매번 필요한 데이터를 불러오면 됩니다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
// Server Component A
+import ServerComponentB from "./ServerComponentB"
+
+export default async function ServerComponentA() {
+  const initialCount = await fetch(...);
+  
+  return <>
+     <h3>I got {initialCount}</h3>
+     <ServerComponentB/>
+  </>
+}
+
1
+2
+3
+4
+5
+6
+
// Server Component B
+import ServerComponentC from "./ServerComponentC"
+
+export default function ServerComponentB() {
+  return <ServerComponentC/>
+}
+
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+
// Server Component C
+import ClientComponent from "./ClientComponent"
+
+export default function ServerComponentC() {
+  const initialCount = await fetch(...);
+  return (
+    <div>
+      <h3>initial count is : {initialCount}</h3>
+    	<ClientComponent initialCount={initialCount}/>
+  	</div>
+  )
+}
+

이제 ServerComponentB에서는 initialCount를 받을 필요도, 넘겨줄 필요도 없습니다. fetch() 동안 결과가 캐시되어있기 때문에 두번이상 API 요청을 보내지 않습니다.

한계점

저는 개발을 하면서 서버컴포넌트에서 API 요청을 보낼때 주로 searchParams에 들어온 값을 기준으로 API를 호출했습니다.

1
+2
+3
+4
+5
+6
+
// ex : 클라이언트 요청 주소 -> /notice?categoryId=4&searchValue=test
+// categoryId=4, searchValue=test 로 데이터를 받아옴
+export default async function NoticePage({ searchParams : {categoryId, searchValue } }) {
+  const notices = await getNotices({ categoryId, searchValue })
+  ...
+}
+

하지만 next.js app router에서 page.js가 아닌 서버컴포넌트에서 searchParams를 얻어올 수 있는 공식적으로 제공하는 방법은 없습니다. 따라서 위에서 설명한, 데이터가 필요한 서버컴포넌트에서 api를 요청하는 건 어렵습니다. API 요청에 필요한 searchParams를 얻을 수 없기 때문입니다.

1
+2
+3
+4
+5
+
// page.js가 아닌 어딘가의 서버컴포넌트
+export default function NoticeList() {
+  // error! categoryId, searvhValue를 알아낼 수 없음
+  const notices = await getNotices({ categoryId, searchValue })
+}
+

따라서 Server Component -> Server Component data sharing을 좀 더 잘 활용하려면, 하나의 Request가 살아있는 동안 여러 서버컴포넌트에서 그 Request의 searchParams에 접근가능해야합니다.

공식적인 방법은 아니나 몇가지 생각해본/찾아본 방법은 다음과 같습니다.

  1. 웹서버 활용

    next.js에서는 서버컴포넌트에 headers()라는 함수를 제공합니다. 요청이 온 Request의 header을 제공해주는 함수입니다. 이 함수는 꼭 page.js만이 아닌 곳에서도 사용가능하기에, header에 searchParams가 들어있다면 page.js가 아닌 서버컴포넌트에서도 searchParams에 접근 가능합니다.

    1
    +2
    +3
    +4
    +5
    +6
    +7
    +
    # nginx 예시
    +server {
    +    location / {
    +        proxy_set_header X-Search-Params $args;
    +        proxy_pass http://localhost:3000;
    +    }
    +}
    +
    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +
    import { headers } from 'next/headers'
    +   
    +export default function NoticeList() {
    +  const header = headers() 
    +  const { categoryId, searchValue } = parseSearchParams(header.get("X-Search-Params"))
    +     
    +  const notices = await getNotices({ categoryId, searchValue })
    +}
    +
  2. searchParams 정도는 props로 내려보내기

    page.js에서 searchParams가 필요한 서버컴포넌트까지 내려줘야하지만, searchParams 객체 하나만 내려주면 되기에 props drilling을 하나의 객체만 내려보내는 수준으로 유지할 수 있습니다.

    1
    +2
    +3
    +
    export default function NoticeList({searchParams : {categoryId, searchValue }}) {
    +  const notices = await getNotices({ categoryId, searchValue })
    +}
    +

    관련해서 next.js 깃허브 discussions에서 논의된 얘기가 있는데, searchParams에 대한 validation/parsing/error handling 없이 하위 컴포넌트로 searchParams를 넘겨줄 가능성은 적다는 댓글이 있었습니다. 가능성이 적은지는 모르겠으나, 확실히 파싱, 유효성 검증이 필요한 searchParams의 경우 page.js에서 먼저 받아 처리를 한 후, 하위 컴포넌트로 props로 넘겨주는게 더 좋은 방법으로 보입니다.

  3. 서버단에서 data sharing 툴 사용

    꼭 searchParams에 국한되지 않더라도, 서버컴포넌트들 사이에서 데이터 or context를 공유하면 좋은 경우가 많습니다. (i18n, theme 등). next.js discussions

    공식적으로 제공해주는 방법은 없지만, 서드파티 툴을 만드려는 시도는 존재합니다(server-only-context, react cache function 활용). 공식문서에서는 db와 같이 네이티브 JS 패턴을 사용하는 방법을 추천해주고 있습니다. 링크


Client Component -> Client Component

자주 있는 상황으로, 어떤 클라이언트 컴포넌트와 디렉터리 상 먼 곳에 있는 클라이언트 컴포넌트가 데이터 또는 상태를 공유해야하는 경우가 있을 수 있습니다. 이럴때는 일반적으로 리액트 앱을 개발할때 사용하는 전역 상태 관리 방법을 채택할 수 있습니다. next.js 공식문서에서도 Context API 및 서드파티 라이브러리에 대한 설명에 있습니다. 링크

예시로는 Context API를 사용하였습니다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+
// layout.tsx
+import { AppProvider } from '../lib/AppProvider';
+
+export default function RootLayout({ children }) {
+    return (
+        <html lang="en">
+            <body className={inter.className}>
+                <AppProvider>{children}</AppProvider>
+            </body>
+        </html>
+    );
+}
+
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+
// lib/AppProvider.tsx
+'use client';
+import React, { createContext, useState, useContext } from 'react';
+
+const AppContext = createContext();
+
+export const AppProvider = ({ children }) => {
+    const [state, setState] = useState(0);
+    const value = {
+        state,
+        setState,
+    };
+    return <AppContext.Provider value={value}>{children}</AppContext.Provider>;
+}
+export const useAppContext = () => useContext(AppContext);
+
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+
// page.tsx
+import Counter from '../components/counter';
+import ServerComp from '../components/server';
+
+export default function Home() {
+    return (
+        <main className={styles.main}>
+            <ServerComp />
+            <Counter />
+        </main>
+    );
+}
+
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+
// components/counter.tsx
+'use client';
+
+import { useAppContext } from '../lib/AppProvider';
+
+export default function Counter() {
+    const { state, setState } = useAppContext();
+
+    const increaseHandler = () => {
+        setState(state + 1);
+    };
+
+    const decreaseHandler = () => {
+        setState(state - 1);
+    };
+    // Use the state and setState as needed
+    return (
+        <>
+            <div>{state}</div>
+            <a href="#" onClick={increaseHandler}>
+                increase
+            </a>{' '}
+            &nbsp;
+            <a href="#" onClick={decreaseHandler}>
+                decrease
+            </a>
+        </>
+    );
+}
+
  • 오해 : Context Provider는 클라이언트 컴포넌트이고, 이것을 가장 상위에 두면 그 아래는 전부 클라이언트 번들에 포함될거라고 오해하는 경우가 있습니다. 그 이유는 next.js 공식문서에 아래와 같은 문구가 있기 때문인데요.

Once "use client" is defined in a file, all other modules imported into it, including child components, are considered part of the client bundle

https://nextjs.org/docs/getting-started/react-essentials#the-use-client-directive

클라이언트 컴포넌트의 자식 컴포넌트들은 클라이언트 번들로 간주된다는 내용입니다. 따라서 Context API Provider를 상위에 두면 그 하위 서버컴포넌트들도 모두 클라이언트 번들에 포함되어 서버컴포넌트를 사용하는 이유가 없어질것만 같습니다.

하지만 실험해본 결과, 클라이언트 번들에 포함되지 않았습니다. 이유는 ServerComponent가 ClientComponent(Context Provider)에 직접 import 되지 않고, props로 넘어갔기 때문입니다

next.js 공식 문서에 따르면 서버컴포넌트를 클라이언트 컴포넌트 밑에 두려면, import 하지 말고 prop(children)으로 넘겨줘야한다고 되어있습니다. 링크

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+
// Unsupported Pattern: Importing Server Components into Client Components
+
+'use client'
+ 
+// This pattern will **not** work!
+// You cannot import a Server Component into a Client Component.
+import ExampleServerComponent from './example-server-component'
+ 
+export default function ExampleClientComponent({
+  children,
+}: {
+  children: React.ReactNode
+}) {
+  const [count, setCount] = useState(0)
+ 
+  return (
+    <>
+      <button onClick={() => setCount(count + 1)}>{count}</button>
+      <ExampleServerComponent />
+    </>
+  )
+}
+
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+
// Recommended Pattern: Passing Server Components to Client Components as Props
+
+'use client'
+ 
+import { useState } from 'react'
+ 
+export default function ExampleClientComponent({
+  children,
+}: {
+  children: React.ReactNode
+}) {
+  const [count, setCount] = useState(0)
+ 
+  return (
+    <>
+      <button onClick={() => setCount(count + 1)}>{count}</button>
+
+      {children}
+    </>
+  )
+}
+

공식 문서에 설명되어 있는대로, import 대신 props(children)으로 넘겨주면 클라이언트 컴포넌트는 자식 컴포넌트를 위한 slot을 남기고, 서버컴포넌트 렌더가 종료되면 그 slot에 서버 컴포넌트가 렌더링 됩니다. 이 방식대로 한다면 서버컴포넌트는 클라이언트 번들에 포함되지 않고, 서버컴포넌트로써 동작합니다.


Server Component -> Client Component

“Server Component -> Server Component”에서 했던 것처럼, 한번 부른 fetch의 결과는 같은 요청이 살아있는 동안 캐시된다는 것을 활용할 수 있습니다. 데이터가 필요한 클라이언트 컴포넌트를 서버컴포넌트로 한번 감싸서 props drilling을 완화할 수 있습니다. 관련 링크(유튜브 영상, 소리주의)

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
// Server Component A
+import ServerComponentB from "./ServerComponentB"
+
+export default async function ServerComponentA() {
+  const initialCount = await fetch(...);
+  
+  return <>
+     <h3>I got {initialCount}</h3>
+     <ServerComponentB />
+  </>
+}
+
1
+2
+3
+4
+5
+6
+
// Server Component B
+import ServerComponentC from "./ServerComponentC"
+
+export default function ServerComponentB() {
+  return <ServerComponentC/>
+}
+
1
+2
+3
+4
+5
+6
+7
+
// Server Component C
+import ClientComponent from "./ClientComponent"
+
+export default function ServerComponentC() {
+  const initialCount = await fetch(...);
+  return 	<ClientComponent initialCount={initialCount}/>
+}
+
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+
// Client Component
+'use client'
+
+import {useState} from "react"
+
+export default function ClientComponent({initialCount}) {
+  const [count, setCount] = useState(initialCount);
+  
+  return (
+    <>
+      {count}
+      <button onClick={() => setCount(prev => prev + 1)}>+1</button>
+      <button onClick={() => setCount(prev => prev - 1)}>-1</button>
+    </>
+  )
+}
+


Client Component -> Server Component

props로 넘겨주는 방법이나, 다음과 같이 cloneElement를 이용한 방법이 있습니다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+
import React from "react";
+import ClientComponent from "./ClientComponent"
+
+export default function ParentComponent({ children }) {
+  return (
+    <ClientComponent>
+       <ServerComponent/>
+    </ClientComponent>
+  ) 
+}
+
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
'use client'
+
+import React from "react";
+
+export default function ClientComponent({ children }) {
+  return (
+    <div>
+       {React.cloneElement(children, {name:"test props"})}
+    </div>
+  ) 
+}
+
1
+2
+3
+4
+5
+6
+7
+8
+9
+
import React from "react";
+
+export default function ServerComponent({ name }) {
+  return (
+    <div>
+       {name}
+    </div>
+  ) 
+}
+

하지만 가능한 것과는 별개로 크게 효용성이 있을지는 모르겠습니다. 이 방법은 클라이언트 컴포넌트의 바로 한단계 밑 서버컴포넌트로만 전달이 가능하기에, 클라이언트 컴포넌트에서 서버컴포넌트를 변경하려면 URL 쿼리를 변경하여 새로 페이지를 fetch하는게 나을 수도 있습니다. URL 쿼리를 변경하는 경우 한단계 밑 서버컴포넌트뿐만 아니라 관련있는 모든 서버컴포넌트들도 변경이 가능하기 떄문입니다.

This post is licensed under CC BY 4.0 by the author.

React 새로고침/뒤로가기 막기

next.js caching

Comments powered by Disqus.

diff --git "a/posts/next.js-Automatic-Static-\354\265\234\354\240\201\355\231\224/index.html" "b/posts/next.js-Automatic-Static-\354\265\234\354\240\201\355\231\224/index.html" new file mode 100644 index 000000000..571a67c92 --- /dev/null +++ "b/posts/next.js-Automatic-Static-\354\265\234\354\240\201\355\231\224/index.html" @@ -0,0 +1 @@ + next.js - Automatic Static 최적화 | 디피의 개발일지
Posts next.js - Automatic Static 최적화
Post
Cancel

next.js - Automatic Static 최적화

next.js에서는 어떤 페이지가 getServerSideProps 또는 getInitialProps를 가지고 있지 않다면, static 페이지로 결정한다. 이렇게 static 페이지로 결정되면, 빌드시 그 페이지는 static 페이지로 빌드된다.

static 페이지는 서버사이드에서 렌더되지 않고 바로 유저에게 전달된다. end-user와 서버 사이에 CDN이 있다면 CDN의 적용 또한 받는다.

query 객체 처리

getServerSideProps 또는 getInitialProps가 없는 페이지는 빌드시 prerender 되어 static 페이지가 생성된다.

prerender 동안은 router의 query는 비어있다. 하지만 hydration이 완료된 이후 next.js는 페이지를 업데이트하며 query객체를 반영한다. 이 과정은 다음과 같은 과정일 때 발생한다.

  • 페이지가 dynamic-route일 경우
  • URL에 query 값이 있을 경우
  • Rewritesnext.config.js에 설정되어있을 때.

query가 완전히 반영되었음을 확인하기 위해선 next/routerisReady 필드를 확인하면 된다.

빌드 결과물

next build로 빌드를 마치면 static 페이지는 .next/server/pages/about.html로 결과물이 나온다. 하지만 getServerSideProps 을 사용한 페이지는 .next/server/pages/about.js로 결과물이 나오게 된다.

주의사항

  • getInitialProps를 사용하는 Custom App이 있다면, getStaticProps 등을 활용한 Static Generation를 적용하지 않는한 automatic static 최적화가 동작하지 않는다.
  • getInitialProps를 사용하는 Custom Document가 있다면, ctx.req가 빌드시 정의되어있어야한다. prerender 단계에서 ctx.req는 undefined이기 때문이다.
  • next/routerisReady 필드가 true가 되기 전까지 asPath 값을 사용하지마라.


출처

  • [automatic-static-optimization][https://nextjs.org/docs/advanced-features/automatic-static-optimization]
This post is licensed under CC BY 4.0 by the author.

next/image 컴포넌트

next.js - Satic HTML Export

Comments powered by Disqus.

diff --git a/posts/next.js-Compiler/index.html b/posts/next.js-Compiler/index.html new file mode 100644 index 000000000..8dbd2ea94 --- /dev/null +++ b/posts/next.js-Compiler/index.html @@ -0,0 +1,79 @@ + next.js Compiler | 디피의 개발일지
Posts next.js Compiler
Post
Cancel

next.js Compiler

Next.js의 컴파일러는 바벨 대신에 Rust 기반의 SWC를 JS 번들링에 사용한다. 이는 바벨보다 17배 빠르며, Next.js 12부터 디폴트로 쓰인다. 만약 바벨을 사용하고 싶다면, 다음을 참고하면 된다.

Next.js Compiler의 자세한 내용은 공식 가이드에 나와있다. 이 글에서는 주로 사용할만한 요소만 요약하여 정리하였다.


Supported Features

  • Styled Components : bebel-plugin-styled-components와 연계하여 styled-components를 위한 바벨 설정을 next.js compiler에서도 사용할 수 있다.

  • Jest : css auto mocking, loading .env 등을 통해 Jest를 이용한 테스트 설정을 간편하게 해준다.

  • Relay : Graph QL 클라이언트 라이브러리인 Relay에 대한 설정을 제공한다.

  • Remove React Properties : babel-plugin-react-remove-properties와 비슷하게 JSX 프로퍼티를 제거한다. 테스팅에 용이하다.

  • Remove Console : 간단한 설정으로 미처 지우지 못한 콘솔 로그를 빌드과정에서 지워준다.

    1
    +2
    +3
    +4
    +5
    +6
    +
    // next.config.js
    +module.exports = {
    +  compiler: {
    +    removeConsole: true,
    +  },
    +}
    +

    일부 로그는 exclude를 통해 남길 수 있다.

    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +
    // next.config.js
    +module.exports = {
    +  compiler: {
    +    removeConsole: {
    +      exclude: ['error'],
    +    },
    +  },
    +}
    +
  • Emotion : `@emotion/babel-plugin의 설정을 next.config.js에서 대신 할 수 있다.

  • Modularize Imports : 패키지를 불러올 때, “barrel file” 을 default import 처럼 사용할 수 있게 해준다.

    1
    +2
    +3
    +4
    +
    import { Row, Grid as MyGrid } from 'react-bootstrap'
    +->
    +import Row from 'react-bootstrap/Row'
    +import MyGrid from 'react-bootstrap/Grid'
    +

    이는 다음과 같이 설정한다.

    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +
    // next.config.js
    +module.exports = {
    +  modularizeImports: {
    +    'react-bootstrap': {
    +      transform: 'react-bootstrap/',
    +    }
    +  }
    +}
    +

    좀 더 다양한 설정을 보기 위해선 공식문서 참조


Experimental Features

  • Minifier debug options : minifier 옵션이 아직 실험단계이기 때문에, 그동안 디버그 목적으로 minifier의 설정을 변경할 수 있는 방법을 제공한다. minifier가 stable 단계에 돌입하면, 이 방법은 사라진다.

  • SWC Plugins : WASM로 쓰여진 SWC 플러그인을 사용하여 swc의 코드 트랜스포메이션 과정을 변경할 수 있다.

    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +
    // next.config.js
    +module.exports = {
    +  experimental: {
    +    swcPlugins: [
    +      [
    +        'plugin',
    +        {
    +          ...pluginOptions,
    +        },
    +      ],
    +    ],
    +  },
    +}
    +

    swcPlugins는 튜플 배열을 받는다. 첫번째는 plugin으로의 path를 받고, 두번째로는 plugin option 을 받는다. 이때 path는 npm module 패키지명이나 .wasm 바이너리 파일의 절대 경로를 지정할 수 있다.


Unsupported Features

만약 어플리케이션에서 .babelrc를 가지고 있다면, next.js는 자동으로 next.js compiler 대신 Bebel을 사용한다.


출처

https://nextjs.org/docs/advanced-features/compiler

This post is licensed under CC BY 4.0 by the author.

next.js - Output File Tracing

SWR

Comments powered by Disqus.

diff --git a/posts/next.js-Deployment/index.html b/posts/next.js-Deployment/index.html new file mode 100644 index 000000000..cc10726d6 --- /dev/null +++ b/posts/next.js-Deployment/index.html @@ -0,0 +1,43 @@ + next.js - Deployment | 디피의 개발일지
Posts next.js - Deployment
Post
Cancel

next.js - Deployment

Next build API

next build를 실행하면, 작성한 소스코드가 최적화된 상태도 빌드된다. 빌드된 결과물 .next 아래에 위치하며 구조는 다음과 같다.

  • .next/static/chunks/pages : 라우트되는 이름 그대로 생성된 js 파일이 위치한다. (/about –> .next/static/chunks/pages/about.js)
  • .next/static/media : next/image를 사용하여 정적으로 삽입된 이미지가 해시되어 위치한다.
  • .next/static/css : 글로벌 CSS 파일이 위치한다.
  • .next/server/pages : 서버에서 프리렌더링되는 HTML과 JS의 엔트리 포인트. .nft.json 파일은 Output File Tracing이 활성화되면 생성된다.
  • .next/server/chunks : 어플리케이션 전반에서 사용되는 JS chunks
  • .next/cache : node.js server에서 캐시된 이미지, 응답, 페이지들이 존재. 빌드 시간을 줄여주고, 이미지 로딩이 빨라진다.

.next 폴더 하위에 존재하는 모든 JS 파일은 컴파일되고, 최소화 된다. 그리고 모든 모던 브라우저을 지원한다.

Static Only

만약 블로그와 같이 정적인 파일로 구성된 어플리케이션이라면, next export를 통해 정적인 파일만 빼서 배포할 수도 있다.

Manual Graceful shutdowns

프로세스로부터 SIGTERM이나 SIGINT 신호를 받으면 특정 코드가 실행되도록 설정할 수 있다. 주로 서버 종료시 클린업 코드를 실행하기 위해 사용된다.

  1. 환경변수에서 NEXT_MANUAL_SIG_HANDLEtrue로 설정한다. (시스템에서 설정해야하며, .env 파일에서 설정하면 안된다.)

    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +
    // package.json
    +{
    +  "scripts": {
    +    "dev": "NEXT_MANUAL_SIG_HANDLE=true next dev",
    +    "build": "next build",
    +    "start": "NEXT_MANUAL_SIG_HANDLE=true next start"
    +  }
    +}
    +
  2. pages/_document.js에서 시그널 핸들러를 설정한다.ㄴ

    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +
    // pages/_document.js
    +   
    +if (process.env.NEXT_MANUAL_SIG_HANDLE) {
    +  process.on('SIGTERM', () => {
    +    console.log('Received SIGTERM: ', 'cleaning up')
    +    process.exit(0)
    +  })
    +   
    +  process.on('SIGINT', () => {
    +    console.log('Received SIGINT: ', 'cleaning up')
    +    process.exit(0)
    +  })
    +}
    +


출처

https://nextjs.org/docs/deployment

This post is licensed under CC BY 4.0 by the author.

next.js - production 배포 전 체크리스트

next.js - Output File Tracing

Comments powered by Disqus.

diff --git a/posts/next.js-Output-File-Tracing/index.html b/posts/next.js-Output-File-Tracing/index.html new file mode 100644 index 000000000..e680c19d0 --- /dev/null +++ b/posts/next.js-Output-File-Tracing/index.html @@ -0,0 +1,87 @@ + next.js - Output File Tracing | 디피의 개발일지
Posts next.js - Output File Tracing
Post
Cancel

next.js - Output File Tracing

next.js는 빌드 시 자동으로 모든 페이지와 그 의존성을 트랙킹하여 배포시 필요한 파일을 알아낸다. 이렇게 함으로써 배포될 파일의 크기를 줄일 수 있다.

이전에 도커로 배포할 땐, 모든 의존성을 가져온 후 next start를 실행해야했다. 하지만 next.js 12부터는 Output File Tracing을 통해 .next/ 디렉터리만 있으면 된다. 단, standalone 모드를 켜야한다.

동작방식

next build가 실행되는 동안, next.js는 @vercel/nft를 사용하여 정적으로 import, require, fs를 분석하여 로드될 페이지를 알아낸다.

next.js의 프로덕션 서버 또한 필요한 파일과, .next/next-server.js.nft.json 을 트래킹한다. .nft.json를 활용할 때는 nft.json에 상대적인 모든 trace의 파일리시트를 읽고, 배포할 위치에 복사한다.


standalone mode

next.js는 node_modules에서 필요한 파일을 뽑아내 배포시에 필요한 파일만으로 구성된 standalone 폴더를 구성할수 있다. 이를 활성화하기 위해선 next.config.js에서 다음과 같이 설정해야한다.

1
+2
+3
+
module.exports = {
+  output: 'standalone',
+}
+

이렇게 하면, 빌드시 .next/standalone 폴더가 생성되고, 여기에 node_modules에서 필요한 파일만 저장된다.

또한 최소화된 server.jsnext start 대신에 사용된다. 이 최소화된 서버는 public, .next/static 폴더를 디폴트로 복사하지 않는다. CDN에 저장되는 것이 가장 이상적이기 때문이다. 다만, standalone/.next/static 폴더에 수동으로 복사할 수는 있다.

Note : next.config.jsnext build 시 읽혀져 server.js에 복사된다. 만약 serverRuntimeConfig 또는 publicRuntimeConfig옵션 이 사용된다면, 빌드시 다른 값으로 변경된다.

만약 프로젝트에서 디폴트 로더로 Image Optimization를 사용중이라면, sharp를 설치해야한다.

1
+
npm i sharp
+


주의사항

  • 모노레포 설정에서 tracing 하면, 프로젝트의 디렉터리가 기본으로 사용된다. next build packages/web-app에서 packages/web-app이 tracing root가 되고, 해당 폴더 밖에 있는 파일들은 포함되지 않는다. 만약 바깥 파일도 tracing에 포함하고 싶으면, experimental.outputFileTracingRoot를 설정할 수 있다.

    1
    +2
    +3
    +4
    +5
    +6
    +7
    +
    // packages/web-app/next.config.js
    +module.exports = {
    +  experimental: {
    +    // this includes files from the monorepo base two directories up
    +    outputFileTracingRoot: path.join(__dirname, '../../'),
    +  },
    +}
    +
  • 필요한 파일을 가져오는데 실패하거나 잘못된 파일을 가져오는 경우가 있다. 이러한 경우 unstable_includeFiles, unstable_excludeFiles 같은 page config를 export 하면 된다. 각 prop은 minimatch의 배열을 받는다. 프로젝트 루트를 루트로 삼고 설정하면 된다

  • 현재 Next.js는 .nft.json을 가지고 아무것도 하지 않는다. 이 파일은 어플리케이션이 배포되는 시스템에서 minimal deployment를 위해 사용된다. 향후 .nft.json 를 활용한 커맨드를 낼 계획이 있다.


Experimental turbotrace

의존성을 tracing하는 것은 느려질 수 있다. 따라서 next.js 팀은 turbotrace를 러스트로 작성하여 이 과정을 빠르게 만들었다.

활성화를 위해선 next.config.js에서 다음과 같이 설정할 수 있다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+
// next.config.js
+module.exports = {
+  experimental: {
+    turbotrace: {
+      // control the log level of the turbotrace, default is `error`
+      logLevel?:
+      | 'bug'
+      | 'fatal'
+      | 'error'
+      | 'warning'
+      | 'hint'
+      | 'note'
+      | 'suggestions'
+      | 'info',
+      // control if the log of turbotrace should contain the details of the analysis, default is `false`
+      logDetail?: boolean
+      // show all log messages without limit
+      // turbotrace only show 1 log message for each categories by default
+      logAll?: boolean
+      // control the context directory of the turbotrace
+      // files outside of the context directory will not be traced
+      // set the `experimental.outputFileTracingRoot` has the same effect
+      // if the `experimental.outputFileTracingRoot` and this option are both set, the `experimental.turbotrace.contextDirectory` will be used
+      contextDirectory?: string
+      // if there is `process.cwd()` expression in your code, you can set this option to tell `turbotrace` the value of `process.cwd()` while tracing.
+      // for example the require(process.cwd() + '/package.json') will be traced as require('/path/to/cwd/package.json')
+      processCwd?: string
+      // control the maximum memory usage of the `turbotrace`, in `MB`, default is `6000`.
+      memoryLimit?: number
+    },
+  },
+}
+


출처

https://nextjs.org/docs/advanced-features/output-file-tracing

This post is licensed under CC BY 4.0 by the author.

next.js - Deployment

next.js Compiler

Comments powered by Disqus.

diff --git a/posts/next.js-Satic-HTML-Export/index.html b/posts/next.js-Satic-HTML-Export/index.html new file mode 100644 index 000000000..9cedb43e4 --- /dev/null +++ b/posts/next.js-Satic-HTML-Export/index.html @@ -0,0 +1,15 @@ + next.js - Satic HTML Export | 디피의 개발일지
Posts next.js - Satic HTML Export
Post
Cancel

next.js - Satic HTML Export

next export 명령어를 통해 next.js 어플리케이션을 static HTML으로 만들 수 있다. 이렇게 만들어진 Static HTML은 Node.js 서버없이 운영이 가능하다. 다음과 같은 기능이 필요없다면, next export를 사용하는 것이 권장된다.

위와 같은 기능들은 node.js 서버를 필요로 하고, 빌드 시 계산할 수 없는 로직을 포함하고 있기에, 위와 같은 기능을 사용중이라면 next export를 통한 node.js 서버 없는 운영이 가능하다.

사용법

package.json을 다음과 같이 변경한다.

1
+2
+3
+4
+5
+6
+7
+
{
+  ...
+  "scripts":{
+    "build": "next build && next export"
+  }
+  ...
+}
+

이후 npm run build를 수행하면, out 디렉터리가 생성된다.

next build 동안 생성된 HTML 파일을 next export를 통해 out 디렉터리에 모아 export 해주는 것이다.

경우에 따라서는 next.config.jsexportPathMap를 설정하여 어떤 페이지들을 static하게 생성해낼것인지 지정이 가능하다. 이때 getStaticPaths를 사용한 페이지는 exportPathMap에서 설정하지 않아도 자동으로 HTML을 생성한다.


지원하는 기능

next export를 하더라도 다음과 같은 기능은 지원된다.


getInitialProps

getInitialPropsgetStaticProps 대신에 사용해도 되지만, 몇가지 주의사항이 존재한다.

  • getInitialPropsgetStaticPropsgetStaticPaths와 같은 페이지에서 함께 쓰일 수 없다. dynamic route를 사용해야한다면, getStaticPaths를 사용하는 대신에 exportPathMap을 설정하는 것이 좋다.
  • getInitalProps가 export 단계에서 호출될 때, contextreq, res는 비어있다.
  • getInitialProps클라이언트 단에서 네비게이트 될 때 호출된다. 빌드시 호출하고 싶다면 getStaticProps를 사용해야한다.
  • getInitialProps는 node.js 라이브러리나 file-system을 사용할 수 없다.

위와 같은 주의사항이 있기에, getInitialProps보다는 getStaticProps를 사용하는 것이 권장된다.


출처

  • https://nextjs.org/docs/advanced-features/static-html-export
This post is licensed under CC BY 4.0 by the author.

next.js - Automatic Static 최적화

next.js - Script 컴포넌트

Comments powered by Disqus.

diff --git "a/posts/next.js-Script-\354\273\264\355\217\254\353\204\214\355\212\270/index.html" "b/posts/next.js-Script-\354\273\264\355\217\254\353\204\214\355\212\270/index.html" new file mode 100644 index 000000000..6ba6755b0 --- /dev/null +++ "b/posts/next.js-Script-\354\273\264\355\217\254\353\204\214\355\212\270/index.html" @@ -0,0 +1,125 @@ + next.js - Script 컴포넌트 | 디피의 개발일지
Posts next.js - Script 컴포넌트
Post
Cancel

next.js - Script 컴포넌트

next.js 에서는 페이지 성능 개선을 위해 서드파티 script를 가져올 수 있는 next/script 컴포넌트를 제공한다.

사용법

import

1
+
import Script from 'next/script'
+

Page script

페이지 컴포넌트에서 사용될 수 있으며, 페이지가 브라우저에 로드되면 script를 fetch하고 실행한다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+
import Script from 'next/script'
+
+export default function Dashboard() {
+  return (
+    <>
+      <Script src="https://example.com/script.js" />
+    </>
+  )
+}
+

Application script

모든 경로에서 같은 script를 추가하고 싶다면 Custom App 에서 다음과 같이 사용할 수 있다. Page script와 마찬가지로 페이지가 로드되면 스크립트를 가져온다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+
// pages/_app.js
+
+import Script from 'next/script'
+
+export default function MyApp({ Component, pageProps }) {
+  return (
+    <>
+      <Script src="https://example.com/script.js" />
+      <Component {...pageProps} />
+    </>
+  )
+}
+

어떤 경로로 접근하든 위 스크립트는 불러와지고, next.js는 위 script가 딱 한번만 불러와질수 있도록 보장한다. 하지만, 모든 페이지에서 같은 script를 사용하는 경우는 거의 없으니 쓸모없는 성능 저하를 막기 위해 특정 페이지에서만 script를 불러오도록 설정하는 것이 좋다.


Strategy

next/script 컴포넌트의 속성인 strategy를 통해 스크립트가 불러오는 타이밍에 대해 설정할 수 있다.

  • beforeInteractive : 유저에게 전달되는 최초 HTML에 삽입되며, next.js 코드를 불러와 hydration이 일어나기 전에 로드한다. 스크립트 로드는 next.js 로드 전에 이뤄지지만, 스크립트 실행은 next.js 로드를 막지 않는다.

    • Custom Document 안에서만 사용해야한다. 따라서 모든 페이지에서 필요한 스크립트일 경우에만 사용한다.
    • 또는 반드시 제일 먼저 실행해야하는 경우에만 사용한다.
    • ex) bot detector, cookie consent managers
  • afterInteractive : 디폴트 설정으로, hydration이 어느정도 진행된 후에 불러온다.

    • 가능한한 빨리 불러와야하는 스크립트지만, next.js 코드보다는 늦게 불러와도 될 때 사용한다.
    • ex) Analytics
  • lazyOnload : 브라우저가 한가할 때 가져온다.(필요한 모든 리소스를 다 가져온 상태).

    • ex) Chat support plugins, Social media widgets
  • worker : 웹 워커 안에서 스크립트를 불러온다. next.js v13.1.2 기준으로 베타 단계인 기능이다.

    • 사용하기 위해선 다음과 같이 next.config.js에 설정해줘야한다.

      1
      +2
      +3
      +4
      +5
      +
      module.exports = {
      +  experimental: {
      +    nextScriptWorkers: true,
      +  },
      +}
      +


Inline Scripts

다음과 같이 inline으로 스크립트 코드를 직접 작성할 수 있다. 다만 이때는 id 속성을 만드시 지정해야한다.

1
+2
+3
+
<Script id="show-banner">
+  {`document.getElementById('banner').classList.remove('hidden')`}
+</Script>
+

dangerouslySetInnerHTML를 사용할 수도 있다.

1
+2
+3
+4
+
<Script
+  id="show-banner"
+  dangerouslySetInnerHTML=
+/>
+


onLoad, onReady, onError

스크립트 로드 상태에 따라 별도의 코드를 실행시키고 싶을 수 있다. next/script에서는 이를 위해 다음과 같은 속성을 제공한다. 콜백함수 안에서는 스크립트의 코드를 사용할 수 이다.

  • onLoad : 스크립트의 로딩이 종료되면 콜백함수 실행
    • beforeInteractive 일때는 사용할 수 없음.
  • onReady: 스크립트의 로딩이 종료되고, 네비게이션 등으로 컴포넌트가 마운트 될 때마다 콜백함수 실행한다.
  • onError : 스크립트 로딩에 실패했을 경우 콜백함수 실행
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+
import Script from 'next/script'
+
+export default function Page() {
+  return (
+    <>
+      <Script
+        src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js"
+        onLoad={() => {
+          console.log(_.sample([1, 2, 3, 4]))	// lodash 코드 사용가능
+        }}
+      />
+    </>
+  )
+}
+


추가적인 attributes

next/script에서는 일반적인 <script> 태그의 DOM attribute 중 next/script에서 사용하지 않는 attribute를 <script>태그에 부여한다. 따라서 noncedata-*로 시작하는 custom data attribute도 사용할 수 있다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+
import Script from 'next/script'
+
+export default function Page() {
+  return (
+    <>
+      <Script
+        src="https://example.com/script.js"
+        id="example-script"
+        nonce="XUENAJFW"
+        data-test="script"
+      />
+    </>
+  )
+}
+

출처

https://nextjs.org/docs/basic-features/script

This post is licensed under CC BY 4.0 by the author.

next.js - Satic HTML Export

next.js - production 배포 전 체크리스트

Comments powered by Disqus.

diff --git "a/posts/next.js-container-\354\225\210\354\227\220\354\204\234-pm2\353\241\234-next.js-\354\225\261-\354\213\244\355\226\211\355\225\230\352\270\260/index.html" "b/posts/next.js-container-\354\225\210\354\227\220\354\204\234-pm2\353\241\234-next.js-\354\225\261-\354\213\244\355\226\211\355\225\230\352\270\260/index.html" new file mode 100644 index 000000000..5bfadcb19 --- /dev/null +++ "b/posts/next.js-container-\354\225\210\354\227\220\354\204\234-pm2\353\241\234-next.js-\354\225\261-\354\213\244\355\226\211\355\225\230\352\270\260/index.html" @@ -0,0 +1,131 @@ + container 안에서 pm2로 next.js 앱 실행하기 | 디피의 개발일지
Posts container 안에서 pm2로 next.js 앱 실행하기
Post
Cancel

container 안에서 pm2로 next.js 앱 실행하기

next.js는 기본적으로 node.js runtime에서 작동된다. 하지만 node.js 는 싱글 스레드 기반이고, 따라서 트래픽이 많은 환경에서 multi-core를 사용하여 node.js 앱을 구동하기 위해선 다음과 같은 방법이 있다.

  • worker thread
  • child process
  • cluster

필자는 next.js에서 가장 사용하기 편한 방식인 cluster를 선택하였고, 편리하게 하기위해 pm2cluster 모드를 사용하기로 결정하였다. 환경은 kubernetes 환경으로 하나의 pod에 2~3개의 next.js 인스턴스를 pm2의 cluster 모드로 관리하고자 하였다.

따라서 가이드를 따라 다음과 같이 Dockerfile과 pm2 설정파일을 작성하였다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+
// ./ecosystem.config.js
+module.exports = {
+  apps: [
+    {
+      name: "next-app",
+      script: "node_modules/next/dist/bin/next",
+      args: "start --port 80",
+      instances: 2,
+      exec_mode: "cluster",
+    },
+  ],
+};
+
1
+2
+3
+4
+5
+6
+7
+8
+9
+
// package.json
+{
+  ...
+  "script":{
+    ...
+    "start-pm2:dev": "pm2 start ecosystem.config.js",
+    ...
+  }
+}
+
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+
# Stage 0 
+FROM node AS nextjs-builder
+
+# next.js 빌드
+WORKDIR /home/deploy/next
+COPY ./package.json ./package.json
+COPY ./package-lock.json ./package-lock.json
+RUN  npm ci
+COPY ./ecosystem.config.js ./ecosystem.config.js
+COPY ./.env ./.env
+COPY ./next.config.js ./next.config.js
+COPY ./public ./public
+COPY ./src ./src
+RUN npm run build
+
+# 프로젝트 실행
+ENTRYPOINT ["npm", "run", "start-pm2:dev"]
+

하지만 막상 도커 이미지를 빌드하고, 컨테이너를 실행하니 pm2가 실행되자마자 바로 컨테이너가 종료되었다. 그 후로도 다른 문제가 계속 발생하였는데, 차례대로 해결한 과정을 정리하였다.


pm2-runtime 사용

pm2를 container에서 실행하려면 pm2 start가 아닌 pm2-runtime start를 사용해야한다. 공식문서 따라서 package.json의 명령문을 다음과 같이 바꿔야한다.

1
+2
+3
+4
+5
+6
+7
+8
+
{
+  ...
+  "script":{
+    ...
+    "start-pm2:dev": "pm2-runtime start ecosystem.config.js",
+    ...
+  }
+}
+

하지만 컨테이너를 실행하니 다음과 같은 오류가 나타났다.

1
+2
+
Error: Could not find a production build in the '/home/deploy/next/ecosystem.config.js/.next' directory. Try building your app with 'next build' before starting the production se
+rver. https://err.sh/vercel/next.js/production-start-no-build-id
+

/home/deploy/next/ecosystem.config.js/.next라는 경로에 빌드 파일이 없다는 말이다. 단순히 빌드오류라고 생각할 수 있지만 뭔가 경로가 이상하다. ecosystem.config.js는 파일인데 이 파일을 마치 디렉터리처럼 인식하고, 이 파일의 하위에서 빌드파일을 찾는다.

현재 프로젝트의 파일 구조는 다음과 같다.

1
+2
+3
+4
+5
+
-- next
+|-- .next (빌드파일)
+|-- package.json
+|-- ecosystem.config.js
+|-- (...기타 소스파일들)
+

Dockerfile에서 마지막 WORKDIR은 /home/deploy/next이므로, 명령어를 실행한 위치는 /home/deploy/next이다. 따라서 home/deploy/next/.next로 빌드파일의 경로를 설정해야하나, ecosystem.config.js로 경로를 설정한 것을 모도 pm2의 문제가 있다고 생각하여 찾아보았다.


pm2 list

검색을 한 결과 나하고 비슷한 문제를 겪은 사람을 발견하였다. 링크 이 사람이 스스로 찾은 답은 다음과 같다.

Adding command pm2 list before pm2-runtime solves the issue, I suppose that the command pm2 list initializes the pm2 and sets the proper path for pm2-runtime.

즉, pm2-runtime을 실행하기 전에 먼저 pm2 list를 실행하라는 뜻이다. 그럼 pm2 가 초기화되어 올바른 경로를 설정한다고 한다. 따라서 package.json을 다음과 같이 변경하였다.

1
+2
+3
+4
+5
+6
+7
+8
+
{
+  ...
+  "script":{
+    ...
+    "start-pm2:dev": "pm2 list && pm2-runtime start ecosystem.config.js",
+    ...
+  }
+}
+

이후 컨테이너를 실행하니 제대로 실행되었다. status를 확인해보니 다음과 같이 나왔다.

1
+2
+3
+4
+
ready - started server on 0.0.0.0:80, url: http://localhost:80
+info  - Loaded env from /home/develop/deploy/next/.env
+ready - started server on 0.0.0.0:80, url: http://localhost:80
+info  - Loaded env from /home/develop/deploy/next/.env
+
This post is licensed under CC BY 4.0 by the author.

next.js - data fetching

next/image 컴포넌트

Comments powered by Disqus.

diff --git a/posts/next.js-data-fetching/index.html b/posts/next.js-data-fetching/index.html new file mode 100644 index 000000000..e551ebd03 --- /dev/null +++ b/posts/next.js-data-fetching/index.html @@ -0,0 +1,395 @@ + next.js - data fetching | 디피의 개발일지
Posts next.js - data fetching
Post
Cancel

next.js - data fetching

next.js에서 사용자의 요청을 받고, 페이지를 만들어낼 때 외부 서버로 필요한 데이터를 요청해야할 때 사용하는 기법이다. data fetching기본적으로 페이지 컴포넌트에서 사용할 수 있고 경우에 따라 Custom App 컴포넌트에서도 사용할 수 있다.

next 13부터는 app/ 구조를 사용한다면 일반 컴포넌트에서도 가능하다고 하지만, 현재(2023.01)는 next 13의 app/ 구조가 베타버전이라 pages/ 구조를 기준으로 작성하였다.

Note: Next.js 13 introduces the app/ directory (beta). This new directory has support for colocated data fetching at the component level, using the new React use hook and an extended fetch Web API.

Learn more about incrementally adopting app/.


getStaticProps

  • 블로그 게시글처럼 빌드 시 이외에 변경되지 않는 값을 불러오고 싶다면 getStaticProps을 사용한다.
1
+2
+3
+4
+5
+
export async function getStaticProps(context) {
+  return {
+    props: {}, // 페이지 컴포넌트에서 필요한 props 값 주입
+  };
+}
+
  • getStaticProps가 만드는 건 HTML, JSON 파일이다.
    • HTML에는 렌더링된 컴포넌트들이 포함되고, JSON 파일은 getStaticProps의 결과가 저장된다.
  • 성능이 좋아진다.
    • 빌드 시 한번 호출되어 static 페이지를 생성하고, 사용자 요청이 이 static 파일을 반환하기에 별도의 계산이 필요없다.
    • getStaticProps는 HTML, JSON 파일을 만드므로 CDN에 캐시된다.

실행시점

getStaticProps는 항상 server-side에서 실행된다. 따라서 DB 접근과 같은 민감한 작업을 수행할 수 있다. 실행 시점은 다음과 같다.

  • next build시에 항상 실행된다.
  • fallback: true일 때는 백그라운드에서 실행된다.
  • fallback: blocking 일 때는 최초 페이지 접근 시에 실행된다.
  • revalidate를 사용하면 백그라운드에서 실행된다.
  • revalidate()를 사용하면 최초 페이지 접근 시 백그라운드에서 실행된다.

제약사항

  • getStaticProps는 static HTML을 만드므로 유저 request에 접근할 순 없다. 만약 접근해야한다면 Middleware를 추가하면 된다.
  • 페이지 컴포넌트에서만 사용할 수 있다. _app, _document, _error에서도 사용할 수 없다
  • production에서는 빌드타임에 한번 실행되지만, 개발 시에는 매 요청마다 실행된다.

Preview mode

preview mode를 사용해 빌드타임 대신에 request time에 실행시킬 수 있다.


getStaticPaths

  • Dynamic Route를 사용하는 페이지에서, 빌드 시 정적으로 렌더링할 경로 설정.
  • getStaticPaths에서 반환된 경로는 모두 정적으로 빌드타임에 렌더링되어 정적으로 저장된다.
  • 이곳에 정의되지 않은 하위 경로는 접근해도 페이지가 안뜬다.
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+
// pages/posts/[id].js
+
+export async function getStaticPaths() {
+  return {
+    paths: [{ params: { id: '1' } }, { params: { id: '2' } }],
+    fallback: false, // false, true, 'blocking'
+  }
+}
+
+export async function getStaticProps(context) {
+  return
+    props: { post: {} },
+  }
+}
+
+export default function Post({ post }) {
+  // Render post...
+}
+
  • getStaticPathsgetStaticProps와 함께 사용해야한다.
    • getStaticPaths에서 리턴한 paths마다 getStaticProps가 실행됨
    • getStaticPaths에서 fallback:true를 반환하면 getStaticProps는 백그라운드에서 실행된다.
    • getStaticPaths에서 fallback:blocking을 반환하면 getStaticProps는 최초 호출 시 실행된다.

제약사항

  • getStaticPathsgetServerSideProps와 함께 쓰일 수 없음.
  • production에서는 빌드타임에 한번 실행되지만, 개발 시에는 매 요청마다 실행된다.

수요에 따라 paths 생성 (Generating paths on-demand)

  • paths로 반환한 페이지가 너무 많으면 빌드시 시간이 오래 걸릴 수 있다. 따라서 빈 배열을 반환하고, 페이지에 최초 접근 시에 그 페이지를 렌더하도록 fallback: blocking과 함께 쓰일 수 있다.
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+
export async function getStaticPaths() {
+  // 페이지 최초 접근시에만 렌더
+  if (process.env.SKIP_BUILD_STATIC_GENERATION) {
+    return {
+      paths: [],
+      fallback: "blocking",
+    };
+  }
+
+  // SKIP_BUILD_STATIC_GENERATION=false 일땐 paths 생성.
+  const res = await fetch("https://.../posts");
+  const posts = await res.json();
+
+  const paths = posts.map((post) => ({
+    params: { id: post.id },
+  }));
+
+  return { paths, fallback: false };
+}
+


Incremental Static Regeneration

static 페이지를 다시 빌드할 필요가 있을 때, 전체 페이지를 다시 빌드할 필요없이 각 페이지를 일정시간마다 자동으로 빌드해서 새롭게 업데이트 된 페이지를 보여주는 방법이다.

사용법은 getStaticProps의 반환 객체에 revalidate를 추가하면 된다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+
export async function getStaticProps() {
+  const res = await fetch("https://.../posts");
+  const posts = await res.json();
+
+  return {
+    props: {
+      posts,
+    },
+    // Next.js will attempt to re-generate the page:
+    // - When a request comes in
+    // - At most once every 10 seconds
+    revalidate: 10, // In seconds
+  };
+}
+
  1. 첫 요청이 오면 빌드 시 만든 static 페이지를 반환한다.
  2. 10초가 지난 후 첫 요청에는 빌드 시 만든 static 페이지를 반환한다.
  3. next.js가 해당 페이지를 백그라운드에서 regenerate한다.
  4. 페이지가 정상적으로 빌드되면 next.js는 이전 페이지를 invalidate 시키고, 새로운 페이지를 보여준다. 페이지 빌드가 실패하면 이전 페이지를 반환하고, 새로운 요청이 들어올 시 다시 빌드를 진행한다.

on-demand Revalidation

특정 주기마다 regenerate하는 대신, 필요할 때마다 업데이트하고 싶을 떄 사용한다.

  1. secret key를 생성후, 다음 주소로 요청을 보낸다.

    1
    +
    https://<your-site.com>/api/revalidate?secret=<token>
    +
  2. secret key를 환경변수에 추가하고, api/revalidate를 다음과 같이 구성한다.

    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +14
    +
    // pages/api/revalidate.js
    +
    +export default async function handler(req, res) {
    +  if (req.query.secret !== process.env.MY_SECRET_TOKEN) {
    +    return res.status(401).json({ message: 'Invalid token' })
    +  }
    +
    +  try {
    +    await res.revalidate('/path-to-revalidate')
    +    return res.json({ revalidated: true })
    +  } catch (err)
    +    return res.status(500).send('Error revalidating')
    +  }
    +}
    +

주의사항

  • CDN에서 캐시가 일어나면 ISR이 적절하게 동작하지 않는다. useCdn : false 옵션을 사용할 수 있다.


getServerSideProps

  • 매 요청마다 새롭게 값을 불러와야할 경우 사용.

    1
    +2
    +3
    +4
    +5
    +
    export async function getServerSideProps(context) {
    +  return {
    +    props: {}, // will be passed to the page component as props
    +  };
    +}
    +
  • 모든 요청에 대한 계산을 해야하고, CDN 캐시 적용이 되지 않기에 더 느리다. 따라서 꼭 매번 새롭게 불러와야할 경우에만 사용한다.

    • 또한 꼭 첫 html에 데이터가 반영되지 않아도 되는 페이지라면, 클라이언트 단에서 데이터를 가져오는 것이 좋다. (next에서 제작한 swr을 사용하면 편하다)
  • getServerSideProps빌드시 번들로 묶이지 않는다. 따라서 클라이언트 사이드에 전달도 되지 않는다. 그렇기에 서버사이드에서만 실행해야하는 코드(DB 호출)를 사용하기에 좋다.

    • 브라우저에서 실행되지 않기에 window.document와 같은 브라우저 API를 사용할 수 없다.

Context

context를 인자로 받는데, 다음과 같은 필드가 포함되어있다.

  • params : dynamic route를 사용 시, 라우팅 파라미터가 담겨있다.
  • req : HTTP IncomingMessage objectcookies를 포함한 객체이다.
  • res : HTTP response object 객체
  • query : dynamic route 파라미터를 포함한 쿼리스트링이 담겨있다.
  • preview : 페이지가 Preview Mode라면 true이고, 아니면 false이다
  • previewData : setPreviewData로 설정된 preview 데이터가 담겨있다.
  • resolveUrl : URL의 정규화된 정보를 포함함
  • locale : 설정된 locale 값
  • locales : 지원하는 locales 포함
  • defaultLocale : default locale로 설정된 값

Return values

getServerSideProps에서는 다음과 같은 데이터를 반환할 수 있고, 각 반환값에 따라 페이지 렌더링이 달라진다.

  • props : 페이지 컴포넌트로 전달되는 데이터를 담는다. 일반적으로 사용하는 반환값으로, props로 넘어간 데이터 가지고 페이지 컴포넌트가 렌더링한다.

    1
    +2
    +3
    +4
    +5
    +
    export async function getServerSideProps(context) {
    +  return {
    +    props: { message: `Next.js is awesome` }, // will be passed to the page component as props
    +  };
    +}
    +
  • notFound : boolean 값을 담는다. true일 경우 페이지 컴포넌트를 렌더링하지 않고 404 코드를 낸다.

    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +14
    +
    export async function getServerSideProps(context) {
    +  const res = await fetch(`https://.../data`);
    +  const data = await res.json();
    +
    +  if (!data) {
    +    return {
    +      notFound: true,
    +    };
    +  }
    +
    +  return {
    +    props: { data }, // will be passed to the page component as props
    +  };
    +}
    +
  • redirect : 다른 페이지로 리다이렉트 시킬 때 사용한다. statusCode 필드를 추가할 수 있으나, 그럴려면 permanent 필드를 설정하면 안된다. (redirect permanent란?)

    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +14
    +15
    +16
    +17
    +
    export async function getServerSideProps(context) {
    +  const res = await fetch(`https://.../data`);
    +  const data = await res.json();
    +
    +  if (!data) {
    +    return {
    +      redirect: {
    +        destination: "/",
    +        permanent: false,
    +      },
    +    };
    +  }
    +
    +  return {
    +    props: {}, // will be passed to the page component as props
    +  };
    +}
    +

with Typescript

타입스크립트와 함께 사용할 때 타이핑 기법들은 다음과 같다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+
import { GetServerSideProps } from 'next'
+
+type Data = { ... }
+
+export const getServerSideProps: GetServerSideProps<{ data: Data }> = async (context) => {
+  const res = await fetch('https://.../data')
+  const data: Data = await res.json()
+
+  return {
+    props: {
+      data,
+    },
+  }
+}
+

추론된 타입을 사용하고 싶으면 다음과 같이 할 수 있다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
import { InferGetServerSidePropsType } from 'next'
+
+type Data = { ... }
+
+export const getServerSideProps = async () => {
+  const res = await fetch('https://.../data')
+  const data: Data = await res.json()
+
+  return {
+    props: {
+      data,
+    },
+  }
+}
+
+function Page({ data }: InferGetServerSidePropsType<typeof getServerSideProps>) {
+  // will resolve data to type Data
+}
+
+export default Page
+


getInitialProps

getServerSideProps와 같이 서버사이드에서 페이지 컴포넌트를 렌더하기 전에 필요한 데이터를 불러오기 위한 메서드이다( getStaticProps, getStaticPaths의 기능은 없음). next v9 이전에 사용하였으며 그 이후에는 getServerSideProps가 사용된다. 하지만 레거시 코드를 읽을 때 필요하니 알아둘 필요가 있다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+
export default function Page({ stars }) {
+  return <div>Next stars: {stars}</div>;
+}
+
+Page.getInitialProps = async (ctx: NextPageContext) => {
+  const res = await fetch("https://api.github.com/repos/vercel/next.js");
+  const json = await res.json();
+  return { stars: json.stargazers_count };
+};
+

getServerSideProps와 차이점

  • 빌드시 번들로 묶인다. 첫 페이지 렌더시에는 서버에서 실행되는데, next/link, next/router로 페이지 네비케이팅 됐을 시에는 클라이언트에서 실행된다.
    • but getInitialProps가 Custom App 컴포넌트에 있고, 네비게이팅된 페이지가 getServerSideProps를 사용중이라면, 서버에서 실행된다.
  • 인자로 받는 context의 구성이 다르다.
    • pathname: 현재 라우트.
    • req : HTTP IncomingMessage objectcookies를 포함한 객체이다.
    • res : HTTP response object 객체
    • query : dynamic route 파라미터를 포함한 쿼리스트링이 담겨있다.
    • asPath : 브라우저에서 보여지는 쿼리를 포함한 실제 path
    • err : 렌더링 중 일어난 에러

주의사항

  • 한 페이지 컴포넌트에서는 하나의 getInitialProps만 실행됨.

    • 따라서 _app.tsx와 페이지 컴포넌트 둘 다 getInitialProps가 있다면 먼저 실행되는 _app.tsxgetInitialProps만 실행된다.

    • 만약 페이지 컴포넌트의 getInitialProps를 실행하려면 _app.tsx를 다음과 같이 변경해야한다.

      1
      +2
      +3
      +4
      +5
      +6
      +7
      +8
      +9
      +10
      +11
      +12
      +13
      +14
      +15
      +16
      +17
      +18
      +
      function MyApp({ Component, pageProps }) {
      +  return <Component ponent {...pageProps} />;
      +}
      +
      +MyApp.getInitialProps = async ({ Component, ctx }) => {
      +  let pageProps = {};
      +  // 하위 컴포넌트에 getInitialProps가 있다면 추가 (각 개별 컴포넌트에서 사용할 값 추가)
      +  if (Component.getInitialProps) {
      +    pageProps = await Component.getInitialProps(ctx);
      +  }
      +
      +  // _app에서 props 추가 (모든 컴포넌트에서 공통적으로 사용할 값 추가)
      +  pageProps = { ...pageProps, posttt: { title: 11111, content: 3333 } };
      +
      +  return { pageProps };
      +};
      +
      +export default MyApp;
      +


shallow route로 화면전환 최적화

같은 페이지 내에서 화면은 같은데 url만 바뀌는 경우, url이 변경될 떄마다 서버에 새로운 페이지를 달라고 요청을 보낸다고 해보자. 그럼 getInitialPropsgetInitialProps가 매번 실행되어 서버의 연산이 너무 많아지게 된다. 이때 사용할 수 있는 것이 ` shallow route`이다

방법은 아래와 같이 router 객체를 사용하여 쓸 수 있다.

1
+2
+3
+4
+5
+6
+7
+8
+
router.push(
+  {
+    pathname: "/cars?model=bmw",
+    query: { ...values, page: 1 }, // 상태값 전달가능
+  },
+  undefined,
+  { shallow: true } // shallow route
+);
+

페이지 변경 감지는 useEffect 또는 componentDidUpdate에서 가능하다

1
+2
+3
+4
+5
+6
+7
+8
+
componentDidUpdate(prevProps) {
+  const { pathname, query } = this.props.router
+  // verify props have changed to avoid an infinite loop
+  if (query.counter !== prevProps.router.query.counter) {
+    // fetch data based on the new query
+  }
+}
+
+

이떄 shallow routing은 동일 페이지의 url 변경에서만 동작하고, 다른 페이지로 이동할 때는 getInitalProps가 그대로 실행된다. 다음 예시는 다른 페이지로 이동하는 예시다.

1
+2
+3
+4
+5
+6
+7
+8
+
router.push(
+  {
+    pathname: "/users",
+    query: { ...values, page: 1 },
+  },
+  undefined,
+  { shallow: true }
+);
+


출처

https://kyounghwan01.github.io/blog/React/next/getInitialProps/#%E1%84%8C%E1%85%AE%E1%84%8B%E1%85%B4%E1%84%89%E1%85%A1%E1%84%92%E1%85%A1%E1%86%BC

https://nextjs.org/docs/basic-features/data-fetching/get-static-props

https://develogger.kro.kr/blog/LKHcoding/133

This post is licensed under CC BY 4.0 by the author.

php 기초 강의

container 안에서 pm2로 next.js 앱 실행하기

Comments powered by Disqus.

diff --git "a/posts/next.js-next-image-\354\273\264\355\217\254\353\204\214\355\212\270/index.html" "b/posts/next.js-next-image-\354\273\264\355\217\254\353\204\214\355\212\270/index.html" new file mode 100644 index 000000000..de4eab9b1 --- /dev/null +++ "b/posts/next.js-next-image-\354\273\264\355\217\254\353\204\214\355\212\270/index.html" @@ -0,0 +1,97 @@ + next/image 컴포넌트 | 디피의 개발일지
Posts next/image 컴포넌트
Post
Cancel

next/image 컴포넌트

next.js에서는 next/image로 자체적인 이미지 컴포넌트를 제공한다. 일반적인 img 태그와는 달리 여러 성능 최적화기법이 내장되어있다. 기본적으로 다음과 같은 기능을 제공한다.

  • 성능 향상 : 각 디바이스에 정확히 맞는 이미지를 모던 웹 포맷에 맞게 제공한다.
  • Visual Stability : Cumulative Layout Shift를 자동으로 예방한다.
    • Cumulative Layout Shift : 웹 화면의 레이아웃이 갑자기 바뀌는 것. 사용자가 의도치않은 동작을 실행할 수 있다.
  • Faster Page Loads : 뷰포트 안에 들어와야만 이미지가 로드되게 한다. 선택적으로 블러처리된 placeholder를 둘 수 있다.
  • Asset Flexibility : 이미지가 리모트 서버에 저장되어있어도, on-demand image resizing을 지원한다.


nexy/Image 컴포넌트 사용법

컴포넌트 import

1
+
import Image from "next/image"
+

image 불러오기

  • Local Image

    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +14
    +
    import profilePic from '../public/me.png'
    +  
    +function Home() {
    +  return (
    +      <Image
    +        src={profilePic}
    +        alt="Picture of the author"
    +        // width={500} automatically provided
    +        // height={500} automatically provided
    +        // blurDataURL="data:..." automatically provided
    +        // placeholder="blur" // Optional blur-up while loading
    +      />
    +  )
    +}
    +
    • import문이 빌드시 분석되어야하기 때문에 Dynamic await import() 또는 require()는 지원되지 않는다.
  • width, height는 next.js에서 자동으로 계산한다. 이는 Cumulative Layout Shift를 방지하기 위해 쓰인다.
    • blurDataURL도 자동으로 지원된다.
  • Remote image

    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +12
    +
    import Image from 'next/image'
    +  
    +export default function Home() {
    +  return (
    +      <Image
    +        src="/me.png"
    +        alt="Picture of the author"
    +        width={500}
    +        height={500}
    +      />
    +  )
    +}
    +
    • 절대주소 또는 상대주소로 이미지의 위치를 명시한다. 이때 상대주소는 public 폴더 이하의 경로이다.
    • 빌드시 remote image에 접근하지 않기에,width, height를 명시해주어야한다.
    • blurDataURL 또한 직접 입력해야한다.


Loaders

Remote image를 불러오는 예제에서 src 값을 /me.png와 같은 상대경로로 입력하였는데, 이것이 가능한 이유는 Loader가 있기 때문이다.

loader는 이미지의 URL을 생성하는 함수이다. src를 변경하여 다양한 사이즈의 이미지를 요청하는 여러 URL을 생성한다. 이러한 여러 URL은 자동으로 srcset을 생성하고, 사이트에 방문한 유저가 적절한 사이즈의 이미지를 다운 받도록한다.

default loader는 이미지를 가져와 최적화한 다음에 next.js 웹 서버에서 제공한다. 만약 CDN이나 image server에서 직접 이미지를 가져가도록 하고 싶다면, 직접 loader 함수를 작성할 수 있다. next/image컴포넌트마다 loader 속성을 지정할 수 있으며, 아니면 어플리케이션 레벨로 next.config.js에서 loader를 지정할 수 있다.


Domains

remote image를 최적화하고 싶을 때, loader 는 디폴트 설정으로 두고, next/image 컴포넌트의 src 에는 절대경로를 입력한다. 악의적인 공격을 막기 위해 remote hostname을 반드시 정의해야한다. 다음과 같이 next.config.js에서 정의할 수 있다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+
module.exports = {
+  images: {
+    remotePatterns: [
+      {
+        protocol: 'https',
+        hostname: 'example.com',
+        port: '',
+        pathname: '/account123/**',
+      },
+      {
+        protocol: 'https',
+        hostname: '**.example.com',
+      },
+    ],
+  },
+}
+
  • 와일드카드 *hostnamepathname에 모두 쓰일 수 있다.
    • * : 하나의 path segment 또는 subdomain과 매칭된다.
    • ** ; 여러 path segment 또는 subdomain과 매칭된다.
      • www.**.com과 같이 중간 부분에서 적용되진 않는다.

좀 더 간단하게 domain만 명시할 수도 있다. 하지만 domain은 와일드카드를 지원하지 알고, protocol을 지정할 수 없기에 remotePatterns가 권장된다.

1
+2
+3
+4
+5
+
module.exports = {
+  images: {
+    domains: ['assets.acme.com'],
+  },
+}
+

remotePatterns에 관한 자세한 내용


Priority

priority속성을 사용하여 이미지 로딩에 우선순위를 둘 수 있다. LCP 엘리먼트에 쓰일 수 있으며, 이미지가 페이지에 접속하자마자 보여야할 때만 사용하는 것이 좋다,


Image Sizing

이미지가 퍼포먼스에 악영향을 미치는 가장 큰 요인은 cumulative layout shift이다 . 이미지가 늦게 로딩되어 다른 엘리먼트를 멀어내는 것을 말한다. 유저의 입장에선 매우 짜증나는 이슈이다.

이 이슈를 막기 위해서는 이미지의 사이즈를 기입해야한다. 이미지의 사이즈를 기업하면, 브라우저는 이미지가 로드되기 전에 이미지를 위한 공간을 미리 잡아둔다.

next/image에서는 다음 세가지 방법중 하나를 사용한다.

  1. static import를 통해 이미지를 가져오면 자동으로 계산한다.

  2. 명시적으로 width, height속성을 기입한다.

  3. fill 속성은 부모 요소를 채울 때 사용하며, 이 속성을 사용하면, width, height를 기입하지 않아도 자동으로 계산된다. 다만 부모 요소의 position이 relative, absolute, fixed 중 하나여야한다.

    fill 속성에 대해 자세히


Styling

기본적으로는 <img> 태그를 사용할 때와 같지만 아래 주의사항이 있다.

글로벌이 아닌 styled-jsx 대신에 className 또는 style을 사용한다.

styled-jsx는 글로벌로 사용하지 않는한 현재 컴포넌트에서만 적용되기에 사용할 수 없다.

fill 속성 사용시, 부모 요소는 position:relative여야한다.

적절한 사이즈를 계산하기 위해 필요하다.

fill 속성 사용시, 부모요소는 deplay: block여야한다.

<div> 엘리먼트일 경우엔 이미 적용되어있지만, 다른 엘리먼트라면 명시적으로 설정해야한다.


출처

next/image 가이드 : https://nextjs.org/docs/basic-features/image-optimization

next/image 레퍼런스 : https://nextjs.org/docs/api-reference/next/image

This post is licensed under CC BY 4.0 by the author.

container 안에서 pm2로 next.js 앱 실행하기

next.js - Automatic Static 최적화

Comments powered by Disqus.

diff --git "a/posts/next.js-production-\353\260\260\355\217\254-\354\240\204-\354\262\264\355\201\254\353\246\254\354\212\244\355\212\270/index.html" "b/posts/next.js-production-\353\260\260\355\217\254-\354\240\204-\354\262\264\355\201\254\353\246\254\354\212\244\355\212\270/index.html" new file mode 100644 index 000000000..bb7772376 --- /dev/null +++ "b/posts/next.js-production-\353\260\260\355\217\254-\354\240\204-\354\262\264\355\201\254\353\246\254\354\212\244\355\212\270/index.html" @@ -0,0 +1,25 @@ + next.js - production 배포 전 체크리스트 | 디피의 개발일지
Posts next.js - production 배포 전 체크리스트
Post
Cancel

next.js - production 배포 전 체크리스트

next.js로 만든 앱을 배포하기 전 체크리스트

  • 가능한만큼 캐싱을 적용했는가?
  • 데이터베이스와 백엔드가 같은 region에 배포되어있는가?
  • 가능한 최소한의 Javascript를 사용하는 것을 목표로 해라
  • Javascript 로딩을 가능한 연기하라
  • logging이 구현되어 있는가?
  • errorHandling이 설정되어있는가?
  • 404 페이지와 500 페이지를 만들었는가?
  • 성능 측정을 해보아라
  • Lighthouse를 실행해서 테스트해보아라.
  • 브라우저 호환성을 확인하라
  • 성능을 높이기 위해 다음 기능을 사용하라
  • loading 성능을 개선하라


캐싱

next.js는 /_next/static 에서 서빙되는 불변 assets을 클라이언트로 보낼 때 자동으로 caching 헤더를 붙인다.

1
+
Cache-Control: public, max-age=31536000, immutable
+

다른 값을 사용하고 싶다면 next.config.js에서 설정할 수 있다. 만약 캐시를 revalidate 시키고 싶다면 getStaticProps에서 revalidate를 설정하면 된다.

next dev로 실행할 경우엔 자동으로 다음과 같은 값으로 설정되어 캐싱되지 않는다.

1
+
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
+

next/image별로의 캐싱 규칙이 존재한다.

getServerSideProps나 API Route에서도 caching 헤더를 사용할 수 있다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+
export async function getServerSideProps({ req, res }) {
+  res.setHeader(
+    'Cache-Control',
+    'public, s-maxage=10, stale-while-revalidate=59'
+  )
+
+  return {
+    props: {},
+  }
+}
+

기본적으로는 Cache-Control은 경우에 따라 다르게 설정된다,

  • getServerSideProps, getInitialProps 를 사용하는 페이지 : next start로 설정된 디폴트 Cache-Control 사용하여 캐싱되는 것을 막는다.
  • getStaticProps
    • revalidate가 설정되있지 않으면, s-maxage=REVALIDATE_SECONDS, stale-while-revalidate
    • revalidate가 설정되어있으면, s-maxage=31536000, stale-while-revalidate


Javascript 사이즈 줄이기

다음과 같은 툴을 사용하면 Javascript 번들 사이즈를 줄이는데 도움이 된다.

/pages 디렉터리에 있는 파일들은 next build시 자동으로 코드가 분리되어 번들된다. Dynamic Imports`를 사용해 lazy-load를 적용할 수도 있다.


Loading Performance

Core Web Vitals를 사용하여 loading performance를 개선할 수 있다. 개선해야할 점이 보이면 다음과 같은 전략을 취할 수 있다.

  • 데이터페이스와 API가 배포되는 곳에서 가까운 지역을 caching region으로 삼아라
  • stale-while-revalidate를 적절히 사용하라
  • Incremental Static Regeneration를 사용해서 백엔드로 보내는 요청량을 줄여라
  • 필요없는 Javascript를 지워서 JS 번들 사이즈를 줄여라
This post is licensed under CC BY 4.0 by the author.

next.js - Script 컴포넌트

next.js - Deployment

Comments powered by Disqus.

diff --git "a/posts/next.js-\355\224\204\353\241\234\354\240\235\355\212\270-\354\265\234\354\240\201\355\231\224-\352\263\274\354\240\225/index.html" "b/posts/next.js-\355\224\204\353\241\234\354\240\235\355\212\270-\354\265\234\354\240\201\355\231\224-\352\263\274\354\240\225/index.html" new file mode 100644 index 000000000..c4caa2bf6 --- /dev/null +++ "b/posts/next.js-\355\224\204\353\241\234\354\240\235\355\212\270-\354\265\234\354\240\201\355\231\224-\352\263\274\354\240\225/index.html" @@ -0,0 +1,237 @@ + next.js 최적화과정 | 디피의 개발일지
Posts next.js 최적화과정
Post
Cancel

next.js 최적화과정

발단

서비스를 운영하던 도중 CPU/Memory 사용량은 낮은데 사용자 응답이 매우 느려지는 상황이 발생하였다. 서비스는 쿠버네티스로 띄워져있었고, HPA metric으로 CPU와 memory가 걸려있었는데, CPU와 memory가 올라가지 않으니 scale-out도 발생하지 않아 적은 pod으로 계속 서비스 되고 있었다. 수동으로 pod을 3~4배 가량 늘려 대응은 하였으나 추후에도 발생할 이슈일 것으로 보여 대응을 하기로 했다.

테스트

이 이슈는 next.js로 띄워져있는 페이지 중 하나에 페이지에 갑자기 트래픽이 몰려 발생한 이슈였다. 따라서 당시 리얼환경과 비슷하게 환경 세팅을 하고 ngrinder를 사용하여 대규모 트래픽 테스트를 하였다. 이슈가 발생했던 페이지를 포함하여 다양한 페이지를 테스트한 결과는 다음과 같았다. (TPS가 낮음 -> 성능이 낮음)

  • 페이지 A (이슈가 발생했던 페이지) : TPS 매우 낮음.
  • 페이지 B : TPS 낮음
  • 페이지 C : TPS 중간
  • 페이지 D : TPS 높음
  • 페이지 E : TPS 매우 높음

해당 페이지들은 다음과 같은 특징을 가지고 있다.

  • 페이지 A (TPS 매우 낮음.)
    • 동적 페이지
    • API 소스 A’로의 요청 2개, 소스 B’로의 요청 1개
    • API 소스 A’는 C’로부터 데이터를 중개해주는 역할을 함.
  • 페이지 B (TPS 낮음)
    • 동적페이지
    • API 소스 B’로의 요청 1개
  • 페이지 C (TPS 중간)
    • 동적페이지
    • API 소스 A’로의 요청 2개
    • 이 A’로 간 API 두개는 A’가 DB로부터 바로 데이터를 가져옴.
  • 페이지 D (TPS 높음)
    • 동적페이지
    • API 요청 없음
  • 페이지 E (TPS 매우 높음)
    • 정적페이지

이 결과들을 보고 내린 결론은 다음과 같다.

  • 성능 저하가 크지 않은 경우
    • next.js 내에서만 연산이 이루어지는 경우 (페이지 D, E)
    • A’로 보내는 API 중 A’가 스스로 처리하는 API를 사용하는 경우 (페이지 C)
  • 성능 저하가 큰 경우
    • A’로 보내는 API 중 A’가 중개 API 로써 동작하는 경우 (페이지 A)
    • B’로 API를 요청하는 경우 (페이지 A, B)

최적화

아래 두 전략을 가지고 최적화를 진행하였다.

  1. API 요청을 최소화하기
  2. 요청량을 기준으로 HPA metric을 산정하기

자세한 내용은 다음과 같다.

API 요청 최소화하기

기본적으로 API 요청이 발생하는 페이지에서 성능저하가 발생하였다. 따라서 API 요청을 최소화하는게 최우선 목표라고 생각하였다.

불필요한 호출 줄이기

가장 성능이 안좋았던 페이지 A는 A’로 API 요청을 2개 보내고, B’로 1개의 요청을 보낸다. A’로 보내는 두개의 요청은 기존에는 다음과 같이 코드가 짜여있었다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+
export const getServerSideProps = async(context) => {
+	...
+	const userProfileData = await getUserProfileData();
+	const userContentData = await getUserContentData();
+	...
+	return {
+		props : {
+			userData,
+			userContentData,
+		}
+	}
+}
+

getUserProfileData()getUserContentData()는 유저가 로그인 되어있든 안되어있든 항상 A’로 요청을 보낸다. 그리고 A’는 로그인 되어있지 않으면 null을 응답한다. 처음 이 코드를 짰을때는 성능에 대해 크게 걱정이 없어서 이렇게 항상 요청하는 식으로 짰다.(코드 조금 더 쓰기 귀찮았다..) 하지만 로그인 여부는 next.js 단에서도 API 요청 없이 알 수 있도록 설정이 되어있었다. 따라서 다음과 같이 next.js 에서 먼저 로그인 여부를 확인하고 로그인 되어있으면 요청하는 식으로 코드를 변경하였다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+
export const getServerSideProps = async(context) => {
+	const { isLoggedIn } = getAuthentication();
+	...
+	let userProfileData = null, userContentData = null;
+	if(isLoggedIn) {
+		userProfileData = await getUserProfileData();
+		userContentData = await getUserContentData();
+
+	}
+	...
+	return {
+		props : {
+			userData,
+			userContentData,
+		}
+	}
+}
+

이로써 미로그인 유저들이 불필요한 API 요청을 보내는 일이 없어졌다. 조금 더 개선을 하자면, getUserProfileData()getUserContentData()를 하나의 API로 합칠 수도 있으나 이건 API 개발자의 작업이니 이 글에선 자세히 작성하진 않겠다.

또 여기서 한번 더 최적화를 한다면 다음과 같이 Promise.all을 사용하여 요청을 동시에 보낼 수도 있다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+
export const getServerSideProps = async(context) => {
+	const { isLoggedIn } = getAuthentication();
+	...
+	let userProfileData = null, userContentData = null;
+	if(isLoggedIn) {
+		[userProfileData, userContentData] = await Promise.all([getUserProfileData(), getUserContentData()])
+	}
+	...
+	return {
+		props : {
+			userData,
+			userContentData,
+		}
+	}
+}
+

기존에는 getUserProfileData()의 응답시간 + getUserContentData()의 응답시간으로 최종 응답시간이 정해졌으나, Promise.all을 사용하면 둘 중 더 느린 응답시간으로 최종 응답시간이 결정되기 때문이다.

캐시 사용

다음은 B’로 보내는 요청을 최적화한 방법이다. B’는 콘텐츠를 가져오는 API로 기존에는 매요청마다 호출하고 있었다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+
export const getServerSideProps = async(context) => {
+	...
+	const contentData = await getContentData('page-a');
+	...
+	return {
+		props : {
+			contentData
+		}
+	}
+}
+

콘텐츠의 내용이 변경가능하여 항상 호출하였으나, 변경 사항을 즉시 반영해야할 심각도를 가진 콘텐츠는 아니었다. 즉 캐시를 사용하여 콘텐츠 변경의 반영이 어느정도 늦어져도 문제는 없었다. 따라서 memory-cache라는 in-memory cache 을 쉽게 사용할 수 있게 도와주는 라이브러리를 사용하기로 하였다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+
import cacheData from "memory-cache";
+
+export const getServerSideProps = async(context) => {
+	...
+	const key = "page-a"
+	let contentData = cacheData.get(key);
+	if (!contentData) {
+		contentData = await getContentData(key);
+		cacheData.put(key, contentData, 60000);
+	}
+	...
+	return {
+		props : {
+			contentData
+		}
+	}
+
+}
+

이렇게 두가지만 적용한 후, 다시 페이지 A를 ngrinder로 테스트한 결과는 다음과 같았다.

  • 미로그인 유저 -> TPS 15배 상승
  • 로그인 유저 -> TPS 2~3배 상승 로그인 유저의 경우 미로그인 유저에 비해 상승량은 적었지만 기존보다는 크게 상승했다.

HPA metric을 요청량 기준으로 변경

이번 이슈를 겪으면서 알게 된 것은 CPU, Memory 사용량이 많아져서 응답이 느리게 갈 가능성은 적다는 것이었다. 정적페이지 및 API 요청 없는 동적페이지의 경우 매우 빠르게 처리하고 있었고, pod 개수도 많아서 예상되는 최대치의 트래픽이 들어오더라도 CPU, Memory 사용량이 많이 점유되는 가능성은 적었다. 반대로 API 요청이 발생하는 페이지에서는 예상보다 훨씬 적은 트래픽이 들어오더라도 터지는 일이 발생하였다.

따라서 서버 응답 장애가 발생한다면, CPU/Memory 사용량은 낮지만 API 요청이 발생하는 페이지에 요청이 몰려 발생할 확률이 훨씬 컸다.

이에따라 기존에는 HPA metric을 다음과 같이 CPU/Memory 기준으로 작성하였는데, network 요청량이 일정 수준이상이 되면 발생하도록 변경하기로 하였다. (아래 yaml 파일들은 실제 파일이 아닌 예시)

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+
# 기존 hpa 설정
+apiVersion: autoscaling/v1
+kind: HorizontalPodAutoscaler
+metadata:
+    name: next-app-hpa
+    namespace: default
+spec:
+    scaleTargetRef:
+        apiVersion: apps/v1
+        kind: Deployment
+        name: next-app
+    minReplicas: 1
+    maxReplicas: 10
+    targetCPUUtilizationPercentage: 50 # CPU가 50%로 맞춰지도록 스케일링 발생
+    targetmemoryutilizationpercentage: 50 # Memory가 50%로 맞춰지도록 스케일링 발생
+
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+
# 변경한 hpa 설정
+apiVersion: autoscaling/v2beta2
+kind: HorizontalPodAutoscaler
+metadata:
+    name: next-app-hpa
+    namespace: default
+spec:
+    scaleTargetRef:
+        apiVersion: apps/v1
+        kind: Deployment
+        name: next-app
+    minReplicas: 1
+    maxReplicas: 10
+    metrics:
+        - object:
+              describedObject:
+                  apiVersion: v1
+                  kind: Service
+                  name: test
+              metric:
+                  name: test_requests_per_second
+              target:
+                  type: Value
+                  value: 200 # 초당 200번 이상 요청이 들어올 시 scale-out 발생
+          type: Object
+        - resource:
+              name: cpu # CPU 설정도 혹시 모르니 남겨둠(CPU가 터지는 상항이 생길 수 있으니..)
+              target:
+                  averageUtilization: 50
+                  type: Utilization
+          type: Resource
+

참고

다시 ngrinder로 테스트를 하니 scale-out 이 정상적으로 동작하였다. 이 두가지 방법을 적용하여 최적화를 완료하였고, 이후 트래픽이 많이 발생하여도 정상적으로 대응 가능하였다.

This post is licensed under CC BY 4.0 by the author.

next.js caching

실용주의프로그래머 1장 실용주의 철학

Comments powered by Disqus.

diff --git a/posts/nextjs-case-study/index.html b/posts/nextjs-case-study/index.html new file mode 100644 index 000000000..569f0378b --- /dev/null +++ b/posts/nextjs-case-study/index.html @@ -0,0 +1,125 @@ + 성능 최적화를 위한 next.js case study | 디피의 개발일지
Posts 성능 최적화를 위한 next.js case study
Post
Cancel

성능 최적화를 위한 next.js case study

원글에서는 TMDB의 클라이언트 웹 어플리케이션을 만들고, 여러번의 실험을 거쳐 성능 최적화에 달성하는 과정을 서술하였습니다.

이 글은 기본적으로 원글을 학습하면서 제가 읽기 편한 방식으로 번역/정리한 글입니다. 정확한 내용을 알고 싶으시면 원글을 보시는 걸 추천드립니다.


용어 정의

  • FCP : First Contentful Paint. 사용자가 화면에서 콘텐츠를 볼 수 있는 페이지 로드 타임라인의 첫 번째 지점.
  • LCP : Largest Contentful Paint. 뷰포트에서 가장 큰 콘텐츠 엘리먼트가 나타날때 까지 걸린 시간
  • TTI : Time To Interactive. 상호작용까지의 시간
  • TBT : Total Blocking Time. 총 차단 시간. 메인 스레드가 입력 응답을 막을 만큼 오래 차단되었을 때, FCP와 TTI 사이 총 시간을 측정
  • CLS : Cumulative Layout Shift

사용 툴

  • Lighthouse
  • WPT : https://www.webpagetest.org/webvitals


Packages Switched

앱을 만들 때 사용한 서드파티 리액트 컴포넌트를 분석하여, 무겁거나 메인 스레드를 blocking 하는 컴포넌트들은 다른 컴포넌트로 대체하였다. 그 결과로 다양한 메트릭에서 좋은 결과를 냈다.

  • @svgr/webpackFont-awsome 대신에 사용하니, Speed Index는 34%, LCP는 23%, TBT는 51% 개선되었다.
  • react-burger-menu을 대체하고 resize-sicky-box로부터 resize-observer-polyfill을 제거하고 custom-built component를 사용하였다. 이는 번들 사이즈가 34.28KB 감소하는 결과로 이어졌다.
  • React Select 대신 React Select Search를 사용하니 LCP 35%, CLS 100%, TBT 18% 개선되었다.
  • React Slick 대신 React Glider를 사용하여 TBT 77% 개선되었다.
  • 브라우저 지원을 위해 native smooth scrolling 대신에 React Scrolling을 사용하였다.
  • React Rating 대신에 React Stars를 사용하여 TBT가 33% 개선되었다.

그럼 상세한 과정을 알아보자

SVG icon Library

처음에 편리함과 유명세 때문에 Font-Awesome 라이브러리를 사용하였다. 하지만, 라이브러리 로딩시 큰 transfer size 때문에 웹 페이지 로딩 속도에 악영향을 주어 Lighthouse 점수가 안좋게 나왔다.

따라서 @svgr/webpack으로 대체하고, 라이브러리 전체가 아닌 개별 아이콘을 import 하는 방식을 통해 성능을 개선하였다.

1
+2
+3
+4
+5
+6
+7
+8
+
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+ 
+// replaced by
+ 
+import HeartIcon from "public/assets/svgs/icons/heart.svg";
+import PollIcon from "public/assets/svgs/icons/poll.svg";
+import CalendarIcon from "public/assets/svgs/icons/calendar.svg";
+import DotCircleIcon from "public/assets/svgs/icons/dot-circle.svg";
+

Application menu

처음에 react-burger-menu를 사용하였으나, 분석해보니 번들 사이즈가 너무 컸다. 다양한 커스터마이징에 대응하기 위해 CSS 스타일이나 애니메이션이 많았기 때문이다. 하지만 그정도의 기능이 필요없었기에 커스텀하여 메뉴를 구현하였다. 이는 번들사이즈를 매우 크게 줄여주었다.

유저가 정렬 옵션을 선택하여 정렬하기 위해 react-select를 처음에 사용하였다. 하지만 react-select에서 제공하는 것만큼 다양한 기능은 필요없었고, 단순한 정렬 컴포넌트만 원했다. 따라서 react-select-search 컴포넌트를 사용하였다. 이로써 번들 사이즈카 크게 줄었고, lighthout 점수도 올랐다.

출연배우를 보여주기 위한 수평 glide를 위해 React-Slick을 사용하였으나, react-glider로 대체하여 번들 사이즈를 크게 줄였다.

The Scrollling Component

앱은 페이지네이션이 구현되어있다. 그리고 이전 페이지, 이후 페이지로 이동할 때마다 페이지의 상단으로 스크롤된다. 이 스크롤을 부드럽게 하기 위해 native smooth scroll 기능을 사용하였다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
window.scroll({
+  top: 0,
+  left: 0,
+  behavior: "smooth",
+});
+ 
+// and
+ 
+document.querySelector(`.${SCROLL_TO_ELEMENT}`)?.scrollIntoView({
+  behavior: "smooth",
+});
+

하지만 이 기능은 모든 브라우저에서 지원하진 않았다. 따라서 react-scroll 패키지를 사용하여 대응하였다. 이 패키지는 성능에 조그마한 악영향을 주었지만 같은 스크롤 기능을 구현하였다.

The Rating Component

React-rating은 동그라미, 별, 따봉 등 다양한 심볼로 커스터마이징하여 레이팅을 매길 수 있게 해준다. 하지만, 테스트 앱에서는 별만을 사용하기로 하였기에 react-stars로 대체하였다. 이로써 번들사이즈는 줄었으나 크게 줄진 않았지만, React-rating이 SVG 아이콘을 사용한 것에 비해, react-stars는 ★ 심볼을 사용하여 성능에 크게 영향을 주었다. Lighthouse 검사 결과 TBT가 33% 줄었다.


다른 기술들

Code-Splitting

유저가 정말 필요할때만 Menu가 보이도록 Menu 컴포넌트를 lazy-loading 하기 위해 코드를 분리하였다. 따라서 페이지 로딩 후 메뉴 컴포넌트 로딩을 보장하는 LazyLoadingErrorBoundary를 사용하였다. 이로써 FCP, LCP는 그대로지만, TBT는 71% 개선되었다.

Inline the Critical, Defer the Non-Critical

CSS는 render-blocking 리소스이다. 페이지가 렌더되기 전에 반드시 로드되고 프로세스되어야한다. 일부 CSS는 최초 페이지에 보여야한다. 이것들은 Critical CSS이다. 하지만 천천히 로드해도 되는 CSS들도 있다.

next.js 문서에 따라 node module CSS 파일을 /pages/_app.js에 import 했다. react-gliderreact-modal-video가 CSS import를 요구했기 떄문이다. 하지만 _app.js에 CSS import 하는 것은 render-blocking을 일으킨다. 하지만, 해당 CSS들은 모든 페이지에서 필요한 것이 아니다.

따라서 이 컴포넌들이 사용하는 CSS는 컴포넌트가 사용되는 곳에 inline으로 처리하였다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+
<div ref="{ref}" className="cast">
+  <Glider
+    hasArrows
+    slidesToShow="{slidesToShow}"
+    slidesToScroll="{1}"
+    itemWidth="{GLIDER_ITEM_WIDTH}"
+  >
+    {cast.map(person => (
+    <PersonLink key="{person.id}" person="{person}" baseUrl="{baseUrl}" />
+    ))}
+  </Glider>
+</div>
+<style jsx>
+  {`
+  /*CSS Classes required for Glider*/
+  `}
+</style>
+

이 변화로, FCP, LCP, TTI에 2~5%의 성능향상이 있었다. 전체 성능도 79%에서 81%로 증가하였다.

Aspect Ratio for Images

Lighthouse에서 CLS에 대한 문제점을 짚어주었다. 어떤 이미지가 CLS을 일으키는지도 알려주었다. 3G와 같은 환경에서는 특히 문제가 될 수 있기에 해결하기로 했다.

aspect-ratio-boxes 기법를 사용하여 이미지의 aspect ratio를 지정하였다. 이로써 페이지가 로딩중이라도 이미지가 요구하는 공간을 충분히 확보하여, 이미지가 로드되었을 때 CLS가 일어나지 않도록 하였다. 이로써 CLS가 0.016에서 0으로 줄어들었다.

테스트 앱을 만든 후 CSS aspect ratio의 브라우저 호환성이 향상되어 잘 작동한다. 따라서 이 기능을 사용하는 것도 좋다.

Preconnects

preconnects를 사용하여 브라우저에게 어떤 리소스를 사용할 것인지 힌트를 줄 수 있다. rel=preconnect를 설정하면 브라우저에게 해당 도메인과 연결을 맺으라고 알려주어 이후 과정을 빠르게 할 수 있다. 이로써 리소스를 빠르게 가져올 수 있다.

1
+2
+
<link rel="preconnect" href="https://image.tmdb.org" />
+<link rel="preconnect" href="https://api.themoviedb.org" />
+

차이는 작지만 다음과 같이 조금 더 빨라진 모습을 볼 수 있다.

Performance MetricFCPSpeed IndexLCPTTITBT
Before0.93.93.432.9360
After0.833.52.862.6353.33
% Change7.7710.2516.6110.236.67

Optimize the API call sequence

여러 API를 호출할 때, 화면을 그리는데 필요한 API는 다른 API에 의해 늦춰지면 안된다. 따라서 다음과 같은 개선를 줄 수 있다.

BEFOREAFTER
장르나 설정같은 메타데이터를 불러오기 위한 API 호출 때문에 영화 포스터를 가져오는 API 요청이 put off 됨메타데이터와 영화 포스터를 동시에 가져오도록 함
영화 포스터 데이터를 fetch함홈화면을 영화 포스터 데이터와 함께 렌더함

Preloading API response

유저가 처음 접속했을 때, Popular 장르와 첫번째 페이지를 보여준다는 것이 정해져있음. 따라서 처음 접속한 유저의 API 요청 옵션은 다음과 같음 Genre="popular" page=1

이에 기반하여 홈화면에 사용할 데이터를 preload할 수 있다.

1
+2
+3
+4
+5
+6
+
<link
+  rel="preload"
+  as="fetch"
+  href="https://api.themoviedb.org/3/movie/popular?api_key=844dba0bfd8f3a4f3799f6130ef9e335&page=1"
+  crossorigin="true"
+/>
+

하지만 이 데이터가 사용되지 않으면 불필요한 네트워크 비용이 발생하니 잘 사용하자.

Preloading the logo and the TMDB trademark

모든 페이지에서 보이는 logo와 TMDB tademark를 preloading하여 FCP와 Speed Index를 5~6% 향상시켰다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+
<link
+  rel="preload"
+  href="{LOGO_IMAGE_PATH}"
+  as="image"
+  media="(min-width: 80em)"
+/>
+<link
+  rel="preload"
+  href="{DARK_TMDB_IMAGE_PATH}"
+  as="image"
+  media="(prefers-color-scheme: dark) and (min-width: 80em)"
+/>
+<link
+  rel="preload"
+  href="{LIGHT_TMDB_IMAGE_PATH}"
+  as="image"
+  media="(prefers-color-scheme: light) and (min-width: 80em)"
+/>
+


Making the site Responsive

SSR과 반응형 디자인을 섞는건 어려움이 있다.

  • 서버는 클라이언트의 window 객체를 모른다. 따라서 window.matchmedia()와 같은 메서드는 사용할 수 없다. 거기다 client hints 또한 모든 브라우저에서 지원되는 것은 아니다.
  • CSS media query는 클라이언트가 데스크톱인지 모바일인지 상관하지 않고 렌더링된다.

이러한 어려움을 극복하기 위해 @artsy/fresnel 라이브러리를 사용하였다. 이로써 CSS breakpoint로 DOM의 모든 엘리먼트를 서버에서 렌더할 수 있다. 그리고 그 breakpoint에 일치하는 컴포넌트만 마운트된다. 이 방식으로, 중복된 마크업과 불필요한 렌더링을 방지했다.

PERFORMANCE PARAMETERFCP (S)SPEED INDEX (S)LCP (S)TTI (S)TBT (MS)
Before0.933.732.62.6360
After1.063.232.662.6663.33
% Change13.9713.42.31.145.55

@artsy/fresnel 번들이 포함되어 몇몇 지수에서는 성능이 악화되었으나, Speed Index는 빨라졌다. 마크업 코드를 줄일 수 있는 것도 좋은 trade-off 이다.


Ideas that did not help

Lighthouse의 피드백에 기반하여 몇가지 대책을 세웠으나 성능상 이점이 없었던 아이디어들 모음

  1. react-lazyload 패키지 대체
    • react-lazyload 패키지를 lazy loading image를 위해 사용하고 있었다. 이 패키지는 메인 스레드를 오래 사용하였다. 이것을 native iamge lazy-loading으로 대체하려 했다.
    • 그러나 TBT는 11배 증가한반면 LCP는 근소하게 감소했다. 이는 native image lazy loading은 뷰포트에 가까운 몇개의 이미지를 로드하고, react-lazyload는 뷰포트 안에 있는 이미지만을 로드하기 때문일 수 있다.
    • 최근에는 Next.js Image Component를 사용할 수도 있다.
  2. image dimension을 설정
    • Aspect Ratio for Images를 세팅하기 전에 CLS를 향상시키기 위해 image dimension을 설정하였다.
    • 하지만 aspect ratio 만큼 잘 동작하지 않았다.
  3. SSR
    • LCP를 감소시키기위해 SSR을 하였으나, 향상보단 퇴화가 있었다.
    • 이유는 페이지를 렌더링할때 필요한 영화 데이터와 이미지가 TMDB API를 통해 가져와지기 때문이다. 이는 서버 응답을 느리고 만들었다. 왜냐만 모든 API 요청/응답이 서버에서 이루어졌기 때문이다.


Ideas that might help

  1. responsive image를 preloading과 함께 구현 링크
  2. service worker를 이용한 캐싱
  3. 리덕스를 포함하여 부풀어진 _app.js 개선. landing일때 리덕스가 필요없는 페이지들도 있기 때문. 코드 분리를 통해 개선
  4. 리덕스 없이 SSR 구현, SSR Caching 사용
  5. 더 가벼운 패키지로 교체
  6. 서드파티 라이브러리를 로드하기 위해 React loading pattern을 사용하여 lazy-loading/code-splitting 기법 적용
  7. 히어로 사진과 같이 최초 몇개 이미지를 위해 Image post-processing 적용
  8. SVG loading spinner를 CSS animation으로 대체
  9. 내부적으로 Javscript를 사용하는 <Image /> 와 달리, HTML/CSS로 이루어진 가벼운 컴포넌트 사용


정리

성능 개선을 위해서 다음과 같은 사항을 고려해볼 수 있다.

패키지 변경

현재 사용하고 있는 패키지에 불필요한 기능까지 포함하고 있는지, 번들 사이즈는 어느정도인지, 상세 구현이 어떻게 되어있는지(SVG? symbol?) 등을 따져봐서 패키지를 좀 더 가볍고 빠른 것으로 대체할 수 있다.

코드 분리

유저가 최초로 랜딩할 페이지에서 필요없는 자원들을 가져오지 않도록 적절히 코드/리소스를 분리한다.

네트워크 요청 우선순위

API, 이미지 등을 불러올 때, 랜딩 페이지에서 우선적으로 보여야하는 것들을 다른 것들보다 느리게 불러오지 않도록 한다.ㄴ

CLS

CLS를 줄이기 위해 aspect ratio를 지정하거나, Next.js의 Image 컴포넌트를 사용할 수 있다.


출처

https://www.patterns.dev/posts/nextjs-casestudy

This post is licensed under CC BY 4.0 by the author.

Vite

next.js appDir

Comments powered by Disqus.

diff --git a/posts/node_ch3/index.html b/posts/node_ch3/index.html new file mode 100644 index 000000000..bd3004e99 --- /dev/null +++ b/posts/node_ch3/index.html @@ -0,0 +1,101 @@ + 노드 스터디 3장 | 디피의 개발일지
Posts 노드 스터디 3장
Post
Cancel

노드 스터디 3장

Node 3장

REPL 사용하기

  • 노드 콘솔은 REPL이라하는데 이유는

    • Read : 입력한 코드를 읽고
    • Eval : 해석하고
    • Print : 결과물을 반환하고
    • Loop : 종료할 때가지 반복함.
  • 터미널에 node를 입력함으로서 접속가능
  • 간단한 명령어 수행

JS 파일 실행

  • helloworld.js 라는 파일을 만들었으면 node helloworld 로 접근할 수 있음

모듈로 만들기

  • 노드는 코드를 모듈로 만들 수 있다는 점에서 브라우저의 자바스크립트와는 다르다.

    • 크롬 60버전부터는 브라우저에서도 모듈을 사용할 수 있음
  • require(‘./var’); 로 불러오고, module.exports = 로 export함

    • 이때, 다른이름으로 불러올 수 있다.
    • 예를들어, module.exports = addTwoNum을 const addTwo = require(‘./temp’); 로 불러올 수 있음.
    • 단, 이때 ES6 이상의 문법에서 비구조화와 헷갈리지말자.
  • 자바스크립트 자체 모듈

    • import {odd, even} from ‘./var’;
    • export defualt checkOddOrEven
    • 이런 식으로 함.
    • export : 함수, 객체, 원시값을 내보낼때 사용
      • 받을땐 import {a, b, c, d} from ‘./temp’ 로 받음
    • exports : 함수, 객체, 원시값을 객체의 형태로 내보낼 때 사용
      1
      +2
      +3
      +4
      +5
      +
      exports.a = "a";
      +exports.b = "b";
      +//이렇게 내보내고
      +import importTemp from "./temp";
      +//이렇게 받음. 이름은 달라도 됨. exports 객체를 받는 것이기에
      +
    • exports default : 파일내에서 하나의 고정된 값만 내보낼때 사용

노드 내장객체

  • global
    • 브라우저의 window와 같은 전역객체
    • 모든 파일에서 접근가능
    • 이때 부를땐 global을 생략하고 부를 수있다.
      • ex) globa.require() -> require()
    • global 객체의 속성에 값을 대입하여 파일간 데이터를 공유할 수 있지만, 남용하지는 마라. 프로그램이 커질수록 어디서 넣었는지 찾기 힘들어져 유지보수가 어려워진다. 모듈형식으로 사용하라
  • console
    • console.log()
    • console.error()
    • console.time() - console.timeEnd()
      • time()부터 timeEnd()까지의 시간 출력
      • 이때 양쪽에 같은 레이블을 넣어, 레이블별로 구분가능하다.
    • console.table([{name:’제로’, birth:1994}, { name: ‘hero’, birth: 1988}])
      • 이런식으로 테이블을 만들 수도 있음
    • console.dir()
      • 객체를 출력하는데, 두번째 인자로 옵션을 넣어서 출력가능
    • console.trace()
      • 에러위치추적
  • 타이머
    • setTimeout(callback, milisec) : 주어진 milisec 이후에 콜백 함수 실행
    • setInterval(callback, milisec) : 주어진 milisec마다 콜백 실행
    • setImmediate(callback) : callback을 즉시 실행. 큐에있는 콜백이 실행 후 실행하도록 일단 큐에 넣는 것.
    • clearTimeout(id) : setTimeout에서 반환한 아이디를 넣어, 종료시키는 것
    • clearInterval(id) : 마찬가지
    • clearImmediate(id) : 마찬가지
    • setImmediate vs setTimeout(callback, 0)
      • 우선 setTimeout(callback, 0) 는 사용하지 않는걸 권장
      • 파일시스템 접근, 네트워킹 등 I/O 작업의 콜백함수 안에서 타이머를 호출하는 경우 setImmediate가 먼저 호출됨.
  • __filename, __dirname
    • 현재 파일의 경로나 파일명을 알아야할때 사용
    • console.log(__filename); 을 통해 현재 파일 경로를 알려줌
    • __dirname은 폴더까지만
    • 구분자 문제가 있어 보통은 path 모듈을 함께 사용함
  • module, exports, require
    • module.exports 대신 exports 단독으로 쓰일수있다.
      • exports.odd = “홀수입니다”
    • module.exports와 exports는 같은 객체, exports.add 이런식으로 exports객체를 만들면, 이것이 module.exports로 들어감.
      • 따라서 한 모듈에 두개 동시에 쓰지말자
    • require.cache : 한번 require한 파일이 저장됨. 다시 require할땐 여기있는 것을 불러옴.
    • require.main : 노드 실행시 첫모듈을 가리킴.
    • 순환참조가 발생할 경우, 대상을 빈 객체로 만듬.
  • this
    • 최상위 스코프에 있는 this는 module.exports 객체를 가리킴.
    • 함수 선언문 내부의 this는 global 객체를 가리킴
  • process
    • 현재 실행되고 있는 노드 프로세스의 정보를 담고 있음.
    • process.env
      • 시스템 환경변수
      • 비밀번호나 각종 API 키를 코드에 직접입력하는 대신 .env(dotenv)에 입력함
      • 코드에서 참조할땐 process.env.SECRET_KEY로 접근
    • process.nextTick(callback)
      • 이벤트루프가 다른 콜백함수보다 nextTick의 콜백함수를 우선으로 처리하도록 함.
    • process.exit(코드)
      • 0이면 정상 1이면 비정상
      • 프로세스 종료

노드 내장 모듈

  • os : os 관련 정보 및 에러와 신호에 대한 정보를 담음.
  • path : 폴더와 파일의 경로를 쉽게 조작하도록 도와주는 모듈
    • 운영체제별로 경로 구분자가 다르기에 필요함.
    • path.parse(경로) : 파일 경로를 root, dir, base, ext, name으로 분리함.
    • path.format(객체) : path.parse()한 객체를 파일경로로 합침.
    • path.normalize(경로) : 구분자를 실수로 사용했을때 정상적인 경로로 변환함.
  • url : 인터넷주소를 쉽게 조작하도록 도와주는 모듈
    • 두가지방식이 있음
      • WHATWG 방식의 url: 노드 버전 7부터 추가됨.
      • classic url
    • url 생성자 불러오기
      1
      +2
      +3
      +
      const url = require("url");
      +const { URL } = url; // WHATWG 방식의 생성자 들어감.
      +const myURL = new URL("https://naver.com");
      +
    • WHATWG 방식은 search부분을 searchParams로 반환하므로 편하다.
      • searchParams에서 여러 메소드가 존재함. 잘 찾아서 사용.
  • querystring
    • const qs = require(‘querystring’);
    • search 부분을 사용하기 쉽게 객체로 만드는 모듈
    • querystring.parse(쿼리) : url의 query부분을 넣고 객체로 분해함.
    • querystring.stringify(객체) : 분해된 query 객체를 문자열로 다시 조립함.
  • crypto
    • 다양한 방식의 암호화를 도와줌.
    • 단방향 암호화 알고리즘 == 복호화할수없는 암호화 == 해시함수
    • 해시기법 : 어떠한 문자열을 고정된 길이의 다른 문자열로 바꿔버림.
    • 사용법
      1
      +2
      +3
      +4
      +5
      +
      const crypto = require("crypto");
      +console.log(
      +  "base64",
      +  crypto.createHash("sha512").update("비밀번호").digest("base64")
      +);
      +
    • createHash(알고리즘) : 사용할 알고리즘을 넣음.
      • sha256, sha512
    • update(문자열) : 비밀번호
    • digest : 인코딩할 알고리즘을 넣음
      • base64, hex, latin1
    • 현재 주로사용하는 알고리즘 : pdkdf2, bcrypt, scrypt 등
    • pdkdf2 : 기존 문자열에 salt라고 불리는 문자열을 붙인 후에, 해시 알고리즘을 반복해서 적용
      1
      +2
      +3
      +4
      +5
      +6
      +7
      +8
      +
      //random으로 64바이트 문자열을 만듬.
      +crypto.randomBytes(64, (err, buf) => {
      +  const salt = buf.toString("base64");
      +  //비밀번호, salt, 반복횟수, 출력바이트, 해시알고리즘 순으로 넣음.
      +  crypto.pbkdf2("비밀번호", salt, 100000, 64, "sha512", (err, key) => {
      +    console.log("password:", key.toString("base64"));
      +  });
      +});
      +
      • 10만번 해도 1초밖에 안걸림. 내부적으로 스레드풀을 사용해 멀티스레딩을 하기에, 블로킹 걱정x
    • 양방향암호화
      • 암호화된 문자열을 복호화할수 있으며, 키를 사용함. 같은키를 사용해야 복호화가능
        1
        +2
        +3
        +4
        +5
        +6
        +7
        +8
        +9
        +10
        +11
        +
        const algorithm = "aes-256-cbc";
        +const key = "abcdefghijklmnopqrstuvwxyz123456";
        +const iv = "1234567890123456";
        +//암호화
        +const cipher = crypto.createCipheriv(algorithm, key, iv);
        +let result = cipher.update("암호화할 문장", "utf8", "base64");
        +result += cipher.final("base64");
        +//복호화
        +const decipher = crypto.createDecipheriv(algorithm, key, iv);
        +let result2 = decipher.update(result, "base64", "utf8");
        +result2 += decipher.final("utf8");
        +
      • 사용가능한 알고리즘 목록은 crypto.getCiphers()를 호출하면 볼수있음.
  • util
    • util.deprecate(func, message) = 함수가 deprecated처리됐음을 알림.
    • util.promisify(func) : 콜백 함수를 프로미스패턴으로 바꿈. async/await 가능, then/catch가능
  • worker_threads
    • 노드에서 멀티스레드로 작업하는 방법
    • const {Worker, isMainThread, parentPort} = require(‘worker_threads’) 로 불러옴
    • isMainThread 메인 스레드에서 실행되는지 구분.
    • 부모에서는 워커생성 후 worker.postMessage로 데이터를 보낼 수 있음.
    • 자식에선 parentPort.on(‘message’) 로 메세지를 받음
  • child_process
    • 다른 프로그램을 실행하고 싶거나 명령어를 수행하고 싶을때 사용함.
    • 이 모듈을 통해 다른 언어의 코드(파이썬 등)을 실행하고 결과값을 받을 수 있음.
    • exec : 셀을 실행해서 명령어를 수행
    • spawn : 새로운 프로세스를 띄우면서 명령어를 실행함.
  • 기타모듈들
    • assert: 값을 비교하여 프로그램이 제대로 동작하는지 테스트하는 데 사용합니다.
    • dns: 도메인 이름에 대한 IP 주소를 얻어내는 데 사용합니다.
    • net: HTTP보다 로우 레벨인 TCP나 IPC 통신을 할 때 사용합니다.
    • string_decoder: 버퍼 데이터를 문자열로 바꾸는 데 사용합니다.
    • tls: TLS와 SSL에 관련된 작업을 할 때 사용합니다.
    • tty: 터미널과 관련된 작업을 할 때 사용합니다.
    • dgram: UDP와 관련된 작업을 할 때 사용합니다.
    • v8: V8 엔진에 직접 접근할 때 사용합니다.
    • vm: 가상 머신에 직접 접근할 때 사용합니다.

파일시스템 접근하기

  • fs 모듈. 파일시스템에 접근하는 모듈. 파일생성/삭제/read/write 가능
  • read : fs.readFile(‘경로’, callback)
    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +
    const fs = require("fs"); //require('fs').promises; 로하면 프로미스형태로 사용가능.
    +fs.readFile("./readme.txt", (err, data) => {
    +  if (err) {
    +    throw err;
    +  }
    +  console.log(data); // 읽은 데이터는 버퍼형식으로 반환됨.
    +  console.log(data.toString()); // 읽을 수 없으므로 toString()으로 변환 시켜줌
    +});
    +
  • write : fs.writeFile(경로, 내용);

  • 동기 메소드와 비동기메소드

    • 동기메소드 : readFileSync, writeFileSync / 콜백함수대신에 직접 리턴값을 받아옴.
    • 비동기메소드 : readFile, writeFile / 명령이 실행되면 백그라운드에 해당파일을 읽으라고 요청하고 다음으로 넘어감.
    • 동기로 하게되면 요청이 처리되는 동안 메인스레드는 놀게됨.
    • 동기로 하되, 요청이 백그라운드에서 처리되도록 하려면, readFile을 사용하되 콜백안에서 다시 readFile을 실행하도록 한다. 이때 콜백지옥은 promises로 실행하면 해결할 수 있다.
  • 버퍼와 스트림

    • 노드는 파일을 일을때 메모리에 파일크기만큼 공간을 마련해두며 파일 데이터를 메모리에 저장한 뒤 사용자가 조작할 수 있도록 함.
    • 이때 메모리에 저장된 데이터가 버퍼
    • Buffer 클래스
      • 버퍼를 직접 다룰 수 있는 클래스
        1
        +
        const buffer = Buffer.from("저를 버퍼로 바꾸세요");
        +
      • .toString() : 문자열로 바꿈
      • .length : 길이
      • .concat() : 합치기
      • .alloc(바이트) : 빈 버퍼를 생성.
    • 버퍼의 단점 : 100MB 파일이 10개 필요하면 1GB 공간이 필요 -> 비효율 -> 따라서 버퍼의 크기를 작게만든 후 여러번으로 나눠보내는 방식이 탄생 -> 스트림
    • fs.createReadStream(path, option), fs.writeReadStream(path, option)

      • 나눠진 조각은 chunk라고 부름
      • option
        • highWaterMark : 버퍼의 크기를 정함.
      • readStream을 반환. 따라서 다음같이 작성
      1
      +2
      +3
      +4
      +5
      +6
      +7
      +8
      +9
      +
      const readStream = fs.createReadStream("./readme3.txt", {
      +  highWaterMark: 16,
      +});
      +const data = [];
      +  
      +readStream.on("data", (chunk) => {
      +  data.push(chunk);
      +  console.log("data :", chunk, chunk.length);
      +});
      +
      • 이벤트리스너
        • data : 읽기가 시작되면 방생
        • error : 읽다가 에러발생시
        • end : 다 읽으면
  • 기타 fs메소드

    • fs.access(경로, 옵션, 콜백) : 접근할 수 있는지
    • fs.mkdir(경로, 콜백)
    • fs.open(경로, 옵션, 콜백)
    • fs.rename(기존경로, 새경로, 콜백)
    • fs.readdir(경로, 콜백)
    • fs.unlink(경로, 콜백) : 파일을 지움
    • fs.rmdir(경로, 콜백) : 폴더를 지움
    • fs.watch(경로, 콜백) : 파일/폴더의 변경사항을 감시할 수 있음
      • change 이벤트는 두번 발생할 수 있으므로 주의
  • 스레드풀
    • 기본적인 스레드풀의 개수는 4개.
    • 처음 4개의 작업이 동시에 실행되고, 후에 4개가 실행
    • 스레드풀의 개수는 변경가능. 나중에 찾아서 함
    • 이때 스레드풀의 개수는 프로세서 코어의 개수와 같거나 많게 두어야 효과가 나옴

이벤트 이해하기

  • .on(‘data’, 콜백) 이렇게 이벤트리스너를 등록할 수 있음.
  • .addListener(이벤트, 콜백) 이것도 가능
  • .emit(이벤트) : 이벤트 호출
  • .removeListener(이벤트, 콜백) : 삭제
  • .removeAllListener(이벤트) : 이 이벤트에 연결된 모든 콜백 삭제
  • .listenerCount(이벤트)

예외처리

  • try-catch
  • 노드내장 모듈의 에러는 실행중인 프로세스를 멈추지 않음. 에러로그를 기록함.
  • throw : 발생시 노드 프로세스 중지. 반드시 try-catch로 잡아야함
  • 프로미스의 에러는 catch하지 않아도 알아서 처리됨. 그래도 일단 붙여라
  • 예측불가능한 에러
    • process.on(‘uncaughtException’, (err) => { console.log(‘예기치못한 에러’, err); }); 이렇게 이벤트로 잡으면 됨. 이건 최후의 수단으로 사용함. 에러를 기록한 후
This post is licensed under CC BY 4.0 by the author.
diff --git a/posts/node_ch4/index.html b/posts/node_ch4/index.html new file mode 100644 index 000000000..151c7ff4f --- /dev/null +++ b/posts/node_ch4/index.html @@ -0,0 +1,211 @@ + 노드 스터디 4장 | 디피의 개발일지
Posts 노드 스터디 4장
Post
Cancel

노드 스터디 4장

http 모듈로 서버 만들기

4.1 요청과 응답 이해하기

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+
- req : 요청에 관한 정보
+- res : 응답에 관한 정보.
+    - .writeHead    : 헤더. 응답에 대한 정보를 기록하는 메서드.
+        - 첫번째 인수 :  코드
+        - 두번째 인수 :  응답에 대한 정보를 알림.
+    - .write        : 본문(Body)
+        - 첫번째 인수 - 클라이언트로 보낼 데이터. 여러번 호출해서 데이터를 여러개 보내도 됨.
+    - .end          : 응답을 종료함. 여기에도 데이터를 넣어서 보낼 수 있음.
+- 서버가 실행되면, 해당 포트번호에서 요청이 오기를 기다림
+- listen 메소드 : 포트번호와 콜백함수를 넣음.
+- 이벤트 리스너 : 서버에 이벤트리스너를 등록할 수 있음. ex) server.on('listening', () => {}) 으로 함.
+    - 'listening' : 연결됐을때 발생함. listen 메소드의 콜백함수 대신에 사용해도 됨.
+    - 'error'   : 서버에 에러가 발생했을 때 발생.
+

4.1 기타

1
+2
+3
+4
+5
+6
+7
+8
+
- 코드
+    - 2xx : 성공
+    - 3xx : 리다이렉션
+    - 4xx : 요청 오류
+    - 5xx : 서버오류
+- 포트번호 : 80번을 사용하면 주소에서 포트를 생략할 수 있음. https 일경우 443번
+    - 실제로 배포할 땐 80번 혹흥 443번 포트를 사용
+- 응답은 무조건 보내야한다. 아니면 클라이언트는 하염없이 기다리게 됨.
+

4.2 REST 와 라우팅 사용하기

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+
- REST : REpresentational State Transfer. 서버의 자원을 정의하고 자원에 대한 주소를 지정하는 방법. 일종의 약속
+    - 주소뿐만 아니라 GET, POST, PUT, PATCH, DELETE, OPTIONS 등의 메서드와 함께 사용됨.
+    - GET : 자원을 가져올 때. 요청의 본문에 데이터를 넣지 않음. 쿼리 데이터로 보냄.
+        - 브라우저에서 캐싱할 수 있으므로 같은 주소로 보낼때 성능이 좋아짐.
+    - post : 자원을 등록할 때. 요청의 본문에 새로 등록할 데이터를 넣어보냄
+    - PUT : 서버의 자원을 요청에 들어있는 자원으로 치환할때 사용. 본문에 데이터를 넣음
+    - PATCH : 서버의 자원의 일부만 수정하고자할 때 사용. 본문에 데이터를 넣음
+    - DELETE : 서버의 자원을 삭제하고자 할 때 사용. 데이터를 넣지 않음
+    - OPTIONS : 요청을 하기 전에 통신 옵션을 설명하기 위해 사용.
+    - 만약 로그인 같은 애매한 동작이 있다면 그냥 POST 하면 된다.
+
+- 이렇게 REST 방식으로 하게 되면, 클라이언트에 구애받지 않고 사용할 수 있어서 좋다.
+
+- RESTful 서버구조
+
HTTP 메서드주소역할
GET/restFront.html 파일 제공
GET/aboutabout.html 파일 제공
GET/users사용자 목록 제공
GET기타기타정적파일제공
POST/user사용자 등록
PUT/user/사용자id해당 id의 사용자 수정
DELETE/user/사용자id해당 id의 사용자 제거\
1
+2
+3
+4
+5
+6
+7
+8
+9
+
- 라우팅
+    - req.method 로 GET, POST 등 HTTP 요청 메소드를 구분할 수 있음
+    - req.url 로 요청한 url을 구분할 수 있음.
+
+- req.on('data', callback)
+    - 요청의 본문에 들어있는 데이터를 꺼내는 작업.
+    - req, res 는 내부적으로 스트림으로 되어있기에 요청/응답의 데이터가 스트림 형식으로 전달.
+    - 또 .on 에서 알수 있듯이 이벤트도 달려있음.
+    - 이때 받은 데이터는 문자열이므로 JSON 형식으로 만드는 JSON.parse 과정이 필요하다.
+

4.2 기타

1
+
- res.end 를 한다고 함수가 끝나지 않는다. 따라서 중간에 끝내야할땐 return res.end() 를 한다.
+

4.3 쿠키와 세션 이해하기

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+
- 클라이언트가 누구인지 구분하기위해, 서버는 응답을 할 때 쿠키를 생성하여 같이 보냄.
+- 쿠키는 유효기간이 있으며, 단순한 키-값 쌍임.
+- 서버로부터 쿠키가 오면 브라우저는 저장해뒀다가 다음에 요청을 보낼때 쿠키도 함께 보냄.
+- 서버는 동봉된 쿠키를 보고 누구인지 파악함.
+
+- req.headers.cookie 에 쿠키가 들어있음.
+- res.writeHead(200, {'Set-Cookie':'mycookie=test'; Expires= ${expires.toGMTString()}; HttpOnly; Path=/`,});
+    - 쿠키 작성해서 보내는 법은 이렇게. 옵션사이는 ; 로 구분
+    - Expires 옵션으로 언제 삭제할지 설정가능. 기본은 클라이언트가 종료될때까지.
+    - HttpOnly 옵션 : 자바스크립트에서 접근할 수 없도록 함.
+    - Path=URL : 쿠키가 전송될 URL을 특정. 기본은 /. 모든 URL에서 전송가능.
+    - Max-age=초 : 날짜대신 초를 입력할 수 있음.
+    - Secure : HTTPS 일 경우에만 쿠키를 전송함.
+    - Domain=도메인명 : 쿠키가 전송될 도메인을 특정
+
+- 세션 : 서버에 사용자 정보를 저장하고, 클라이언트와는 세션 아이디로만 소통함.
+    - 쿠키에 세션 아이디를 저장하고, 그 세션 아이디로 서버에 있는 정보를 접근하는 것.
+    - 꼭 쿠키를 쓰지 않아도 되는데, 대부분은 쿠키가 간단해서 쿠키를 쓴다. 이를 세션 쿠키라고 함.
+

4.3 기타

1
+2
+3
+4
+
- url 모듈
+    - url.parse(req.url) : 요청 url을 파스함
+- qs.parse(query) : 쿼리를 파스하여 JSON 형태로 바꿔줌.
+- encodeURIComponent() : 헤더에는 한글을 설정할 수 없으므로, 인코딩하는 메소드
+

4.4 https와 http2

1
+2
+3
+4
+
- https : 웹 서버에 SSL 암호화를 추가합니다.
+    - GET이나 POST 요청을 할 때 오가는 데이터를 암호화해서 중간에 다른 사람이 요청을 가로채더라도 내용을 확인할 수 없게함.
+    - 인증서를 인증기관에서 구입해야 사용가능.(Let's Encrypt 같은 기관에서 무료로 발급)
+    - 인증서가 있다면 다음과 같이
+
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+
const https = require("https");
+
+const fs = require("fs");
+
+https
+  .createServer(
+    {
+      cert: fs.readFileSync("도메인 인증서 경로"),
+      key: fs.readFileSync("도메인 비밀키 경로"),
+      ca: [
+        fs.readFileSync("상위 인증서 경로"),
+        fs.readFileSync("상위 인증서 경로"),
+      ],
+    },
+    (req, res) => {
+      res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
+      res.write("<h1>Hello Node!</h1>");
+      res.end("<p>Hello Server!</p>");
+    }
+  )
+  .listen(443, () => {
+    console.log("443번 포트에서 서버 대기 중입니다!");
+  });
+
1
+2
+
- http2 : SSL 암호화와 더불어 최신 HTTP 프로토콜인 http/2 를 사용할 수 있게함.
+    - 기존 1.1 보다 요청 및 응답 방식이 개선되고, 속도고 많이 개선 됨.
+

4.5 cluster

1
+2
+3
+4
+5
+6
+7
+8
+9
+
- cluster 모듈 : 기본적으로 싱글 프로세스로 동작하는 노드가 CPU 코어를 모두 사용할 수 잇도록 하는 모듈. 포트를 공유하는 노드 프로세스를 여러 개 둘 수 있어서, 요청이 많이 들어왔을 때 병렬로 실행된 서버의 개수만큼 요청이 분산되게 함.
+    -> 스레드가 아니라 프로세스임.
+
+- 단점 : 메모리를 공유하지 못함. / 세션을 메모리에 저장하는 경우 문제가 됨. -> 레디스로 해결
+
+- 마스터 프로세스 : 요청이 들어오면, 만들어진 워커 프로세스에 요청을 분배함.
+- 워커 프로세스 : 실질적인 일을 하는 프로세스
+
+- 실무에서는 pm2 등의 모듈로 cluster를 처리함.
+
This post is licensed under CC BY 4.0 by the author.
diff --git a/posts/node_ch5/index.html b/posts/node_ch5/index.html new file mode 100644 index 000000000..da6eeb64b --- /dev/null +++ b/posts/node_ch5/index.html @@ -0,0 +1 @@ + 노드 스터디 5장 | 디피의 개발일지
Posts 노드 스터디 5장
Post
Cancel

노드 스터디 5장

패키지매지저

package.json

  • 설치한 패키지의 버전 관리
  • 노드 프로젝트를 시작하기 전에 무조건 package.json부터 만들고 시작해야함.
  • npm init 으로 프로젝트 생성하면 만들어짐.
  • scripts
    • npm 명령어를 저장해두는 부분.
    • 저장된 명령어를 npm run 으로 실행한다.
  • package-lock.json
    • node_modules에 들어있는 패키지들의 정확한 버전과 의존관계가 담겨있음
  • npm install –save-dev [패키지]
    • 개발용패키지. 개발중에만 사용되고 빌드에선 안됨
  • 전역설치
    • 패키지를 다 추가하는게 아니고, 명령어를 사용하기위해 설치

패키지버전 이해

  • 세자리로 이루어짐. semetic versioning
  • 첫번째 : major - 0이면 개발중. 1 -> 2 이면 하위호환이 안될 정도로 변경됐을때
  • 두번째 : minor - 호환됨
  • 세번째 : patch - 새로운 기능 추가보단, 기존 기능에 문제가 있어서 수정한 것
  • ^, ~, >, < : 설치하거나 업데이트할때 어떤 걸 해야할지 알림.
    • ^ : minor 버전까지만 설치하거나 업데이트함.
    • ~ : patch 버전까지만
    • >, <, >=, <=, = : 초과, 미만, 이상, 이하, 동일.
    • @latest : 안정된 가장 최신
    • @next : 가장 최근 배포판. 알파나 베타버전도 받아짐.

기타 npm 명령어

  • npm update
  • npm uninstall
  • npm search
  • npm info
  • npm adduser : npm 로그인
  • npm whoami
  • npm logout
  • npm version
  • npm deprecate [패키지명] [버전] [메시지] : 자신의 패키지에만 적용가능.
  • npm publish : 자신이 만든 패키지를 배포
  • npm unpublish : 배포한 패키지 제거. 배포한지 72시간이 지나면 불가.
  • npm ci : package-lock.json에 기반하여 패키지 설치.
This post is licensed under CC BY 4.0 by the author.
diff --git a/posts/node_ch6/index.html b/posts/node_ch6/index.html new file mode 100644 index 000000000..185c067cd --- /dev/null +++ b/posts/node_ch6/index.html @@ -0,0 +1,379 @@ + 노드 스터디 6장 | 디피의 개발일지
Posts 노드 스터디 6장
Post
Cancel

노드 스터디 6장

Express 로 웹서버 만들기


6.1 익스프레스 프로젝트 시작

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+
const express = require("express");
+
+const app = express();
+app.set("port", process.env.PORT || 3000);
+
+app.get("/", (req, res) => {
+  res.send("Hello, Express");
+});
+
+app.listen(app.get("port"), () => {
+  console.log(app.get("port"), "번 포트에서 대기 중");
+});
+
  • app.set(‘port’, 값) 으로 포트 설정
  • app.set(키, 값) 으로 다른 정보도 저장 가능. 나중에 app.get(키) 로 가져올 수 있음
  • app.get(주소, 라우터) : 이 주소로 GET 요청이 들어올때 처리.
  • app.listen() : 포트에서 요청 대기하고 서버 실행.
  • res.send() : res.write(), res.end() 대응. res.status(200).send() 처럼 코드 등록 가능. 기본 200.
  • res.sendFile() : 파일을 대신 보낼 수 있음. html 파일을 보낼 때 사용
    • res.sendFile(path.join(__dirname, “/index.html”));
    • 이 코드는 path 모듈이 있어야함.

6.2 자주 사용하는 미들웨어

  • app.use() 로 등록함.
  • 요청과 응답을 조작하여 기능을 추가하거나, 나쁜 요청을 걸러냄. 아래처럼 req, res, next을 받는 콜백으로 등록함.
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+
app.use((req, res, next) => {
+  console.log("모든 요청에 다 실행됩니다.");
+  next();
+});
+app.use("/abc", (req, res, next) => {
+  // abc로 시작하는 요청에 실행
+  console.log("abc로 시작하는 요청에 실행됩니다.");
+  next();
+});
+app.get("/abc", (req, res, next) => {
+  // abc로 시작하는 get 요청에 실행.
+  console.log("abc로 시작하는 get 요청에 실행됩니다.");
+  next();
+});
+app.post("/abc", (req, res, next) => {
+  // abc로 시작하는 post 요청에 실행.
+  console.log("abc로 시작하는 post 요청에 실행됩니다.");
+  next();
+});
+app.get(
+  // 여러개 등록
+  "/",
+  (req, res, next) => {
+    console.log("GET / 요청에서만 실행됩니다.");
+    next();
+  },
+  (req, res) => {
+    throw new Error("에러는 에러 처리 미들웨어로 갑니다.");
+  }
+);
+app.use((err, req, res, next) => {
+  console.error(err);
+  res.status(500).send(err.message);
+});
+
  • 위 예에서 볼 수 있듯이, app.get, app.post 등과 같은 것들도 next를 인자로 받아 미들웨어로 활용할 수 있음.

  • 또 마지막 바로 위 예시에서처럼, 여러개를 등록할 수 있다. 여기선 에러가 발생하는데. 이렇게 에러가 발생시 그 아래에 에러처리 미들웨어에 전송됨.

  • 에러처리 미들웨어 : 매개변수가 반드기 네 개여야함. 가장 아래 위치/

  • 미들웨어는 위에서부터 아래로 순서대로 실행됨.

  • dotenv 패키지

    • .env 파일을 읽어 process.env로 만듬. process.end.COOKIE_SECRET으로 사용가능.

6.2.1 morgan

  • 요청과 응답에 대한 정보를 콘솔에 기록함.
  • app.use(morgan(‘dev’)) 로 등록
    • dev : 개발환경
    • combined : 배포환경
    • common, short, tiny 등도 있음

6.2.2 static

  • 정적인 파일들을 제공하는 라우터 역할.
  • 기본적으로 제공되기에 따로 설치할 필요는 없음
1
+2
+3
+
app.use("요청 경로", express.static("실제 경로"));
+
+app.use("/", express.static(path.join(__dirname, "public")));
+
  • 외부인이 서버 구조를 쉽게 파악하지 못하게하여 보안에 도움을 줌.
  • 요청경로에 해당하는 파일이 없으면, 알아서 next를 호출함.
  • 파일을 발견하면, 응답으로 파일을 보내고 next를 호출하지 않음.

6.2.3 body-parser

  • 요청의 본문(body)에 있는 데이터를 해석해서 req.body 객체로 만들어줌.
  • 이미지, 동영상, 파일 데이터는 처리하지 못함. (이건 multer로 해결)
  • 익스프레스 4.16.0 버전부터 express 에 내장되어있기에 따로 설치하지 않고 다음처럼 사용
1
+2
+3
+
app.use(express.json());
+app.use(express.urlencoded({ extended: false }));
+// false이면 내장된 querystring을 사용하고, true면 npm 패키지인 qs를 사용
+
  • 하지만, Raw, Text 형식의 데이터를 해석할 땐 따로 설치해줘야함.

  • app.use(cookieParser(비밀키));

  • 동본된 쿠키를 해석해 req.cookies 객체로 만듬.
  • 첫번째 인수로 비밀키를 넣어주어 서명된 쿠키가 있는 경우, 제공한 비밀키를 통해 해당 쿠키가 서버가 만든 쿠키임을 검증할 수 있음.
  • 서명된 쿠키는 req.signedCookies 객체에 들어있음.
  • 쿠키를 생성/제거할때는 res.cookie(키,값,옵션), res.clearCookie() 메소드를 사용
  • 쿠키를 지우려면 키와 값 외에 옵션도 정확히 일치해야 지워짐. 단, expire나 maxAge는 달라도 됨.
1
+2
+3
+4
+5
+6
+
res.cookie("name", "zerocho", {
+  expires: new Date(Date.now() + 900000),
+  httpOnly: true,
+  secure: true,
+});
+res.clearCookie("name", "zerocho", { httpOnly: true, secure: true });
+
  • 옵션중엔 signed 라는 옵션이 있는데, true로 설정하면 쿠키뒤에 서명이 붙음.
  • 대부분은 서명옵션을 켜두는 것이 좋음. 비밀키는 .env에 넣어두면 됨.

6.2.5 express-session

  • 세션 관리 시 클라이언트에 쿠키를 보냄.
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+
app.use(
+  session({
+    resave: false,
+    saveUninitialized: false,
+    secret: process.env.COOKIE_SECRET,
+    cookie: {
+      httpOnly: true,
+      secure: false,
+    },
+    name: "session-cookie",
+  })
+);
+
  • 인수로 세션에 대한 설정을 받음.
  • resave : 요청이 올 때 세션에 수정사항이 생기지 않더라도 세션을 다시 저장할지 설정함.
  • saveUninitialized : 세션에 저장할 내역이 없더라도 처음부터 세션을 생성할지 설정함.
  • express-session 1.5 버전 이전에는 cookie-parser 후에 둬야하는데, 이후 버전에서는 상관없음.
  • 서명을 하려면 secret 옵션에 키를 넣어준다. 이때 키는 cookie-parser의 키와 같게 한다.
  • cookie 옵션 : 세션 쿠키 옵션
  • store : 메모리에 세션을 저장할 것이냐
  • 이렇게 만든 req.session 객체에 값을 대입하거나 삭제해서 세션을 변경한다.
    • req.session.name = ‘zerocho’
    • req.session.destory()

6.2.6 미들웨어의 특성 활용하기

  • next에 인수넣기
    • “route” : 다음 라우터의 미들웨어로 바로 이동함.
    • 이외의 인수 : err 미들웨어로 이동함. -> err 미들웨어의 err 매개변수가 됨.
  • 다음 미들웨어로 데이터 넘겨주기
    • req.data 에 넣어주면 된다.
    • 다른 이름과 겹치지 않게만 해주자.
  • 유용한 패턴 : 미들웨어 안에 미들웨어 넣기
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+
app.use(morgan("dev"));
+// 또는
+app.use((req, res, next) => {
+  morgan("dev")(req, res, next);
+});
+
+// 위 두개는 같은 작업을 함.
+// 아래처럼 할 시 유용하다.
+app.use((req, res, next) => {
+  if (process.env.NODE_ENV === "production") {
+    morgan("combined")(req, res, next);
+  } else {
+    morgan("dev")(req, res, next);
+  }
+});
+

6.2.7 multer

  • 이미지, 동영상을 비롯한 여러가지 파일들을 멀티파트 형식으로 업로드할 때 사용함.
  • 멀티파트 형식 : enctype이 multipart/form-data 인 폼을 통해 업로드하는 데이터의 형식
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+
const multer = require("multer");
+
+// 기본 설정
+const upload = multer({
+  storage: multer.diskStorage({
+    destination(req, file, done) {
+      done(null, "uploads/");
+    },
+    filename(req, file, done) {
+      const ext = path.extname(file.originalname);
+      done(null, path.basename(file.originalname, ext) + Date.now() + ext);
+    },
+  }),
+  limits: { fileSize: 5 * 1024 * 1024 },
+});
+
+//upload 에 있는 미들웨어 사용
+// single : 하나만 업로드하는 경우.
+app.post("/upload", upload.single("image"), (req, res) => {
+  console.log(req.file, req.body);
+  res.send("ok");
+});
+// html form에 multiple로 설정되어 여러가지가 오는 겨웅
+// array로 교체
+app.post("/upload", upload.array("many"), (req, res) => {
+  console.log(req.files, req.body);
+  res.send("ok");
+});
+
+//여러개를 업로드하지만, input 태그나 폼 데이터의 키가 다른 경우엔 fields 사용
+app.post(
+  "/upload",
+  upload.fields([{ name: "image1" }, { name: "image2" }]),
+  (req, res) => {
+    console.log(req.files, req.body);
+    res.send("ok");
+  }
+);
+
+// 파일을 업로드하지 않고, 멀티파트 형식으로 업로드하는 경우. none 사용
+app.post("/upload", upload.none(), (req, res) => {
+  console.log(req.body);
+  res.send("ok");
+});
+

6.3 Router객체로 라우팅 분리하기

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+
//routes/user.js
+const express = require("express");
+
+const router = express.Router();
+
+router.get("/", (req, res) => {
+  res.send("Hello, User");
+});
+
+module.exports = router;
+
+// app.js
+const userRouter = require("./routes/user");
+app.use("/user", userRouter);
+
  • 기본적으로 위와같이 분리하고, 연결하면 된다.
  • 이때 각 폴더의 index.js 파일은 생략가능.
  • 라우터는 같은 주소로 몇개를 불러도 next 만 제대로 호출해주면 된다.
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+
router.get(
+  "/",
+  function (req, res, next) {
+    next("route");
+  },
+  function (req, res, next) {
+    console.log("실행되지 않습니다");
+    next();
+  },
+  function (req, res, next) {
+    console.log("실행되지 않습니다");
+    next();
+  }
+);
+router.get("/", function (req, res) {
+  console.log("실행됩니다");
+  res.send("Hello, Express");
+});
+
  • :id 같이 매개변수를 받을 수 있는데, 같은 주소를 사용하는 라우터의 맨 뒤에 있어야함.
  • 아니면 이게 와일드카드처럼 다 잡아먹음
1
+2
+3
+4
+5
+6
+7
+
router.get("/user/:id", function (req, res) {
+  res.send("Hello, Express");
+});
+// 도달불가
+router.get("/user", function (req, res) {
+  res.send("Hello, Express");
+});
+
  • app.route 나 router.route 로 주소는 같지만 메서드가 다른 코드를 한 덩어리로 묶을 수 있음
1
+2
+3
+4
+5
+6
+7
+8
+
router
+  .route("/abc")
+  .get((req, res) => {
+    res.send("GET /abc");
+  })
+  .post((req, res) => {
+    res.send("POST /abc");
+  });
+

6.4 req, res 객체 살펴보기

  • express의 req, res 객체는 http 모듈의 req, res 객체를 확장한 것.
  • 확장된 것들

    • req
      • req.app: req 객체를 통해 app 객체에 접근할 수 있습니다. req.app.get(‘port’)와 같은 식으로 사용할 수 있습니다.
      • req.body: body-parser 미들웨어가 만드는 요청의 본문을 해석한 객체입니다.
      • req.cookies: cookie-parser 미들웨어가 만드는 요청의 쿠키를 해석한 객체입니다.
      • req.ip: 요청의 ip 주소가 담겨 있습니다.
      • req.params: 라우트 매개변수에 대한 정보가 담긴 객체입니다.
      • req.query: 쿼리스트링에 대한 정보가 담긴 객체입니다.
      • req.signedCookies: 서명된 쿠키들은 req.cookies 대신 여기에 담겨 있습니다.
      • req.get(헤더 이름): 헤더의 값을 가져오고 싶을 때 사용하는 메서드입니다
    • res
      • res.app: req.app처럼 res 객체를 통해 app 객체에 접근할 수 있습니다.
      • res.cookie(키, 값, 옵션): 쿠키를 설정하는 메서드입니다.
      • res.clearCookie(키, 값, 옵션): 쿠키를 제거하는 메서드입니다.
      • res.end(): 데이터 없이 응답을 보냅니다.
      • res.json(JSON): JSON 형식의 응답을 보냅니다.
      • res.redirect(주소): 리다이렉트할 주소와 함께 응답을 보냅니다.
      • res.render(뷰, 데이터): 다음 절에서 다룰 템플릿 엔진을 렌더링해서 응답할 때 사용하는 메서드입니다.
      • res.send(데이터): 데이터와 함께 응답을 보냅니다. 데이터는 문자열일 수도 있고 HTML일 수도 있으며, 버퍼일 수도 있고 객체나 배열일 수도 있습니다.
      • res.sendFile(경로): 경로에 위치한 파일을 응답합니다.
      • res.set(헤더, 값): 응답의 헤더를 설정합니다.
      • res.status(코드): 응답 시의 HTTP 상태 코드를 지정합니다.
  • req나 res 객체의 메서드는 다음과 같이 메서드 체이닝을 지원함.
1
+
res.status(201).cookie("test", "test").redirect("/admin");
+

6.5 템플릿 엔진 사용하기

  • 템플릿 엔진 : 자바스크립트를 사용해서 HTML 을 렌더링 -> 따라서 효율적인 HTML 작성을 하게 해줌.

  • 퍼그(pug)

    • 다음과 같은 코드가 있어야함.
    1
    +2
    +
    app.set("views", path.join(__dirname, "views"));
    +app.set("view engine", "pug");
    +
    • views 는 템플릿 파일들이 위치한 폴더를 지정하는 것.
      • res.render가 이 폴더를 기준으로 템플릿엔진을 찾아 렌더링함.
    • view engine은 어떠한 종류의 템플릿 엔진을 사용할지 나타냄.
    • 문법은 찾아보면서 하면 될듯.
    • 변수
      • res.render(주소, 변수) : 이렇게해서 변수를 전달할 수 있다.
        1
        +
        res.render("index", { title: "Express" });
        +
      • 변수를 넣는 대신에 res.locsls 객체로 넣을 수도 있음.
      • 이렇게하면 다른 미들웨어에서도 접근할 수 있어서 좋다.
        1
        +2
        +
        res.locals.title = "Express";
        +res.render("index");
        +
    • 반복문
      • each A, idx in [B, C, D]
      • 이런식으로 반복문을 돌릴 수 있다. 뽑는 두번째인자는 index
    • 조건문
      • if, else if, else 문으로 분기처리 가능.
      • case-when 문으로 switch-case 가능
    • include
      • 다른 퍼그나 HTML 파일을 넣을 수 있음.
    • extends와 block
      • 레이아웃을 정활 수 있음. 공통되는 레이아웃부분을 따로 관리할 수 있다. include와 함께 사용됨.
  • 넌적스

    • 퍼그와는 달리 HTML 문법을 크게 변화하지 않는다. HTML + js 느낌.
    • 연결방법은 다음과 같음
    1
    +2
    +3
    +4
    +5
    +6
    +7
    +
    const nunjucks = require("nunjucks");
    +app.set("view engine", "html");
    +
    +nunjucks.configure("views", {
    +  express: app,
    +  watch: true,
    +});
    +
    • 변수 : 퍼그와 똑같이 날림
    • 반복문 : for ~ in 으로 함
  • 에러처리 미들웨어 : 새로운게 아니라 그냥 배운 템플릿엔진으로 에러처리 미들웨어 구현하는 것.

This post is licensed under CC BY 4.0 by the author.
diff --git a/posts/node_ch7/index.html b/posts/node_ch7/index.html new file mode 100644 index 000000000..4b2d92d26 --- /dev/null +++ b/posts/node_ch7/index.html @@ -0,0 +1,299 @@ + 노드 스터디 7장 | 디피의 개발일지
Posts 노드 스터디 7장
Post
Cancel

노드 스터디 7장

MySQL

데이터베이스란

  • DBMS : 데이터베이스 관리시스템
  • RDBMS : 관계형 DBMS. 대표적으로 Oracle, mysql, mssql 등이 있음.

Datagrip 사용

  • Datagrip : 데이터베이스를 위한 IDE. 학생인증을 하면 무료사용가능
  1. 프로젝트 생성
  2. alt+insert 혹은 좌측 + 클릭 -> 원하는 데이터베이스 클릭
  3. 좌측 하단에 download 클릭
  4. 이후 유저이름, 비밀번호 생성 후 ok
  5. 좌측 상단 초록 화살표 클릭하여 connect
  • 오류
    • 내용 : Server returns invalid timezone. Need to set ‘serverTimezone’ property.
    • database의 property의 들어가서 advanced 탭을 클릭한 후, serverTimezone 의 값을 Asia/Seoul 로 변경한다.

데이터베이스 및 테이블 생성

  • 교재에서는 프롬프트로 진행
  • 데이터 베이스 생성 : CREATE SCHEMA [데이터베이스명]

    • 옵션 : DEFAULT CHARACTER SET utf8 -> utf8 셋을 사용하여 한글을 사용할 수 있게함.
    • SCHEMA 대신 DATABASE를 사용해도 됨.
    • Mysql에선 SCHEMA 와 DATABASE는 같은 말
  • 테이블 생성
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+
CREATE TABLE nodejs.users (
+  -> id INT NOT NULL AUTO_INCREMENT,
+  -> name VARCHAR(20) NOT NULL,
+  -> age INT UNSIGNED NOT NULL,
+  -> married TINYINT NOT NULL,
+  -> comment TEXT NULL,
+  -> created_at DATETIME NOT NULL DEFAULT now(),
+  -> PRIMARY KEY(id),
+  -> UNIQUE INDEX name_UNIQUE (name ASC))
+  -> COMMENT = '사용자 정보'
+  -> DEFAULT CHARACTER SET = utf8
+  -> ENGINE = InnoDB;
+
  • CREATE TABLE nodejs.users : 명령어 + 테이블 명. 이때 테이블 명은, 데이터베이스명.테이블명 으로 함.
    • use nodejs; 로 이 데이터베이스에 들어왔떈 데이터베이스명 생략가능.
  • 컬럼 선언 : () 안에 선언함. [컬럼명 자료형 옵션] 으로 구성

    • ex : id INT NOT NULL AUTO_INCREMENT

    • 자료형

      • INT / FLOAT / DOUBLE : 숫자
      • VARCHAR(자릿수) / CHAR(자릿수) : 문자열 저장
        • VARCHAR 은 가변길이로, 20이면 0~20까지 저장가능하지만, CHAR은 반드시 20을 채워야함.
      • TEXT : 긴 글을 저장할 때 사용. 수백자 이내면 VARCHAR로 처리하고, 이상이면 TEXT로 처리
      • TINYINT : -128 ~ 127 까지의 정수를 저장. 0과 1만 저장하면 BOOLEAN으로 활용 가능.
      • DATETIME : 날짜와 시간에 대한 정보를 담고 있음.
      • 이 외 많은 자료형들이 있지만 위에가 자주 사용하는 것들.
    • 옵션

      • NULL / NOT NULL : 빈칸을 허용할지 안할지.
      • AUTO_INCREMENT : 숫자를 자동으로 올림.
      • UNSIGNED : 숫자 자료형일때 사용가능. 음수를 무시하고 양수를 더 크게 받을 수 있음.
      • ZEROFILL : 숫자의 자릿수가 고정되어있을 때, 앞을 0으로 채움.
        • ex) INT(4) 인데 1을 넣으면 0001로 저장.
      • DEFAULT : 데이터베이스 저장시 해당 컬럼에 값이 없으면 MySQL이 기본 값을 대신 넣음.
        • 위에선 now()를 사용해서, 저장할 당시의 시간을 저장
        • now() 대신에 CURRENT_TIMESTAMP 를 사용해도 됨.
      • PRIMARY KEY : row를 대표하는 기본키일 경우 설정.
        • 컬럼을 다 적고, 위에서처럼 따로 적어도 된다.
      • UNIQUE INDEX : 값이 고유해야하는지 묻는 옵션. PRIMARY KEY는 자동으로 설정됨.
      • CONSTRAINT [제약조건명] FOREIGN KEY [컬럼] REFERENCES [참고하는 테이블명] [참고하는 컬럼명]
        • 외래키. 서로 연결하는 것.
        • ON UPDATE / DELETE : 연결한 것이 수정되거나 삭제될 시 하는 행위 정의
          • CASCADE로 하면 같이 수정하거나 삭제함.
    • 테이블 자체 옵션

      • 테이블 컬럼 작성이 끝나고 이후에 옵션으로 붙임.
      • COMMENT : 테이블에 대한 보출 설명
      • DEFAULT CHARACTER SET : utf8으로 설정하지 않으면 한글입력이 안됨.
      • ENGNED : MylSAM과 InnoDB를 제일 많이 사용함.
  • 테이블 확인 : desc [테이블명]
  • 테이블 제거 : drop table [테이블명]

CRUD 작업하기

  • Create : 그냥 데이터를 넣는 것.

    • insert into [테이블명] (컬럼, 컬럼) VALUES (값, 값);
  • read : SELECT (컬럼, 컬럼) FROM [테이블명]

    • 모든 컬럼을 조회하려면 * 사용
    • WHERE 로 특정 조건을 가진 데이터만 조회가능
      • EX) WHERE married = 1 AND age > 30;
    • ORDER BY [컬럼명] [ASCDESC]
      • 정렬 기능.
    • LIMIT [숫자]
      • 조회할 로우 개수 설정.
    • OFFSET [건널뛸 숫자]
      • 처음부터 몇개까지 건널 뛰고 출력할 건지 정할 수 있음.
      • 반드시 LIMIT 과 함께 쓰여야함.
  • Update : UPDATE [테이블명] SET [컬럼명=바꿀값] WHRER [조건]

    • 조건에 맞는 row의 해당하는 column을 수정할 수 있음.
  • Delete : DELETE FROM [테이블명] WHERE [조건]

    • 조건에 맞는 row를 삭제

시퀄라이즈 사용

  • ORM : 자바스크립트 객체와 데티어베이스의 관계를 매핑해주는 도구.
  • MySQL외 다른 MariaDB, PostgreSQL, SQLite, MSSQL 등 다른 데이터베이스도 같이 쓸 수 있음.
  • 자바스크립트 구문을 알아서 SQL로 바꿔줌. 따라서 SQL 언어를 직접 사용하지 않아도 자바스크립트 만으로 MySQL을 조작할 수 있고, 다룰 수 있음.

설치 후 초기화

  • 설치 : sequelize, sequelize-cli, mysql2

    • sequelize :
    • sequelize-cli : 시퀄라이즈 명령어를 실행하기 위한 패키지
    • mysql2 : mysql과 시퀄라이즈를 이어주는 드라이버
  • 설치 후 sequelize init 명령어로 초기화 -> config, models, migrations, seeders 폴더 생성

    • models/index.js에서 sequelize cli 가 자동으로 생성해주는 코드는 필요없는 부분이 많음. 따라서 다음처럼 정리 가능
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+
const Sequelize = require("sequelize");
+
+const env = process.env.NODE_ENV || "development";
+const config = require("../config/config")[env];
+const db = {};
+
+const sequelize = new Sequelize(
+  config.database,
+  config.username,
+  config.password,
+  config
+);
+db.sequelize = sequelize;
+
+module.exports = db;
+

MySQL 연결

  • 시퀄라이즈 연결
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
const { sequelize } = require("./models");
+...
+sequelize
+   .sync({ force: false })    // 서버 실행 시 mysql 연결. force:true 일 경우, 서버 실행 시 마다 테이블을 재생성함. 테이블을 잘못만든 경우 true로 설정
+   .then(() => {
+      console.log("데이터베이스 연결 성공");
+   })
+   .catch((err) => {
+      console.error(err);
+   });
+
+
  • MySQL과 연동할 땐, config 폴더 안의 config.json 정보가 사용됨.
    • operatorAliases가 있다면 삭제.
    • 나머지는 적절히 수정

모델 정의하기

  • 시퀄라이즈 모델 == MySQL 테이블
  • 시퀄라이즈는 모델과 MySQL 테이블을 연결해주는 역할을 함.
  • 보통 모델 이름은 단수형으로, 테이블이름은 복수형으로 함.
  • 모델 함수

    • static init
      • 테이블에 대한 설정을 함.
    • static associate
      • 다른 모델과의 관계를 적음.
  • 자료형 대응
MySQL시퀄라이즈
VARCHAR(100)STRING(100)
INTINTEGER
TINYINTBOOLEAN
DATETIMEDATE
INT UNSIGNEDINTEGER.UNSIGNED
NOT NULLallowNull: false
UNIQUEunique: true
DEFAULT now()defaultValue: Sequelize.NOW
  • 자세한 모델 정의/연결법은 강의에서 찾아보기 [https://thebook.io/080229/ch07/06/02-04/]

관계정의하기

  • 1:N - 시퀄라이즈에선 hasMany 라는 메서드로 표현됨.
    • belongsTo : 로우를 불러올대 연결된 테이브르이 로우를 가져오도록 함.
    • 어디에 belongsTo를 쓰는가 -> 다른 모델의 정보가 들어가는 테이블에 쓰면 됨.
    • hasMany에서는 sourceKey를 쓰고, belongsTo에서는 targetKey를 사용
  • 1:1 - hasOne 사용
  • N:M - belongsToMany
    • 양쪽에서 belongsToMany로 연결해주면 됨.
    • through 속성에 넣은 이름으로 새로운 모델을 만들어냄.

쿼리 알아보기

  • 쿼리는 프로미스를 반환하므로 then을 붙여서 결괏값을 받을 수 있다.
  • 대응

    • 주의점 : MySQL의 자료형이 아니라, 시퀄라이즈 모델에서 정의한 자료형대로 넣어야함. 시퀄라이즈가 자동으로 변경함.
  • 조회
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+
SELECT * FROM nodejs.users;
+User.findAll({});
+
+SELECT * FROM nodejs.users LIMIT 1;
+User.findOne({});
+
+SELECT name, married FROM nodejs.users;
+User.findAll({
+  attributes: ['name', 'married'],
+});
+
+SELECT name, age FROM nodejs.users WHERE married = 1 AND age > 30;
+const { Op } = require('sequelize');
+const { User } = require('../models');
+User.findAll({
+  attributes: ['name', 'age'],
+  where: {
+    married: 1,
+    age: { [Op.gt]: 30 },   // 시퀄라이즈는 자바스크립트 객체를 사용하여 쿼리를 생성하므로, 특수 연산자 사용. Op.gt 은 operator greater than 임.
+  },
+});
+
+//Op.or 사용
+SELECT id, name FROM users WHERE married = 0 OR age > 30;
+const { Op } = require('sequelize');
+const { User } = require('../models');
+User.findAll({
+  attributes: ['id', 'name'],
+  where: {
+    [Op.or]: [{ married: 0 }, { age: { [Op.gt]: 30 } }],
+  },
+});
+
+//Order by
+SELECT id, name FROM users ORDER BY age DESC;
+User.findAll({
+  attributes: ['id', 'name'],
+  order: [['age', 'DESC']],
+});
+
+SELECT id, name FROM users ORDER BY age DESC LIMIT 1;
+User.findAll({
+  attributes: ['id', 'name'],
+  order: [['age', 'DESC']],
+  limit: 1,
+});
+
+SELECT id, name FROM users ORDER BY age DESC LIMIT 1 OFFSET 1;
+User.findAll({
+  attributes: ['id', 'name'],
+  order: ['age', 'DESC'],
+  limit: 1,
+  offset: 1,
+});
+
  • 수정 : 첫번째로 수정할 내용, 두번째로 조건
1
+2
+3
+4
+5
+6
+
UPDATE nodejs.users SET comment = '바꿀 내용' WHERE id = 2;
+User.update({
+  comment: '바꿀 내용',
+}, {
+  where: { id: 2 },
+});
+
  • 생성
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+
+INSERT INTO nodejs.users (name, age, married, comment) VALUES ('zero', 24, 0, '자기소개1');
+const { User } = require('../models');
+User.create({
+  name: 'zero',
+  age: 24,
+  married: false,
+  comment: '자기소개1',
+});
+
+
  • 삭제
1
+2
+3
+4
+
DELETE FROM nodejs.users WHERE id = 2;
+User.destory({
+  where: { id: 2 },
+});
+

관계쿼리

  • 관계쿼리 : find 메소드에서 옵션으로 include를 주면, JOIN 메소드 사용가능.
1
+2
+3
+4
+5
+6
+7
+8
+
// 특정사용자를 가져오면서, 댓글까지 모두 가져오고 싶을때
+const user = await User.findOne({
+  include: [
+    {
+      model: Comment,
+    },
+  ],
+});
+
  • 여기서 user.Comments로 접근 가능.
  • 관계를 설정했다면, 아래 메소드 지원
    • getComments, setComments, addComment, addComments, removeComments
    • 이렇게, 동사뒤에 모델의 이름을 붙여서 사용한다.
    • 동사 뒤에 모델 이름을 바꾸고 싶으면, 관계 설정시 as 옵션을 사용할 수 있음.
1
+2
+3
+4
+5
+
db.User.hasMany(db.Comment, {
+  foreignKey: "commenter",
+  sourceKey: "id",
+  as: "Answers",
+});
+
  • include 나 관계 쿼리 메소드에도 where나 attributes 같은 옵션을 사용할 수 있음.
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+
const user = await User.findOne({
+  include: [
+    {
+      model: Comment,
+      where: {
+        id: 1,
+      },
+      attributes: ["id"],
+    },
+  ],
+});
+// 또는
+const comments = await user.getComments({
+  where: {
+    id: 1,
+  },
+  attributes: ["id"],
+});
+
  • 관계쿼리 수정, 생성, 삭제 때는 조금 다르게 함.
1
+2
+3
+4
+
const user = await User.findOne({});
+const comment1 = await Comment.create();
+const comment2 = await Comment.create();
+await user.addComment([comment1, comment2]);
+

SQL 쿼리하기

  • 직접 SQL 쿼리를 사용할 수 있음.
1
+2
+
const [result, metadata] = await sequelize.query("SELECT * from comments");
+console.log(result);
+
This post is licensed under CC BY 4.0 by the author.

노드 스터디 6장

REACT/CORS 개발 환경에서, 외부 API 와 연결할때 쿠키가 생성되지 않는 문제

Comments powered by Disqus.

diff --git a/posts/oop-analysis/index.html b/posts/oop-analysis/index.html new file mode 100644 index 000000000..654bef25e --- /dev/null +++ b/posts/oop-analysis/index.html @@ -0,0 +1 @@ + 객체지향 분석론 | 디피의 개발일지
Posts 객체지향 분석론
Post
Cancel

객체지향 분석론

Coad Yourdon

E-R 다이어그램을 사용하여 객체의 행위를 모델링.

객체 식별, 구조 식별, 주제정의, 속성과 인스턴스 연결 정의, 연산과 메시지 연결 정의 등의 과정으로 주로 관계를 분석 하는 기법


Booch

미시적, 거시적 개발 프로세스를 모두 사용하는 분석 방법.

클래스와 객체들을 분석 및 식별하고 클래스의 속성과 연산을 정의


Jacobson

UseCase를 사용하여 분석.


Wirfs-Brock 방법

분석과 설계간 구분이 없으며, 고객 명세서를 평가해서 설계작업까지 연속적으로 수행하는 기법



출처

https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=agopwns&logNo=220998991245

This post is licensed under CC BY 4.0 by the author.

CASE

UML

Comments powered by Disqus.

diff --git a/posts/oop/index.html b/posts/oop/index.html new file mode 100644 index 000000000..162bae779 --- /dev/null +++ b/posts/oop/index.html @@ -0,0 +1,161 @@ + Object Oriented Programming | 디피의 개발일지
Posts Object Oriented Programming
Post
Cancel

Object Oriented Programming

OOP는 너무 거대한 개념이라 다 다루지는 못하지만 간단히만 알아보자


간단한 설명

OOP : 중심적 프로그래밍 패러다임. 현실 세계의 사물들을 객체라고 보고, 그 객체로부터 개발하고자 하는 애플리케이션에 필요한 특징들을 뽑아와 프로그래밍하는 것(추상화)

장점

  • OOP로 작성한 코드는 재사용성이 높다.

    • 자주 사용되는 로직을 라이브러리로 만들어두면 개발로드가 줄어들고, 신뢰성도 확보가 됨

    • 라이브러리를 각종 예외상황에 맞게 잘 구현하면, 개발자가 사소한 실수를 해도 에러를 컴파일 단계에서 잡아낼 수 있음
    • 내부적으로 어떻게 돌아가는지 몰라도 그냥 사용하면 됨.
  • 객체 단위로 코드가 나뉘어져있어 디버깅과 유지보수가 쉽다.

  • 데이터 모델링 할때, 객체와 매핑하는 것이 훨씬 수월하기에 요구사항을 보다 명확하게 파악하여 프로그래밍 가능

단점

  • 객체 간의 정보 교환이 모두 메시지 교환을 통해 일어나므로, overhead가 발생하게 됨.
    • 하지만, 하드웨어의 발전으로 많이 보완됨
  • 객체가 상태를 가짐

    • 객체 안에는 변수가 존재하고, 이 변수를 통해 객체가 예측할 수 없는 상태를 갖게 되어 어플리케이션 내부에서 버그를 발생시킴

      -> 따라서 함수형 프로그래밍을 발전 시켜 해결하고자 함

객체 지향적 설계원칙(SOLID)

  1. SBP(Single Responsibility Principle) : 단일 책임 원칙
    • 클래스는 단 하나의 책임을 가져야하며 클래스를 변경하는 이유는 단 하나의 이유이어야 함
  2. OCP(Open-Closed Principle) : 개방-폐쇄 원칙
    • 확장에는 열려있어야하고, 변경에는 닫혀있어야한다.
    • OCP는 다형성을 통해 지켜질 수 있다.
      • 다형성 : 하나의 객체 혹은 메소드가 여러 타입을 참조할 수 있음.
        • 객체 다형성 : 자식 객체가 부모 객체의 인스턴스로 할당될 수 있음
        • 메소드 다형성 : 오버로딩을 통해 구현 가능
      • IoC 컨테이너에서 DI를 통해 다형성을 이용하여 OCP를 지켜준다. -> 유지보수가 매우 쉬워짐
  3. LSP(Liskov Substitution Principle) : 리스코프 치환원칙
    • 상위 타입의 객체를 하위 타입의 객체로 치환해도 상위 타입을 사용하는 프로그램은 정상적으로 동작해야한다.
  4. ISP(interface Segregation Principle) : 인터페이스 분리 원칙
    • 인터페이스는 그 인터페이스를 사용하는 클라이언트를 기준으로 분리해야한다.
  5. DIP(Dependency Inversion Principle) : 의존 역전 원칙
    • 고수준 모듈은 저수준 모듈의 구현에 의존해서는 안된다.


좀 더 자세히

용어 및 정의

정의

  • 객체 지향의 가장 기본은 객체이며, 객체의 핵심은 기능을 제공하는 것이다.
  • 실제로 객체를 정의할 때 사용하는 것은 객체가 제공해야할 기능이며, 객체 내부의 데이터로 정의되지 않는다.
  • 이러한 기능들을 오퍼레이션(operation)이라고 부른다.

시그니처

  • 객체 지향으로 설계하기 위해서는 오퍼레이션의 사용법을 알아야한다.
  • 오퍼레이션의 사용법은 다음 세가지로 구성되고, 이 세가지를 시그니처라고 부른다.
    • 기능 식별 이름
    • 파라미터 및 파라미터 타입
    • 기능 실행 결과값 및 타입

인터페이스

  • 객체가 제공하는 모든 오퍼레이션의 집합을 객체의 인터페이스라고 부른다.

메시지

  • 오퍼레이션의 실행을 요청하는 것을 메시지를 보낸다라고 표현한다.
  • 자바에서는 메서드를 호출하는 것이 메시지를 보내는 과정에 해당함

설계 원칙

책임

  • 객체마다 자신만이 제공할 수 있는 기능에 대한 책임이 있다.

  • 객체가 갖는 책임의 크기는 작을 수록 좋다.

    • 한 객체가 너무 많은 기능을 포함하면(책임이 크면), 그 기능과 관련된 데이터들도 모두 한 객체에 포함되게 되고, 객체에 정의된 많은 오퍼레이션들이 데이터를 공유하는 방식으로 프로그래밍 되는데, 이는 절차지향과 다를바가 없기 때문이다.
  • 이것을 해결하기 위해, SBP(Single Responsibility Principle) : 단일 책임 원칙이 필요하다.

개방-폐쇄 원칙

  • “기존 코드를 변경하지 않으면서 코드의 수정을 허용하는 것”
  • 즉, 원하는 기능을 구현하였을때, 사용자는 우리가 짠 코드를 변경할 순 없으나, 그 기능은 변경가능하도록 하는 것이다.
  • 예시
    • Car라는 클래스가 있을때, 사용자는 Car라는 클래스를 확장하여 Taxi 라는 클래스를 만들었다고 해보자.
    • 이때 Taxi 에는 Car의 기능을 포함하여 새로운 기능 (요금 받기… 등)이 필요하다. 따라서 이러한 기능들은 Taxi 클래스 안에서 정의하여 구축할 수 있다.
    • 또 Car 클래스에 속한 기본적인 기능(accelate(), break() 등)에 변경이 필요하면, 이 기능들을 오버라이드하여 필요한 기능들을 구축할 수 있다.
    • 위와 같이, Car라는 클래스를 변경하지 않으면서, Car의 확장은 허용하는 것이 OCP 원칙이다.

리스코프 치환 원칙

  • 상위 타입의 객체를 하위 타입의 객체로 치환해도 상위 타입을 사용하는 프로그램은 정상적으로 동작해야한다.

  • 상위 타입인 fruits에 하위타입인 Apple이 있다면, fruits를 사용하는 곳에서는 Apple을 사용해도 문제 없도록 해야한다.

  • 위배한 예시

    • 직사각형은 정사각형이 아니지만, 정사각형은 직사각형이다. 따라서 직사각형을 상속하여 정사각형 객체를 정의했다고 해보자.
    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +14
    +15
    +16
    +17
    +18
    +19
    +20
    +
    public class Rectangle {
    +    private int width;
    +    private int height;
    +
    +    public void setWidth(final int width) {
    +        this.width = width;
    +    }
    +
    +    public void setHeight(final int height) {
    +        this.height = height;
    +    }
    +
    +    public int getWidth() {
    +        return width;
    +    }
    +
    +    public int getHeight() {
    +        return height;
    +    }
    +}
    +
    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +14
    +
    public class Square extends Rectangle {
    +
    +    @Override
    +    public void setWidth(final int width) {
    +        super.setWidth(width);
    +        super.setHeight(width);
    +    }
    +
    +    @Override
    +    public void setHeight(final int height) {
    +        super.setWidth(height);
    +        super.setHeight(height);
    +    }
    +}
    +
    • 이때 직사각형을 인자로 받고, 세로가 가로보다 짧다면, 세로를 가로의 길이에 1을 더한 만큼의 길이를 갖게 만들어 세로를 더 길게만드는 메서드가 있다고 해보자.
    • 이때 정사각형은 항상 가로와 세로의 길이가 같으므로, 위 메서드를 실행하면, 가로와 세로의 길이가 같게 된다. 위 메서드의 수행결과에 문제가 발생한 것이다.
    • 따라서 이는 리스코프 치환원칙에 위배되므로, 정사각형은 직사각형을 상속받으면 안된다.
  • 리스코프 치환원칙은 기능의 명세와 확장에 대한 것이다.

    • Retangle 클래스의 setHeight() 메서드는 아래와 같은 기능을 명세한다.
      • 높이 값을 파라미터로 전달받은 값으로 변경한다.
      • 폭값은 변경되지 않는다.
    • 하지만, Square의 setHeight()는 높이와 폭 모두 변경하게 된다. 이렇게 상위 타입에서 정한 명세를 하위 타입에서도 그대로 지킬 수 없다면 상속을 하면 안된다.

의존성

  • 한 객체가 다른 객체를 이용한다는 것은, 한 객체의 코드에서 다른 객체를 생성하거나 다른 객체의 메서드를 호출한다는 것을 의미한다.
  • 이러한 의존의 영향은 꼬리에 꼬리를 문것처럼 계속 전파되고, 변경한 여파가 다시 자기 자신까지 변화시킬 수 있는데, 이것을 순환의존이라고 한다.
  • 이것을 해결하기 위해, DIP(Dependency Inversion Principle) : 의존 역전 원칙이 필요하다

인터페이스 분리 원칙

  • 인터페이스는 그 인터페이스를 사용하는 클라이언트를 기준으로 분리해야한다.

  • 특정 클라이언트를 위한 인터페이스를 여러개 만드는 것이 범용 인터페이스 하나보다 낫다

  • 예시

    • ISP를 적용하기 전

      1
      +2
      +3
      +4
      +5
      +6
      +
      // ISP를 적용하지 않은 예제
      +public interface multifunction {
      +  void copy();
      +  void fax(Address from, Address to);
      +  void print();
      +}
      +
      1
      +2
      +3
      +4
      +5
      +6
      +7
      +8
      +9
      +10
      +11
      +12
      +13
      +14
      +15
      +16
      +
      public class CopyMachine implements multifunction {
      +  @Override
      +  public void copy() {
      +    System.out.println("### 복사 ###");
      +  }
      +
      +  @Override
      +  public void fax(Address from, Address to) {
      +    // 사용하지 않는 인터페이스가 변경되어도 함께 수정이 일어난다.
      +  }
      +
      +  @Override
      +  public print() {
      +    // 사용하지 않는 인터페이스가 변경되어도 함께 수정이 일어난다.
      +  }
      +}
      +
      • CopyMachine에 필요없는 fax, print 도 모두 구현해줘야한다.
      • 만약 multifunction의 fax() print()의 리턴타입이 변경되면 CopyMachine에서도 모두 변경해줘야한다.
    • ISP를 적용한 이후

      1
      +2
      +3
      +4
      +5
      +6
      +7
      +8
      +9
      +10
      +11
      +12
      +
      // ISP가 적용된 예제
      +public interface Print{
      +  void print();
      +}
      +    
      +public interface Copy {
      +  void copy();
      +}
      +    
      +public interface Fax {
      +  void fax(Address from, Address to);
      +}
      +
      1
      +2
      +3
      +4
      +5
      +6
      +
      public class copyMachine implements Copy {
      +  @Override
      +  void copy() {
      +    System.out.println("### 복사 ###");
      +  }
      +}
      +

캡슐화

캡슐화

  • 객체가 내부적으로 기능을 어떻게 구현하는지를 숨기는 것

  • 객체지향은 기본적으로 캡슐화를 통해서 한 곳의 변화가 다른 곳에 미치는 영향을 최소화한다.
  • 내부 기능 구현이 변경되더라도, 그 기능을 사용하는 코드는 영향을 받지 않도록 해주어서, 내부 구현 변경의 유연함을 주는 기법이 캡슐화이다.

캡슐화를 위한 두 개의 규칙

  1. Tell, Don’t Ask

    • 데이터를 물어보지 않고, 기능을 실행해 달라고 말하라는 규칙이다.

    • 데이터를 읽는 것은 데이터를 중심으로 코드를 작성하게 만드는 원인이 된다.

    • 데이터를 private로 클래스 내부에 숨기고, 메소드를 통해 데이터에 접근해야한다.

      1
      +2
      +3
      +4
      +5
      +6
      +
      public class Customer {
      +    private Wallet wallet;
      +    public Wallet getWallet() {
      +        return wallet;
      +    }
      +}
      +
  2. 데미테르 법칙 : 한 객체 안에서 다른 객체의 메서드를 부를때는 다음 상황일때만이다.

    • 메서드에서 생성한 객체의 메서드만 호출
    • 파라미터로 받은 객체의 메서드만 호출
    • 필드로 참조되는 객체의 메서드만 호출

객체지향 설계과정

  1. 제공해야할 기능을 찾고, 또는 세분화하고 그 기능을 알맞는 객체에 할당한다.
    1. 기능을 구현하는데 필요한 데이터를 객체에 추가한다.
    2. 객체에 데이터를 먼저 추가하고, 그 데이터를 이용하는 기능을 넣는다.
    3. 기능은 최대한 캡슐화하여 구현한다.
  2. 객체간에 어떻게 메시지를 주고 받을 지 결정한다.

상속을 통한 재사용의 단점

  1. 상위 클래스 변경의 어려움
    • 어떤 클래스를 상속 받는 다는 것은 그 클래스에 의존한다는 뜻이고, 따라서 의존하는 클래스의 코드가 변경되면 영향을 받을 수 있다.
  2. 클래스의 불필요한 증가
    • 유사한 기능을 확장하는 과정에서 클래스의 개수가 불필요하게 증가할 수 있다.
  3. 상속의 오용
    • 같은 종류가 아닌 클래스의 구현을 재사용하기 위해 상속을 받게 되면, 문제가 발생한다.
    • 상속을 받는 클래스가 상위 클래스와 IS-A 관계가 아닌 경우에 발생함

객체 조립 : 위 단점의 해소법

  • 객체지향언어에서 객체 조립은 보통 필드에서 다른 객체를 참조하는 방식으로 구현된다.
  • 상속에 비해 조립을 통한 재사용의 단점은 상대적으로 런타임 구조가 복잡해진다는 것이고, 상속보다 구현이 어렵다는 것이다.
  • 하지만, 변경의 유연함을 확보하는데서 오는 장점이 크기 때문에, 상속보다 조립하는 방법을 먼저 고려해야한다.

상속은 언제 사용하는가?

  • 재사용이 아닌, 기능의 확장이라는 관점에서 상속을 적용해야함
  • 명확한 IS-A 관계가 성립 되어야함.

출처

https://asfirstalways.tistory.com/177

This post is licensed under CC BY 4.0 by the author.

React v18 주요 변경점

알고리즘 팁(WIP)

Comments powered by Disqus.

diff --git a/posts/opengraph/index.html b/posts/opengraph/index.html new file mode 100644 index 000000000..cc9443dfb --- /dev/null +++ b/posts/opengraph/index.html @@ -0,0 +1,21 @@ + Open Graph(OG) 프로토콜 | 디피의 개발일지
Posts Open Graph(OG) 프로토콜
Post
Cancel

Open Graph(OG) 프로토콜

Open Graph 프로토콜

HTML문서의 head에는 <meta> 태그로 사이트의 정보가 표기되어있는 경우가 많다. 이는 크롤러가 무엇이 제목이고, 무엇이 내용인지 파악하기 쉽도록 하기 위해서 표기한 것이다.

페이스북의 Open Graph 프로토콜은 이러한 메타태그의 표기 방법은 통일한 것이다. 이 Open Graph 프로토콜이 우리가 보는 미리보기 화면의 실체를 구성하는 메타 데이터 표기 방법이다.


작동원리

  1. 사용자가 링크를 입력창에 입력한다.
  2. 페이스북, 네이버 블로그, 카카오톡은 입력창의 문자열이 “링크”라는 것을 파악한다.(정규표현식 등으로)
  3. 링크라는 것이 파악되면 페이스북, 네이버 블로그, 카카오톡의 크롤러는 미리 그 웹 사이트를 방문해서 HTML head의 오픈그래프 메타데이터를 긁어온다.
  4. 이중에서 og:title, og:description, og:image은 각각 제목, 설명, 이미지의 정보를 나타낸다.
  5. 그 정보를 바탕으로 미리보기 화면을 생성한다.


기본 메타데이터

1
+2
+3
+4
+5
+6
+7
+8
+
<!-- 제목 -->
+<meta property="og:title" content="제목" />
+<!-- type -->
+<meta property="og:type" content="website" />
+<!-- 이미지 -->
+<meta property="og:image" content="이미지 경로" />
+<!-- canonical link(대표 URL) -->
+<meta property="og:url" content="url" />
+

옵션 메타데이터

  • og:audio

  • og:description

    • 1
      +2
      +
      <!-- 설명 -->
      +<meta property="og:description" content="내용<br/>줄바꿈" />
      +
  • og:determiner

  • og:locale

  • og:local:alternate

  • og:site_name

  • og:video


트위터의 경우

트위터는 자사의 메타데이터 표기법을 가지고 있다. 하지만, 웹사이트에 오픈그래프 프로토콜만 있을 경우엔 오픈그래프를 가져온다.


개발자는 고쳤다는데, 왜 미리보기가 안 바뀔까?

서버 자원을 아끼기 위해 캐싱된 HTML을 불러오기 때문이다.

페이스북에선 Sharing Debugger, 카카오스토리에서는 내부 개발자도구를 통해 캐싱 삭제를 할 수 있다.


클릭 2의 미스터리

내가 바로 쓴 글을 페이스북에 올리면 바로 조회수가 올라간다. 그 이유는 크롤러가 우리 사이트를 한번 미리 방문하기 때문에 조회수 1이 올라가기 때문이다. 구글과 같이 방금 조회한 사람이 크롤러라는 것을 알려주는 경우도 잇지만, 페이스북은 그렇지 않다.


오픈그래프 적용

  1. og:title, og:description, og:image 는 반드시 명시한다.

  2. 페이스북의 Sharing Debugger를 통해 제대로 적용됐는지 확인한다.
  3. 카카오톡, 페이스북, 네이버 블로그 등 사이트마다 이미지가 다르게 보일 수 있다. 따라서 각 사이트에서 제대로 보이는지 확인한다.
  4. 가능하다면 A/B 테스트를 통해 어떤 문구가 더 효과적인지 확인한다.


출처

https://blog.ab180.co/posts/open-graph-as-a-website-preview

This post is licensed under CC BY 4.0 by the author.
diff --git a/posts/os/index.html b/posts/os/index.html new file mode 100644 index 000000000..53ed21ab4 --- /dev/null +++ b/posts/os/index.html @@ -0,0 +1 @@ + 운영체제 개요 | 디피의 개발일지
Posts 운영체제 개요
Post
Cancel

운영체제 개요

프로세스와 스레드의 차이

프로세스

실행중인 프로그램으로, 메모리에 적재되어 CPU의 할당을 받을 수 있는 것을 말함.

OS로부터 주소공간, 파일, 메모리등을 할당받으며, 이것들을 총칭하여 프로세스라고한다.

할당받는 메모리 공간

  • 프로세스 스택 : 함수의 매개변수, 복귀주소, 로컬 변수 같은 임시자료를 저장
  • 데이터 섹션 : 전역변수들을 수록
  • 힙 : 프로세스 실행 중에 동적으로 항당되는 메모리

프로세스 제어 블록(Process Control Block, PCB)

PCB는 특정 프로세스에 대한 중요한 정보를 저장하고 있는 OS의 자료구조이다. OS는 프로세스를 관리하기 위해 프로세스의 생성과 동시에 고유한 PCB를 생성한다.

프로세스는 CPU를 할당받아 작업을 처리하다가도, 시간이 지나면 진행하던 작업을 저장하고 CPU를 반환해야하는데, 이때 작업의 진행상항은 PCB에 저장하게 된다. 그리고 다시 CPU를 할당 받으면 PCB에 저장된 내용을 불러와 이전에 종료된 시점부터 다시 수행한다.

PCB에 저장되는 정보

  • 프로세스 식별자(process ID, PID)
  • 프로세스 상태 : new, ready, running, waiting, terminated 등의 상태를 저장
  • 프로그램 카운터 : 프로세스가 다음에 실행할 명령어의 주소
  • CPU 레지스터
  • CPU 스케쥴링 정보 : 프로세스의 우선순위, 스케쥴 큐에 대한 포인터 등
  • 메모리 관리 정보 : 페이지 테이블 또는 세그먼트 테이블 등과 같은 정보를 포함
  • 입출력 상태 정보 : 프로세스에 할당된 입출력 장치들과 열린 파일 목록
  • 어카운팅 정보 : 사용된 CPU 시간, 시간제한, 계정번호 등


스레드 (Thread)

프로세스의 실행단위. 한 프로세스 내에서 동작되는 여러 실행흐름으로 프로세스 내의 주소 공간이나 자원을 공유할 수 있음.

스레드는 스레드 ID, 프로그램 카운터, 레지스터 집합, 스택으로 구성된다. 같은 프로세스에 속한 다른 스레드와 코드, 데이터 섹션, 그리고 열린파일이나 신호와 같은 운영체제 자원들을 공유한다.

하나의 프로세스를 다수의 실행단위로 구분하여 자원을 공유하고 자원의 생성과 관리의 중복성을 최소화하여 수행 능력을 향상 시키는 것을 멀티 스레딩이라고 한다. 이 경우 각각의 스레드는 독립적인 작업을 수행해야 하기 때문에 각자의 스택과 PC 레지스터 값을 갖고 있다.

스택을 스레드마다 독립적으로 할당하는 이유

스택은 함수 호출시 전달되는 인자, 되돌아갈 주소값 및 함수 내에서 선언하는 변수 등을 저장하기 위해 사용되는 메모리 공간이므로, 스택 메모리 공간이 독립적이라는 것은 독립적인 함수 호출이 가능하다는 것이고, 이는 독립적인 실행 흐름이 추가되는 것이다. 따라서 스레드의 정의에 따라 독립적인 실행 흐름을 추가하기 위한 최소 조건으로 독립된 스택을 할당한다.

PC 레지스터를 스레드마다 독립적으로 할당하는 이유

PC 값은 스레드가 명령어의 어디까지 수행하였는지를 나타나게 된다. 스레드는 CPU를 할당 받았다가 스케쥴러에 의해 다시 선점당한다. 그렇기 때문에 명령어가 연속적으로 수행되지 못하고 어느 부분까지 수행했는지 기억할 필요가 있다. 따라서 PC 레지스터를 독립적으로 할당한다.



멀티 스레드

멀티 스레딩의 장점

여러 프로세스를 이용하여 동시에 처리하던 일을 스레드로 구현할 경우, 메모리 공간과 시스템 자원 소모가 줄어들게 된다. 스레드간의 통신이 필요한 경우에도 전역 변수의 공간 또는 Heap 영역을 사용하여 데이터를 주고받을 수 있다. 그렇기 때문에 프로세스 간 통신보다 통신방법이 훨씬 간단하다. 심지어 스레드의 context switch는 프로세스 context switch와는 달리 캐시 메모리를 비울 필요가 없기 때문에 더 빠르다. 따라서 시스템의 throughput이 향상되고 자원소모가 줄어들며, 따라서 프로그램은 더욱 빨라진다.


문제점

공유하는 자원이 있다. 따라서 서로 다른 스레드가 데이터와 힙 영역을 공유하기 때문에 어떤 스레드가 다른 스레드에서 사용중인 변수나 자료구조에 접근하여 엉뚱한 값을 읽어오거나 수정할 수 있다.

따라서 멀티스레딩 환경에서는 동기화 작업이 필요하다. 동기화를 통해 작업처리 순서를 컨트롤 하고 공유자원에 대한 접근을 컨트롤 하는 것이다. 하지만 이로인해 병목 현상이 발생하여 성능이 저하될 가능성이 높다. 그러므로 과도한 락으로 인한 병목현상을 줄여야한다.


멀티 스레드 vs 멀티 프로세스

멀티 스레드

  • 멀티 프로세스보다 적은 메모리 공간 차지. context switch가 빠름.
  • 공유된 자원으로 인한 오류로 전체 스레드가 영향을 받을 수 있음. 동기화에 시간이 걸림

멀티 프로세스

  • 하나의 프로세스가 죽어도, 다른 프로세스에는 영향을 끼치지 않음.
  • 많은 메모리, CPU 차지.

두 가지가 서로 상반된 장/단점을 가지고 있기에, 동작하는 시스템에 따라 적합/부적합이 구분된다. 따라서 시스템의 특징에 따라 적합한 동작 방식을 선택하고 적용해야한다.



스케쥴러

프로세스를 스케쥴링 하기 위한 3가지 종류의 Queue

  • Job Queue : 현재 시스템 내에 있는 모든 프로세스의 집합
  • Ready Queue : 현재 메모리 내에 있으면서 CPU를 잡아서 실행되기를 기다리는 프로세스의 집합
  • Device Queue : Device I/O 작업을 대기하고 있는 프로세스의 집합


각각의 Queue 에 프로세스들을 넣고 빼주는 스케쥴러에도 크게 세 가지 종류가 존재함

**장기 스케줄러(Long term scheduler or job scheduler) **

많은 프로세스들이 한번에 메모리에 올라올 경우, 대용량메모리(디스크)에 임시로 저장된다. 이 pool에 저장되어있는 프로세스 중 어떤 프로세스에 메모리를 할당하여 ready queue로 보낼지 결정하는 역할을 함

  • 메모리와 디스크 사이의 스케줄링을 담당
  • 프로세스에 메모리를 할당
  • degree of multiprogramming 제어 (실행중인 프로세스 수 제어)
  • 프로세스의 상태 : new -> ready


단기 스케줄러 (short-term scheduler or CPU scheduler)

  • CPU와 메모리 사이의 스케줄링을 담당.
  • Ready queue에 존재하는 프로세스 중 어떤 프로세스를 running 시킬지 결정
  • 프로세스에 CPU를 할당 (scheduler dispatch)
  • 프로세스의 상태 : ready -> running -> waiting -> ready


중기 스케줄러(Medium-term scheduler or Swapper)

  • 여유 공간을 마련하기 위해, 프로세스를 통째로 메모리에서 디스크로 쫓아냄(swapping)
  • 프로세스에게서 메모리를 deallocate
  • degree of multiprogramming 제어
  • 프로세스의 상태 : ready -> suspended
    • suspended 상태
      • 외부적인 이유로 프로세스의 수행이 정지된 상태로, 메모리에서 내려간 상태를 의미함. 프로세스 전태를 디스크로 swap out 한다.
      • blocked 상태는 I/O 작업을 기다는 상태이므로 스스로 ready state로 돌아갈 수 있지만, suspended 상태는 스스로 돌아갈 수 없음


CPU 스케줄러

Ready Queue에 있는 프로세스들 대상

FCFS(First come First serve)

특징

  • 먼저 온 순서대로 처리해줌

  • 비선점형(non-preemptive) 스케줄링 : 한번 CPU을 잡으면 CPU burst가 완료될때까지 CPU를 반환하지 않음.

    • 할당 되었던 CPU가 반환될 때만 스케줄링이 이루어진다.

문제점

  • convoy effect
    • 소요시간이 긴 프로세스가 먼저 도달하면 효율성이 낮아진다.


SJF(shortest job first)

  • CPU burst time이 짧은 프로세스가 먼저 할당됨
  • non-preemptive 스케줄링 : 한번 CPU에 올라간 프로세스는 끝까지 수행됨

문제점

  • starvation : 어떤 프로세스 A보다 CPU burst time이 짧은 프로세스가 계속 들어오면 A는 영원히 실행되지 않음


SRTF (shortest Remaining time first)

  • SJF 에서 선점형 스케줄링 추가
  • 현재 수행중인 프로세스보다 남은 burst time이 짧은 프로세스가 있으면, 현재 프로세스를 빼고 그 프로세스를 수행함

문제점

  • starvation
  • 새로운 프로세스가 도착할때마다 스케줄링을 다시하기 때문에, CPU burst time을 측정할 수가 없음


Priority Scheduling

  • 우선순위가 높은 CPU를 먼저 할당하는 방식
  • 선점형 스케줄링 방식: 우선순위가 높은 것이 들어오면 CPU를 뺏도록 함
  • 비선점형 스케줄링 방식 : 우선순위가 높은 것이 들어오면, Ready queue의 head에 두고 현재 수행중인 프로세스는 끝마침

문제점

  • starvation
    • aging 기법을 통해, 오래 기다릴수록 우선순위를 높여줌으로써 해결할 수 있음


Round Robin

  • 현대적인 CPU 스케줄링
  • 각 프로세스는 동일한 크기의 할당 시간(time quantum)을 갖게 됨.
  • 할당시간이 지나면 프로세스는 선점 당하고, ready queue의 제일 뒤에 줄을 섬
  • CPU 사용시간이 랜덤한 프로세스들이 섞여있을 경우 효율적이다.
  • 프로세스의 context를 저장하기에 가능한 방법.

장점

  • Response time이 빨라짐. 어떤 프로세스라도 (n-1)q 단위 시간이상 기다리지 않음(q : time quantum, n : 프로세스 수)
  • 프로세스가 기다리는 시간이 CPU burst time만큼 증가함. 따라서 공정하게 분배된다.

주의할 점

할당하는 시간이 너무 커지면 FCFS와 같아지고, 너무 작아지면 잦은 context switch로 overhead가 발생한다. 따라서 적절하게 설정해준다.


HRN(Highest Response ratio Next)

SJF의 단점을 개선한 기법. 각 작업의 우선순위로 서비스해주는 스케줄링

우선순위

  • (대기시간 + 서비스시간) / 서비스시간



프로세스 동기화

Critical Section(임계영역)

동일한 자원을 동시에 접근하는 작업을 실행하는 코드 영역

ex) 공유하는 변수 사용, 동일 파일 사용

Critical Section Problem

Requirement

  • Mutual Exclusion : 상호배제
    • 프로세스 P1이 critical section을 실행중이라면, 다른 프로세스들은 P1이 가진 Critical section을 수행할 수 없도록 함
  • Progress
    • Critical Section을 실행중인 프로세스가 없고, 별도의 동작이 없는 프로세드들만 Critical Section진입 후보로서 참여할 수 있음
  • Bounded waiting(한정된 대기)
    • P1이 critical section에 진입 신청 후 받아들여지기 전까지, 다른 프로세스들이 Critical section에 진입하는 횟수는 제한이 있어야한다.

해결책

Lock

  • 하드웨어 기반 해결책으로써, 동시에 공유 자원에 접근하는 것을 막기 위해 Critical Section 에 진입하는 프로세스는 Lock 을 획득하고 Critical Section 을 빠져나올 때, Lock 을 방출함으로써 동시에 접근이 되지 않도록 한다.
  • 문제점 : 다중처리기 환경에서는 시간적인 효율성 측면에서 적용할 수 없다

Semaphores

  • 소프트웨어상에서 Critical Section 문제를 해결하기 위한 동기화 도구

  • 종류
    • Counting Semaphore
      • 가용한 개수를 가진 자원에 대한 접근 제어용으로 사용되며, 세마포는 그 가용한 자원의 개수로 초기화 된다. 자원을 사용하면 세마포가 감소, 방출하면 세마포가 증가 한다.
    • Binary Semaphore
      • MUTEX 라고도 부르며, 상호배제의 (Mutual Exclusion)의 머릿글자를 따서 만들어졌다. 이름 그대로 0 과 1 사이의 값만 가능하며, 다중 프로세스들 사이의 Critical Section 문제를 해결하기 위해 사용한다.
  • 단점
    • Busy waiting
      • Spin lock이라고 불리는 Semaphore 초기 버전에서 Critical Section 에 진입해야하는 프로세스는 진입 코드를 계속 반복 실행해야 하며, CPU 시간을 낭비했었다. 이를 Busy Waiting이라고 부르며 특수한 상황이 아니면 비효율적이다. 일반적으로는 Semaphore에서 Critical Section에 진입을 시도했지만 실패한 프로세스에 대해 Block시킨 뒤, Critical Section에 자리가 날 때 다시 깨우는 방식을 사용한다. 이 경우 Busy waiting으로 인한 시간낭비 문제가 해결된다.

모니터

  • 고급 언어의 설계 구조물로서, 개발자의 코드를 상호배제 하게끔 만든 추상화된 데이터 형태이다.

  • 공유자원에 접근하기 위한 키 획득, 자원사용 후 해제 를 모두 처리함.(세마포어는 직접 키 해제와 공유자원 접근 처리가 필요함)

Deadlock

  • 둘 이상의 프로세스가 Critical Section 진입을 무한정 기다리고 있고, Critical Section에서 실행되는 프로세스는 진입 대기 중인 프로세스가 실행되어야만 빠져나올 수 있는 상황을 지칭함
  • 즉, 원형으로 서로를 기다리고 있는 상황

메모리 관리 전략

용어

Swapping

메모리의 관리를 위해 사용되는 기법.

표준 Swapping 방식으로는 round-robin 과 같은 스케줄링의 다중 프로그래밍 환경에서 CPU 할당 시간이 끝난 프로세스의 메모리를 보조 기억장치(e.g. 하드디스크)로 내보내고 다른 프로세스의 메모리를 불러 들일 수 있다.

주기억장치로 불러들이는 것을 swap-in 이라하고, 보조기억장치로 내보내는 것을 swap-out이라 한다.

작업이 오래 걸리기에 현재는 메모리 공간이 부족할때만 수행됨

단편화

메모리에 남은 공간들이 있으나, 프로세스에 끼여 사용하지 못하는 현상

  • 외부 단편화 : 프로세스 사이에 끼여 남은 공간.
  • 내부 단편화 : 프로세스에 할당 된 것보다 작은 공간을 사용할때, 그 남는 공간

압축

외부단편화의 해소를 위해, 프로세스가 사용하는 메모리 공간을 모두 한 곳에 모아, 남는 공간을 한곳에 모으는 방법

작업효율이 안좋다.

Paging

물리 메모리를 Frame이라는 고정 크기로 분리하고, 논리 메모리(프로세스가 점유하는)를 페이지라 불리는 고정 크기의 블록으로 분리함.

하나의 프로세스가 사용하는 공간은 여러개의 페이지로 나뉘어서 논리메모리에서 관리되고, 개별 페이지는 순서에 상관없이 물리메모리에 있는 프레임에 맵핑되어 저장됨

페이징 기법을 사용하여 논리메모리는 물리메모리에 저장될때, 연속되어 저장될 필요가 없고, 물리 메모리의 남는 프레임에 적절히 배치 됨으로써 외부 단편화를 해결할 수 있다.

단점

  • 내부단편화 문제가 크게 발생할 수 있다. 예를들어 페이지 크기가 1,024B 이고 프로세스 A 가 3,172B 의 메모리를 요구한다면 3 개의 페이지 프레임(1,024 * 3 = 3,072) 하고도 100B 가 남기때문에 총 4 개의 페이지 프레임이 필요한 것이다. 결론적으로 4 번째 페이지 프레임에는 924B(1,024 - 100)의 여유 공간이 남게 되는 내부 단편화 문제가 발생하는 것이다.

Segmentation

논리메모리와 물리메모리를 같은 크기의 블록이 아닌, 서로 다른 크기의 논리적 단위인 세그먼트로 분할하는 방법.

사용자는 두개의 주소를 지정(세그먼트 번호 + offset)하고, 세그먼트 테이블에는 각 세그먼트의 시작 물리주소와 길이를 저장

단점

  • 서로 다른 크기의 세그먼트들이 메모리에 적재되고 제거되는 일이 반복되다 보면, 자유공간들이 많은 수의 작은 조작들로 나뉘어져 못쓰게 될 수도 있다. (외부 단편화)

가상 메모리

프로세스 전체가 메모리 내에 올라오지 않더라도 실행이 가능하도록 하는 기법으로, 프로그램이 물리 메모리보다 커도 된다는 장점이 존재.

개발 배경

프로그램의 사이즈가 커짐에 따라 물리메모리에 프로세스 전체를 올릴 수 없거나, 올릴 수 있는 프로세스의 수가 줄어들게 되었다. 또한 가끔씩만 사용되는 코드가 메모리를 차지하게 되는 문제가 발생하였다.

프로그램의 일부분만 메모리에 올릴 수 있게 되면 다음과 같은 장점이 있다.

  • 물리 메모리 크기에 프로그램의 사이즈가 제약받지 않는다.

  • 더 많은 프로그램을 동시에 실행할 수 있게 된다. 이에따라 응답시간은 유지되고, CPU 이용률처리율은 높아진다.

  • swap이 필요한 입출력이 줄어들기 때문에 프로그램들이 빠르게 실행된다.’

가상 메모리가 하는 일

가상메모리는 실제의 물리메모리 개념과 사용자의 논리메모리 개념을 분리한 것으로 정리할 수 있다. 이로써 작은 메모리를 가지고도 얼마든지 큰 가상 주소 공간을 프로그래머에게 제공할 수 있다.

가상주소공간

  • 한 프로세스가 메모리에 저장되는 논리적인 모습을 가상메모리에 구현한 공간.
  • 프로세스가 요구하는 메모리 공간을 가상메모리에서 제공함으로써 현재 직접적으로 필요치 않은 메모리 공간은 실제 물리 메모리에 올리지 않는 것으로 물리 메모리를 절약할 수 있다.
  • 예를 들어, 한 프로그램이 실행되며 논리 메모리로 100KB 가 요구되었다고 하자. 하지만 실행까지에 필요한 메모리 공간(Heap영역, Stack 영역, 코드, 데이터)의 합이 40KB 라면, 실제 물리 메모리에는 40KB 만 올라가 있고, 나머지 60KB 만큼은 필요시에 물리메모리에 요구한다고 이해할 수 있겠다.

프로세스간 페이지 공유

  • 가상메모리는 시스템 라이브러리가 여러 프로세스들 사이에 공유될 수 있도록 한다. 각 프로세스들은 공유 라이브러리를 자신의 가상 주소 공간에 두고 사용하는 것처럼 인식하지만, 라이브러리가 올라가있는 물리 메모리 페이지들은 모든 프로세스에 공유되고 있다.
  • 프로세스들이 메모리를 공유하는 것을 가능하게 하고, 프로세스들은 공유 메모리를 통해 통신할 수 있다. 이 또한, 각 프로세스들은 각자 자신의 주소 공간처럼 인식하지만, 실제 물리 메모리는 공유되고 있다.
  • fork()를 통한 프로세스 생성 과정에서 페이지들이 공유되는 것을 가능하게 한다.

Demand Paging(요구 페이징)

프로그램 실행 시작시에 필요한 것만 메모리에 적재하는 것을 의미. 가상 메모리는 대개 페이지로 관리되고, 요구 페이징을 사용하는 가상 메모리에서는 실행과정에서 필요해질 때만 페이지들을 적재한다. 한번도 접근되지 않은 페이지는 물리메모리에 적재되지 않는다.

프로세스 내의 개별 페이지들은 페이저(pager)에 의해 관리된다. 페이저는 프로세스 실행에 실제 필요한 페이지들만 메모리로 읽어옴으로써, 사용되지 않을 페이지를 가져오는 시간낭비와 메모리 낭비를 줄일 수 있다.

페이지 교체

요구 페이징에서 필요한 페이지가 물리메모리에 없는 상태를 page fault(페이지부재)라고 한다. 이때 필요한 페이지는 불러오게 되는데, 만약 물리메모리가 꽉 찬 상태라면, 이미 들어있는 페이지를 빼고 새로운 페이지를 넣어야한다. 이때의 프로세스는 다음과 같다.

  1. 디스크에서 필요한 페이지의 위치를 찾는다.
  2. 빈 페이지 프레임을 찾는다.
    1. 페이지 교체 알고리즘에 따라 희생될 페이지를 고른다.
    2. 희생될 페이지를 디스크에 기록하고, 관련 페이지 테이블을 수정한다.
  3. 새롭게 비워진 페이지 테이블 내 프레임에 새 페이지를 넣고, 프레임 테이블을 수정한다.
  4. 사용자 프로세스 재시작.

페이지 교체 알고리즘

  • FIFO
    • 장점 : 쉽다.
    • 단점
      • 활발하게 사용중인 페이지를 빼버릴 수도 있다.
      • belady의 모순 : 페이지를 저장할 수 있는 페이지 프레임 갯수를 늘려도, 되려 페이지의 부재가 많이 발생하는 모순이 발생할 수 있다.
  • 최적 페이지 교체(optimal page replacement)
    • 앞으로 가장 오랫동안 사용되지 않을 페이지를 찾아 교체하는 것. belady의 모순이 발생하지 않음.
    • 장점 : 가장 낮은 page fault rate를 가진다.
    • 단점 : 구현이 어렵다. 모든 프로세스의 메모리 참조 계획을 미리 파악할 순 없기때문
  • LRU(Least Recently used)
    • 가장 오랫동안 사용되지 않은 페이지를 선택하여 교체한다.
    • 최적근사알고리즘으로, 대체적으로 FIFO보다는 우수하고, OPT 보다는 덜 우수하다.
  • LFU(Least Frequently used)
    • 가장 적게 사용된 페이지를 교체하는 것.
    • 어떤 프로세스가 특정 페이지를 집중적으로 사용하다, 다른 기능을 사용하게되면 더 이상 사용하지 않아도 계속 메모리에 머물게 되어 초기 가정에 어긋나는 시점이 발생할 수 있다
    • 잘 안쓰임
  • MFU(Most frequently used)
    • 가장 적게 사용된 페이지는 최근에 올라왔고, 따라서 앞으로도 계속 사용할 것임을 가정한 알고리즘
    • 잘 안쓰임

Locality of cache

캐시의 지역성 원리

캐시의 hit rate를 극대화시키기위해 locality의 원리를 사용한다. 지역성의 전제조건으로 프로그램은 모든 코드나 데이터를 균등하게 access하지 않는다는 특성을 기본으로 한다. 즉, locality란 기억장치 내의 정보를 균일하게 access하는 것이 아닌 어느 한 순간에 특정 부분을 집중적으로 참조하는 특성인 것이다.

  • temporal locality : 최근에 참조된 주소의 내용은 다시 참조될 확률이 높다.
  • spatial locality : 최근에 참조된 주소와 인접한 주소의 내용은, 참조될 확률이 높다.

caching line

캐시에 저장된 데이터가 어느 곳에 저장되어 있는지 몰라 모든 데이터를 순회해야 한다면 시간이 오래 걸리게 된다. 즉, 캐시에 목적 데이터가 저장되어 있다면 바로 접근하여 출력할 수 있어야 캐시가 의미 있어진다.

따라서 캐시에 데이터를 저장할때 특정 자료구조를 사용하여 묶음으로 저장하게 되는데, 이를 캐싱라인이라고 한다.

프로세스는 다양한 주소에 있는 데이터를 사용하므로 빈번하게 사용하는 데이터의 주소 또한 흩어져있다. 따라서 캐시에 저장하는 데이터에는 데이터의 메모리 주소 등을 기록해 둔 태그를 달아놓을 필요가 있다. 이러한 태그들의 묶음을 캐싱 라인이라고 하고, 메모리로부터 가져올 때도 캐싱라인을 기준으로 가져온다. 종류로는 대표적으로 세가지 방법이 있다.

  1. Full associative : set의 개수가 1인 것. 즉, 데이터가 전체 블록에서 어디에나 들어갈 수 있음.
  2. set associative : 전체 블록을 n개의 set으로 나누어 저장. 데이터는 할당된 set에서 어떠한 블록에도 들어갈 수 있음.
  3. direct map : 데이터가 정해진 위치에만 들어갈 수 있도록 한 것.

출처

https://github.com/JaeYeopHan/Interview_Question_for_Beginner/tree/master/OS#%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4%EC%99%80-%EC%8A%A4%EB%A0%88%EB%93%9C%EC%9D%98-%EC%B0%A8%EC%9D%B4

This post is licensed under CC BY 4.0 by the author.

1522 문자열 교환

2631 줄세우기

Comments powered by Disqus.

diff --git a/posts/paasta-noti/index.html b/posts/paasta-noti/index.html new file mode 100644 index 000000000..54efddcf5 --- /dev/null +++ b/posts/paasta-noti/index.html @@ -0,0 +1 @@ + expo push notification | 디피의 개발일지
Posts expo push notification
Post
Cancel

expo push notification

paasta notification 회의

  • 방법들
    1. expo 서버로 보내는 방법 (https://docs.expo.dev/push-notifications/sending-notifications/)
      • expo에서 제공하는 라이브러리를 사용하면 됨.
      • but 자바 sdk를 제공하긴 하는데 이건 maven 기반이고, 현재 우리 프로젝트는 gradle 기반임
    2. fcm, apns 에 직접 보내는 방법 (https://docs.expo.dev/push-notifications/sending-notifications-custom/)
      • 두가지 코드를 써야함
      • 애플 개발자 계정 필요
    3. fcm에 apns를 연동하고, 2번에서 fcm으로만 보내는 방법
      • 공식 방법이 아니라 제대로 동작할지 의문.
      • 한번 테스트가 필요함
      • 애플 개발자 계정이 필요
    4. expo-notification 라이브러리를 포기하고, plain react-native 방법으로 noti 구현하기
      • 프론트쪽 코스트가 크게 발생함.
      • 불가능한 건 아닌데 기능에 비해 코스트가 너무 큼. notification과 연관없는 부분까지 영향을 끼치게 되서
      • 방법
        1. expo bare workflow - native 코드를 들어내는 작업
          • expo run:ios : xcode 필요해서 맥에서 해야함
          • 프로젝트가 bare 모드로 바뀌어서 나만이 아니라 다른 팀원들과 다른 코드들도 영향을 받게됨.
          • 설정하나하나를 직접 해줘야하는 코스트가 발생하게 되어 사실상 expo를 사용하는 이유가 줄어듦
        2. react-native-firebase 사용
          • ios 개발자계정 필요
          • 메세지 기능만 사용하기엔 너무 거대한 라이브러리.
This post is licensed under CC BY 4.0 by the author.
diff --git a/posts/pagehide/index.html b/posts/pagehide/index.html new file mode 100644 index 000000000..31feda6a0 --- /dev/null +++ b/posts/pagehide/index.html @@ -0,0 +1,17 @@ + beforeunload vs pagehide | 디피의 개발일지
Posts beforeunload vs pagehide
Post
Cancel

beforeunload vs pagehide

ios 모바일에서 새로고침시 현재 스크롤 위치를 고정하기 위해 다음과 같은 코드를 사용하였다.

1
+2
+3
+4
+5
+6
+7
+8
+
if (sessionStorage.scrollPosition) {
+  window.scrollTo(0, sessionStorage.scrollPosition);
+}
+
+window.onbeforeunload = function () {
+  sessionStorage.scrollPosition = document.body.scrollTop || document.documentElement.scrollTop;
+};
+
+

하지만 제대로 동작하지 않았는데, 찾아보니 ios에서는 beforeunload 이벤트를 지원해주지 않는다는 을 읽었다. (글에서는 ios safari를 지원하지 않는다고 명시되어있지만, 크롬에서도 동작하지 않았다.)

대신에 pagehide를 사용하도록 안내받았고, 실제로 동작했다. 하지만 둘의 차이점에 대해서 명확히 알고 넘어가기로 했다.


beforeunload vs pagehide

beforeunload

  • window, document, resource가 unload 되려할 때 발생하는 이벤트.
  • document는 계속 보이고 있고, 이벤트는 취소할 수 있다. 따라서 실제로 페이지를 떠날건지 사용자에게 물어볼 수 있다.
  • 모바일에서 다음과 같은 상황에서는 발생하지 않는다.
    1. 모바일 사용자가 페이지를 방문한다
    2. 사용자가 다른 앱으로 전환한다.
    3. 사용자가 앱 관리자에서 브라우저를 닫는다.
  • back/forward cache 와 호환되지 않는다. 이벤트 후에 더이상 페이지가 존재하지 않을 것이라고 가정하기 때문이다. 따라서 브라우저는 unload 리스너가 있으면 bfcache에 페이지를 넣지않는다. 하지만 이는 성능에 악영향을 끼친다. 따라서 성능 악영향을 최소화하기 위해 저장되지 않은 변경 사항이 있을 때만 unload 리스너를 붙이는게 좋다.

pagehide

  • 세션 히스토리에서 다른 페이지를 표시하는 과정에서 브라우저가 현재 페이지를 숨길때 Window에서 발생한다.
    • ex) 뒤로가기 눌렀을 때 이전 페이지가 보이기 전에 발생
  • unload 이벤트와 마찬가지로 모바일에서 안정적으로 동작하지 않는다.
  • back/forward cache와 호환된다. 따라서 이 이벤트에 리스너를 붙이는 건 bfcache를 비활성화시키지 않는다.


의문 : 왜 beforeunload 는 ios에서 동작하지 않지?

beforeunload와 pagehide의 가장 큰 차이는 bfcache와 호환되지는 안되는지라는 건 알았다.

하지만 다시 최초로 돌아가서 왜 beforeunload는 ios에서 동작하지 않는지 의문이 든다. 왜냐면 MDN 공식문서에서는 safari IOS에서 버전 1부터 지원한다고 적혀있기 때문이다.

1시간 정도 조사를 해본 결과 아래와 같은 단서를 찾았다.

단서 1

  • ios에 버그가 있다고 한다.
  • 링크를 클릭해서 다른 곳으로 이동하는 경우나 history.back()을 사용하는 경우에는 동작하는데, history.back()으로 다른 도메인으로 이동할 때는 동작하지 않았다고 한다.
  • 새로고침 동작에서도 버그일수도 있겠다는 생각이 들었다.

단서 2

  • webkit의 known issue이다
  • 애플에서 의도적으로 막았다(?)

이후 좀 더 조사를 해봤지만, ios에서는 지원을 안한다, ios의 버그이다라는 말 이외에 찾을 수 없었다. 추정만 있지 명확한 공식문서는 볼 수 없었다.

향후 beforeunload 이벤트가 필요해졌을 떄 한번 더 찾아봐야할거 같다.

This post is licensed under CC BY 4.0 by the author.

useState 원리

bfcache

Comments powered by Disqus.

diff --git "a/posts/php-php-\352\260\225\354\235\230/index.html" "b/posts/php-php-\352\260\225\354\235\230/index.html" new file mode 100644 index 000000000..2ededabc7 --- /dev/null +++ "b/posts/php-php-\352\260\225\354\235\230/index.html" @@ -0,0 +1,93 @@ + php 기초 강의 | 디피의 개발일지
Posts php 기초 강의
Post
Cancel

php 기초 강의

PHP 기초

PHP의 원리

php 원리

  1. 웹브라우저에서 웹 서버로 index.php을 달라고 요청보냄
  2. 웹서버는 index.php을 처리할 수 있는 프로그램인 php에게 위임함
  3. php는 index.php를 찾고, 해석해서 html 파일을 만듬.
  4. 웹 서버는 html을 받고 웹브라우저로 보내줌.

PHP의 데이터 타입

숫자와 산술연산자

  • int : 정수형
  • float : 소수형

문자열과 문자열 처리

  • string
    • ’’
    • ””
  • 연산자
    • . : 문자열 붙이기 ex) “hello “.”world” -> “hello world”

변수

  • $변수명 으로 선언

PHP와 URL 쿼리

php 입력으로 URL 쿼리에 접근할 때는 $_GET['key']로 접근한다.

ex)

/index.php?name=shin

-> <?php echo $_GET['name']; ?>

-> shin 출력

PHP 함수의 사용

php에는 많은 내장함수가 있음. 대표적으로는 문자열의 길이를 알려주는 strlen(), 새줄을 <br>로 바꿔주는 nl2br

내장함수 레퍼런스

반복문

배열의 형식

array() 함수로 배열을 만든다.

키 없이 생성하면 인덱스로 접근가능하고, 키를 함께 선언하면 키로 접근 가능하다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+
<?php
+  $cars=array("volvo", "bmw");
+	echo $cars[1];
+?>
+<?php
+  $cars_with_key = array(
+    'aaa' => "volvo");
+	echo $cars['aaa'];
+?>
+

array 문서

웹 어플리케이션

form과 post

FORM의 action으로 php를 주어 데이터를 전송할 수 있고, POST로 넘긴 데이터는 $_POST['key']로 접근할 수 있다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
<!doctype html>
+<html>
+  <body>
+    <form action="form.php" method="post">
+      <p><input type="text" name="title" placeholder="Title"></p>
+      <p><textarea name="description"></textarea></p>
+      <p><input type="submit"></p>
+    </form>
+  </body>
+</html>
+
+
1
+2
+3
+4
+
// form.php
+<?php
+file_put_contents('data/'.$_POST['title'], $_POST['description']);
+?>
+

기타 predefined variable

글 수정 기능 구현

php는 html 태그 중간에도 삽입이 가능하다. 이는 문자열인 경우에도 마찬가지이다. 이를 통해 input 태그에 수정할 값을 디폴트 값으로 지정이 가능하다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+
<form action="update_process.php" method="post">
+		<input type="hidden" name="old_title" value="<?=$_GET['id']?>">
+		<p>
+			<input type="text" name="title" placeholder="Title" value="<?php print_title(); ?>">
+		</p>
+		<p>
+			<textarea name="description" placeholder="Description">
+  			<?php print_description(); >
+  		</textarea>
+		</p>
+		<p>
+			<input type="submit">
+		</p>
+</form>
+

파일명 변경은 rename() 메서드를 사용하여 가능하다.

1
+2
+3
+4
+5
+
<?php
+rename('data/'.$_POST['old_title'], 'data/'.$_POST['title']);
+file_put_contents('data/'.$_POST['title'], $_POST['description']);
+header('Location: /index.php?id='.$_POST['title']);
+?>
+

필요한 경우에만 html 태그를 노출 시키는 것도 가능하다. 예를들어 아래 코드는 URL에 id 쿼리값이 있어야만 update 링크를 보이도록 한 예시이다.

1
+2
+3
+
<?php if(isset($_GET['id'])) { ?>
+	<a href="update.php?id=<?=$_GET['id']?>">update</a>
+<?php } ?>
+

마무리

파일로 모듈화 - require

  • require() : 필요한 파일을 불러옴. 파일이 없을 시 fatal error가 나타난다.
  • require_once() : require()와 동일하지만, 여러 모듈에 거쳐 한번만 불러옴.
  • include() : 필요한 파일을 불러옴. 파일이 없을 시 warning이 나타나지만 스크립트가 계속 실행됨.
  • include_once() : include()와 동일. 여러 모듈에 거쳐 한번만 불러옴.

보안

XSS

  • htmlspecialchars() : 내부에 스크립트 코드가 있어도 그대로 문자열로 출력함.
  • strip_tags() : allowable_tags로 허용할 태그만 남기고, 나머지 태그는 전부 지운 후 출력

파일경로보호

  • basename() : ., .. 와 같은 상대경로를 지우고, 파일명만 남기는 함수
    • ex) ../password.txt -> password.txt

출처

인프런 web2 - php 강의

This post is licensed under CC BY 4.0 by the author.

쿠버네티스 Ingress

next.js - data fetching

Comments powered by Disqus.

diff --git a/posts/prefix-postfix/index.html b/posts/prefix-postfix/index.html new file mode 100644 index 000000000..12a7c4b30 --- /dev/null +++ b/posts/prefix-postfix/index.html @@ -0,0 +1,17 @@ + 전위식/후위식 | 디피의 개발일지
Posts 전위식/후위식
Post
Cancel

전위식/후위식

전위식

연산자를 먼저 표시하고 연산에 필요한 피연산자를 나중에 표시

1
+2
+3
+4
+
(A + B) * (C - D)
+((A + B) * (C - D))
+*(+(AB)-(CD))
+*+AB-CD
+


후위식

피연산자를 먼저 표시하고, 연산자를 나중에 표시

1
+2
+3
+4
+
(A+B) * (C-D)
+((A+B) * (C-D))
+((AB)+(CD)-)*
+AB+CD-*
+
This post is licensed under CC BY 4.0 by the author.

통합 테스트

블랙박스/화이트박스 테스트

Comments powered by Disqus.

diff --git a/posts/promise-async-await/index.html b/posts/promise-async-await/index.html new file mode 100644 index 000000000..596b6d3c9 --- /dev/null +++ b/posts/promise-async-await/index.html @@ -0,0 +1,261 @@ + Promise, async/await | 디피의 개발일지
Posts Promise, async/await
Post
Cancel

Promise, async/await

Promise

비동기로 처리하기 위한 방법 중 하나.

다음과 같이 사용

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+
//Promise 선언
+var _promise = function (param) {
+  return new Promise(function (resolve, reject) {
+    window.setTimeout(function () {
+      if (param) {
+        resolve("해결 완료");
+      } else {
+        reject(Error("실패!!"));
+      }
+    }, 3000);
+  });
+};
+
+//Promise 실행
+_promise(true).then(
+  function (text) {
+    // 성공시
+    console.log(text);
+  },
+  function (error) {
+    // 실패시
+    console.error(error);
+  }
+);
+

위에서는 .then()에 함수를 두개 넣어서 각각 성공, 실패시에 대응하고 있다. 하지만 에러를 잡아낼때는 다음과 같이 .catch()가 더 자주쓰인다. 체이닝 중간에 넣을 수 있기때문이다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+
asyncThing1()
+  .then(function () {
+    return asyncThing2();
+  })
+  .then(function () {
+    return asyncThing3();
+  })
+  .catch(function (err) {
+    return asyncRecovery1();
+  })
+  .then(
+    function () {
+      return asyncThing4();
+    },
+    function (err) {
+      return asyncRecovery2();
+    }
+  )
+  .catch(function (err) {
+    console.log("Don't worry about it");
+  })
+  .then(function () {
+    console.log("All done!");
+  });
+

위 로직을 그림으로 표현하면 다음과 같다

img


Promise.all

여러 프로미스를 모두 완료될때까지 기다리는 방법이다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+
var promise1 = new Promise(function (resolve, reject) {
+  window.setTimeout(function () {
+    console.log("첫번째 Promise 완료");
+    resolve("11111");
+  }, Math.random() * 20000 + 1000);
+});
+
+var promise2 = new Promise(function (resolve, reject) {
+  window.setTimeout(function () {
+    console.log("두번째 Promise 완료");
+    resolve("222222");
+  }, Math.random() * 10000 + 1000);
+});
+
+Promise.all([promise1, promise2]).then(function (values) {
+  // values에는 배열 형태로 resolved value가 들어옴
+  console.log("모두 완료됨", values);
+});
+


바로 new Promise 생성하기

Promise.all 예시처럼 return 하지 않고 바로 new Promise를 생성하면, 파라미터로 넘겨준 익명함수는 바로 실행된다. 따라서 다음과 같이 사용가능하다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+
new Promise(function (resolve, reject) {
+  if (+new Date() % 2 === 0) {
+    resolve("Stuff worked!");
+  } else {
+    reject(Error("It broke"));
+  }
+})
+  .then(alert)
+  .catch(alert);
+

만약 Promise.all 예시에서 return으로 Promise 객체를 반환했다면 코드를 아래와 같이 변경해야한다.

1
+2
+3
+
Promise.all([promise1(), promise2()]).then(function (values) {
+  console.log("모두 완료됨", values);
+});
+



Async, await

Promise와 같이 비동기를 처리하는 방법이다. 하지만 좀 더 간단하고 이해하기 쉽다.

  • Promise로 구현
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+
function makeRequest() {
+  return getData()
+    .then((data) => {
+      if (data && data.needMoreRequest) {
+        return makeMoreRequest(data)
+          .then((moreData) => {
+            console.log(moreData);
+            return moreData;
+          })
+          .catch((error) => {
+            console.log("Error while makeMoreRequest", error);
+          });
+      } else {
+        console.log(data);
+        return data;
+      }
+    })
+    .catch((error) => {
+      console.log("Error while getData", error);
+    });
+}
+
+const makeRequest = () => {
+  return promise1().then((value1) => {
+    // do something
+    return promise2(value1).then((value2) => {
+      // do something
+      return promise3(value1, value2);
+    });
+  });
+};
+
  • async/await로 구현
    • 에러처리는 try/catch로 할 수 있다.
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+
async function makeRequest() {
+  try {
+    const data = await getData();
+    if (data && data.needMoreRequest) {
+      const moreData = await makeMoreRequest(data);
+      console.log(moreData);
+      return moreData;
+    } else {
+      console.log(data);
+      return data;
+    }
+  } catch (error) {
+    console.log("Error while getData", error);
+  }
+}
+
+const makeRequest = async () => {
+  const value1 = await promise1();
+  const value2 = await promise2(value1);
+  return promise3(value1, value2);
+};
+

출처

https://programmingsummaries.tistory.com/325

https://github.com/JaeYeopHan/Interview_Question_for_Beginner/tree/master/JavaScript#promise

This post is licensed under CC BY 4.0 by the author.

this에 대해서

Arrow function

Comments powered by Disqus.

diff --git a/posts/react-SWR/index.html b/posts/react-SWR/index.html new file mode 100644 index 000000000..09af8ce3d --- /dev/null +++ b/posts/react-SWR/index.html @@ -0,0 +1,535 @@ + SWR | 디피의 개발일지
Posts SWR
Post
Cancel

SWR

SWR은 vercel에서 제작한 React Hooks로, 먼저 캐시(stale)로부터 데이터를 반환한 후, fetch 요청(revalidate)을 하고, 최종적으로 최신화된 데이터를 가져오는 전략이다. 이름은 HTTP 캐시 무효 전략인 stale-while-revalidate에서 유래되었다.

전체적인 기능은 React Query와 비슷하며 React Query 쪽의 커뮤니티가 더 크다. 따라서 사용하기 전에 장단을 잘 비교해보고 둘 중 하나를 선택하는 것이 좋을 것 같다.


예시

1
+2
+3
+4
+5
+6
+7
+8
+9
+
import useSWR from 'swr'
+
+function Profile() {
+  const { data, error, isLoading } = useSWR('/api/user', fetcher)
+
+  if (error) return <div>failed to load</div>
+  if (isLoading) return <div>loading...</div>
+  return <div>hello {data.name}!</div>
+}
+
  • useSWR hook은 keyfetcher 함수를 받는다.
    • key는 고유한 식별자이며, fetcher로 전달된다.
    • fetcher는 데이터를 반환하는 어떠한 비동기 함수도 될 수 있다.
  • useSWR은 dataerror, isLoading을 반환한다.

SWR이 해결하는 문제

전통적으로 리액트에서 비동기적인 데이터 로딩을 처리하는 방식은 다음과 같다.

  1. 최상위 레벨 컴포넌트에서 useEffect를 사용해 데이터를 받아온다.
  2. props를 통해 자식 컴포넌트에 전달한다.
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+
// 페이지 컴포넌트
+
+function Page () {
+  const [user, setUser] = useState(null)
+
+  // 데이터 가져오기
+  useEffect(() => {
+    fetch('/api/user')
+      .then(res => res.json())
+      .then(data => setUser(data))
+  }, [])
+
+  // 전역 로딩 상태
+  if (!user) return <Spinner/>
+
+  return <div>
+    <Navbar user={user} />
+    <Content user={user} />
+  </div>
+}
+
+// 자식 컴포넌트
+
+function Navbar ({ user }) {
+  return <div>
+    ...
+    <Avatar user={user} />
+  </div>
+}
+
+function Content ({ user }) {
+  return <h1>Welcome back, {user.name}</h1>
+}
+
+function Avatar ({ user }) {
+  return <img src={user.avatar} alt={user.name} />
+}
+

보통 최상위 레벨 컴포넌트에서 가져온 모든 데이터를 유지하고, 트리 아래의 모든 자식 컴포넌트의 props로 추가해야한다. 페이지에 더 많은 데이터 의존성을 추가한다면 코드는 점점 더 유지보수가 어려워진다.

React Context나 global state를 사용하여 props 전달을 막을 수 있지만, 동적콘텐츠 문제가 여전히 존재한다. 페이지 콘텐츠 내 컴포넌트들은 동적일 수 있으며 최상위 레벨 컴포넌트는 자식 컴포넌트가 필요로 하는 데이터가 뭔지 정확히 알 수 없다.

SWR은 이 문제를 해결한다. SWR에서 위 코드는 다음과 같이 리팩토링 할 수 있다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+
function useUser () {
+  const { data, error, isLoading } = useSWR(`/api/user`, fetcher)
+
+  return {
+    user: data,
+    isLoading,
+    isError: error
+  }
+}
+
+// 페이지 컴포넌트
+function Page () {
+  return <div>
+    <Navbar />
+    <Content />
+  </div>
+}
+
+// 자식 컴포넌트
+function Navbar () {
+  return <div>
+    ...
+    <Avatar />
+  </div>
+}
+
+function Content () {
+  const { user, isLoading } = useUser()
+  if (isLoading) return <Spinner />
+  return <h1>Welcome back, {user.name}</h1>
+}
+
+function Avatar () {
+  const { user, isLoading } = useUser()
+  if (isLoading) return <Spinner />
+  return <img src={user.avatar} alt={user.name} />
+}
+
+

데이터는 이제 데이터가 필요한 컴포넌트로 범위가 제한되었으며 모든 컴포넌트는 서로에게 독립적이다. 모든 부모 컴포넌트들은 데이터나 데이터 전달에 관련된 것들을 알 필요가 없다. 그냥 렌더링만 담당한다.

또한 동일한 SWR 키를 사용한다면, 요청은 중복 제거, 캐시, 공유되므로 단 한번의 요청만 API로 전송되어 네트워크 비용의 낭비도 없다.

또한 애플리케이션은 이제 사용자 포커스나 네트워크 재연결 시에 데이터를 갱신할 수 있다. 브라우저 탭을 전환할 때 자동으로 데이터가 갱신된다는 것을 의미한다.


기능

SWR의 기능에는 다음과 같은 것이 있다고 한다. 다 다루기에는 너무 많기에 이 글에서는 개인적으로 흥미있는 것들만 정리하였다.

  • 빠르고, 가볍고, 재사용 가능한 데이터 가져오기
  • 내장된 캐시 및 요청 중복 제거
  • 실시간 경험
  • 전송 및 프로토콜에 구애받지 않음
  • SSR / ISR / SSG support
  • TypeScript 준비
  • React Native

  • 빠른 페이지 네비게이션
  • 인터벌 폴링
  • 데이터 의존성
  • 포커스시 재검증
  • 네트워크 회복시 재검증
  • 로컬 뮤테이션(Optimistic UI)
  • 스마트한 에러 재시도
  • 페이지 및 스크롤 위치 복구
  • React Suspense


프리패칭

최상위 레벨 페이지 데이터

` rel=”preload”를 권장함. 아래와 같이 html 코드를 작성 후, <head>`에 넣기만 하면 됨.

1
+
<link rel="preload" href="/api/data" as="fetch" crossorigin="anonymous">
+

rel="preload"는 페이지 요청 시 해당 소스 자원을 우선적으로 로드하라는 뜻이고, 따라서 JS가 다운로드 되기 전에 데이러를 프리패칭한다. 동일한 URL로 fetch 요청 결과는 재사용된다.

프로그래밍 방식으로 프리패치

SWR은 preload API를 제공하여 자원을 프리패치하고 캐시 안에 저장할 수 있게 해준다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+
function App({ userId }) {
+  const [show, setShow] = useState(false)
+ 
+  // preload in effects
+  useEffect(() => {
+    preload('/api/user?id=' + userId, fetcher)
+  }, [userId])
+ 
+  return (
+    <div>
+      <button
+        onClick={() => setShow(true)}
+        {/* preload in event callbacks */}
+        onHover={() => preload('/api/user?id=' + userId, fetcher)}
+      >
+        Show User
+      </button>
+      {show ? <User /> : null}
+    </div>
+  )
+}
+

next.js 페이지 프리패칭과 함께 다음 페이지와 데이터 모두를 즉시 로드할 수 있다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+
import useSWR, { preload } from 'swr'
+ 
+// should call before rendering
+preload('/api/user', fetcher);
+preload('/api/movies', fetcher);
+ 
+const Page = () => {
+  // useSWR hook이 렌더링을 지연시키지만, "/api/user", "/api/movies"는 이미 프리로딩을 시작하고 있고,
+  // 따라서 waterfall 문제는 발생하지 않는다.
+  const { data: user } = useSWR('/api/user', fetcher, { suspense: true });
+  const { data: movies } = useSWR('/api/movies', fetcher, { suspense: true });
+  return (
+    <div>
+      <User user={user} />
+      <Movies movies={movies} />
+    </div>
+  );
+}
+

데이터 프리필

이미 존재하는 데이터를 SWR 캐시에 미리 채우길 원한다면, fallbackData 옵션을 사용할 수 있다.

1
+
useSWR('/api/data', fetcher, { fallbackData: prefetchedData })
+

SWR가 데이터를 아직 가져오지 않았다면, 폴백으로 prefetchedData를 반환할 것이다.

<SWRConfig>fallback 옵션을 사용하여 모든 SWR hooks 및 다중 키에 대해서도 이것을 구성할 수 있다.


Next.js SSG 및 SSR

기본값으로 프리렌더링하기

페이지가 프리렌더링 되어야하면, Next.js는 2가지 형태의 프리렌더링을 지원한다.

  • 정적생성 (SSG)
  • 서버 사이드 렌더링 (SSR)

SWR와 함께 SEO를 위해 페이지를 프리렌더링할 수 있고, 캐싱, 재검증, 포커스 추적, 클라이언트 사이드에서 간격을 두고 다시 가져오기와 같은 기능도 있다.

모든 SWR hooks에 초기값으로 프리패칭된 데이터를 넘겨주기 위해 SWRConfigfallback 옵션을 사용할 수 있다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+
 export async function getStaticProps () {
+  // `getStaticProps`는 서버 사이드에서 실행됩니다.
+  const article = await getArticleFromAPI()
+  return {
+    props: {
+      fallback: {
+        '/api/article': article
+      }
+    }
+  }
+}
+ 
+function Article() {
+  // `data`는 `fallback`에 있기 때문에 항상 사용할 수 있습니다.
+  const { data } = useSWR('/api/article', fetcher)
+  return <h1>{data.title}</h1>
+}
+ 
+export default function Page({ fallback }) {
+  // `SWRConfig` 경계 내부에 있는 SWR hooks는 해당 값들을 사용합니다.
+  return (
+    <SWRConfig value=>
+      <Article />
+    </SWRConfig>
+  )
+}
+

Complex Keys

useSWRarrayfunction 타입을 key로 사용할 수 있다. 이 타입의 키를 이용해 미리 패치된 데이터를 사용하기 위해선 fallback key들을 unstable_serialize와 함께 직렬화 해야한다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+
import useSWR, { unstable_serialize } from 'swr'
+ 
+export async function getStaticProps () {
+  const article = await getArticleFromAPI(1)
+  return {
+    props: {
+      fallback: {
+        // unstable_serialize()에 배열 스타일의 키
+        [unstable_serialize(['api', 'article', 1])]: article,
+      }
+    }
+  }
+}
+ 
+function Article() {
+  // 배열 스타일의 키 사용
+  const { data } = useSWR(['api', 'article', 1], fetcher)
+  return <h1>{data.title}</h1>
+}
+ 
+export default function Page({ fallback }) {
+  return (
+    <SWRConfig value=>
+      <Article />
+    </SWRConfig>
+  )
+}
+


Suspense

리액트의 Suspense API와 함께 사용할 수도 있다. suspense 옵션을 활성화하면 된다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+
import { Suspense } from 'react'
+import useSWR from 'swr'
+ 
+function Profile () {
+  const { data } = useSWR('/api/user', fetcher, { suspense: true })
+  return <div>hello, {data.name}</div>
+}
+ 
+function App () {
+  return (
+    <Suspense fallback={<div>loading...</div>}>
+      <Profile/>
+    </Suspense>
+  )
+}
+

서스펜스 모드에서 data는 항상 응답을 가져와서 undefined를 검사할 필요가 없다. 하지만 에러가 발생할 경우 ErrorBoundary를 사용해 캐치해야한다.

conditional fetch와 함께 사용하기

일반적으로 suspense를 활성화하면 렌더링 시에 data는 undefined를 갖지 않는다.

하지만 conditional fetch나 의존적 fetch와 함께 사용하면 data는 요청이 일시 중단된 경우 undefined가 된다.

1
+2
+3
+4
+5
+6
+
function Profile () {
+  const { data } = useSWR(isReady ? '/api/user' : null, fetcher, { suspense: true })
+ 
+  // `isReady`가 false이면 `data`는 `undefined`입니다
+  // ...
+}
+

자세한 내용

Server-Side Rendering

suspense를 SSR에서 사용하려면 fallbackData나 fallback을 통해 초기 데이터를 반드시 넣어줘야한다. 이것은 <Suspense>를 서버사이드에서 데이터를 가져오는데 사용할 수 없고, 클라이언트사이드나 getStaticProps 등 프레임워크에서 제공하는 data fetching 메소드에서만 가져올 수 있다는 말이다.

자세한 내용


Middleware

SWR hook의 전후에 로직을 실행할 수 있도록 해줌. 여러 미들웨어가 존재하면 각 미들웨어는 다음 미들웨어를 감쌈. 마지막 미들웨어가 원본 SWR hook useSWR을 받는다.

API

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
function myMiddleware (useSWRNext) {
+  return (key, fetcher, config) => {
+    // hook을 실행하기 전...
+    
+    // 다음 미들웨어를 처리하거나, 마지막인 경우 `useSWR` hook을 처리합니다.
+    const swr = useSWRNext(key, fetcher, config)
+ 
+    // hook을 실행한 후...
+    return swr
+  }
+}
+
1
+2
+3
+4
+5
+
<SWRConfig value=>
+ 
+// 또는...
+ 
+useSWR(key, fetcher, { use: [myMiddleware] })
+

확장하기

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+
function Bar () {
+  useSWR(key, fetcher, { use: [c] })
+  // ...
+}
+ 
+// useSWR(key, fetcher, { use: [a, b, c] }) 와 같음.
+function Foo() {
+  return (
+    <SWRConfig value=>
+      <SWRConfig value=>			
+        <Bar/>
+      </SWRConfig>
+    </SWRConfig>
+  )
+}
+

사용 예제 : 이전 결과 유지하기

useSWR의 키가 변경되었더라도 새로운 데이터가 로드되기 전까지는 여전히 이전 결과를 반환받길 원할 수 있다.

이는 useRef와 함께 사용하여 지연 미들웨어로 구축할 수 있다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+
import { useRef, useEffect, useCallback } from 'react'
+ 
+// 키가 변경되더라도 데이터를 유지하기 위한 SWR 미들웨어입니다.
+function laggy(useSWRNext) {
+  return (key, fetcher, config) => {
+    // 이전에 반환된 데이터를 저장하기 위해 ref를 사용합니다.
+    const laggyDataRef = useRef()
+ 
+    // 실제 SWR hook.
+    const swr = useSWRNext(key, fetcher, config)
+ 
+    useEffect(() => {
+      // 데이터가 undefined가 아니면 ref를 업데이트합니다.
+      if (swr.data !== undefined) {
+        laggyDataRef.current = swr.data
+      }
+    }, [swr.data])
+ 
+    // 지연 데이터가 존재할 경우 이를 제거하기 위한 메서드를 노출합니다.
+    const resetLaggy = useCallback(() => {
+      laggyDataRef.current = undefined
+    }, [])
+ 
+    // 현재 데이터가 undefined인 경우에 이전 데이터로 폴백
+    const dataOrLaggyData = swr.data === undefined ? laggyDataRef.current : swr.data
+ 
+    // 이전 데이터를 보여주고 있나요?
+    const isLagging = swr.data === undefined && laggyDataRef.current !== undefined
+ 
+    // `isLagging` 필드 또한 SWR에 추가합니다.
+    return Object.assign({}, swr, {
+      data: dataOrLaggyData,
+      isLagging,
+      resetLaggy,
+    })
+  }
+}
+


데이터 상태

useSWR이 반환하는 데이터 중 두가지가 현재 데이터의 상태를 알려줌

  • isLoading : 처음 또는 key 변경으로 데이터를 로딩하고 있을 때 true
  • isValidading : 데이터를 로딩하거나, revalidaing 할 때 true
This post is licensed under CC BY 4.0 by the author.

next.js Compiler

module federation

Comments powered by Disqus.

diff --git a/posts/react-boilerplate/index.html b/posts/react-boilerplate/index.html new file mode 100644 index 000000000..bef087acf --- /dev/null +++ b/posts/react-boilerplate/index.html @@ -0,0 +1 @@ + React-boilterplate 설명 | 디피의 개발일지
Posts React-boilterplate 설명
Post
Cancel

React-boilterplate 설명

react-boilerplate 설명

  • react-boilerplate 라는, 리액트 프로젝트를 처음 시작할때 create-react-app을 대체하여 사용하기에 아주 좋아보이는 프로젝트가 있다.

    https://github.com/react-boilerplate/react-boilerplate

  • 상당히 많은 초기 설정을 하는데, 너무 많아서 뭐가 뭔지 알아보기가 어렵다.

  • 따라서 이 초기설정들을 설명해놓은 글이 있고, 이 포스트는 이 글을 보고 공부한 내용을 적은 것이다.

    • https://github.com/react-boilerplate/react-boilerplate/blob/master/docs/general/introduction.md

Tech stack

  • Core
    • React
    • React Router
    • Redux
    • Redux Saga
    • Reselect
    • Immer
    • Styled Componentes
  • Unit Testing
    • Jest
    • react-testing-library
  • Linting
    • ESLint
    • Prettier
    • stylelint

Project Structure

app/

  • 앱을 개발하는 곳
  • container, component 구조를 사용
    • container : redux store랑 연결 되어있는 컴포넌트를 포함
    • components : container에 데이터로 종속된 컴포넌트들을 포함
    • 컨테이너는 어떻게 동작하는지를 정의하고, 컴포넌트는 어떻게 보이는지를 정의함

internals/

  • Configuration, generator, template을 포함
  • 앱의 엔진이라고 부를 수도 있음.
  • 우리의 소스코드는, 웹팩을 거쳐 브라우저가 이해할 수 있는 형태로 변환된다.
  • internals/webpack
    • ECMAScript 6 or ECMAScript 7 를 사용할텐데, 웹백이 주요 브라우저와의 호환성을 맞춰준다.
  • internals/generators
    • 새로운 컴포넌트, 컨테이너, 라우트를 스캐폴딩한다
  • internals/mocks
    • jest가 테스팅할때 필요한 mocks를 포함함

server/

  • development and production server configuration 파일을 포함

Basic building Block

app/app.js 설정

  • @babel/polyfill : generator function, Promise 등을 가능하게 함
  • history : 브라우저 히스토리를 기억함. ConnectedRouter에서 사용됨
  • redux store 가 instantiated 됨
  • ReactDOM.render() : <App /> 만이 아니라, <Provider /> <LanguageProvider /> <ConenctedRouter /. 또한 렌더함
  • Hot module replacement 가 vanilla webpack HMR 를 통해 셋업됨.
    • 모든 reducer, injected sagas, componentes, containers, i18n message를 hot reloadable 가능 하게 함
  • i18n internatinalization support setup
  • Offline 플러그인이 포함되어 앱을 offine-first하게 함(https://developers.google.com/web/fundamentals/codelabs/offline)
  • <Provider /> 가 redux store와 앱을 연결시킴
  • <LanguageProvider /> 가 language translation을 지원함

Redux

  • configureStore.js에 store 설정이 있음.
  • 사용중인 middleware
    • Router middleware : route와 redux store의 동기를 담당
    • Redux saga

Reselect

  • redux state를 슬라이싱하고, 컴포넌트와 관련있는 sub-tree만 제공함.
  • Computational power, memoization, composability 라는 세가지 feature를 가지고 있음.
  • 자세히는 관련 문서를 봐야할 거 같음.

Linting

  • ESLint, stylelint, Prettier 가 설정되어있어, 세이브할때마다 자동으로 검사해줌
  • git hook 또한 설정되어있어서 커밋할때마다 자동으로 코드 상의 에러를 분석/수정 해줌.
  • 사용하고 싶지 않으면 package.json의 lint-staged 섹션을 봐라.
This post is licensed under CC BY 4.0 by the author.

9251 LCS

expo 배포

Comments powered by Disqus.

diff --git a/posts/react-key/index.html b/posts/react-key/index.html new file mode 100644 index 000000000..ddff806a8 --- /dev/null +++ b/posts/react-key/index.html @@ -0,0 +1,91 @@ + React에서 Key를 사용하는 이유 | 디피의 개발일지
Posts React에서 Key를 사용하는 이유
Post
Cancel

React에서 Key를 사용하는 이유

리액트로 개발을 하다가 리스트를 만들면, 다음과 같이 key를 사용하라는 안내문이 뜬다.

key_error

평소엔 무시하고 key를 그냥 넣었지만, 왜 그런지 갑자기 궁금해져 찾아보았다.

Recursing On Children

해당 내용은 리액트 공식문서에 Recursing On Children으로 나와있다.

리액트 리렌더링 과정에서 리스트를 처리할때, 리액트는 기본적으로 실제 DOM의 리스트와 virtual DOM의 변경된 리스트를 비교한다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+
// 전
+<ul>
+   <li>first</li>
+   <li>second</li>
+</ul>
+
+// 후
+<ul>
+   <li>first</li>
+   <li>second</li>
+   <li>third</li>
+</ul>
+

예를 들어, 위와 같이 기존 리스트의 마지막에 새로운 요소가 추가되었다고 해보자. 이때 리액트는 비교알고리즘을 통해 리스트를 차례대로 비교한다. 먼저 <li>first</li>이 서로 같다는 걸 확인하고, <li>second</li>이 같다는 걸 확인한다. 그리고 <li>third</li>이 다르다는 걸 확인하고, <li>third</li> 을 트리에 추가하는 리렌더링을 수행한다.

이와 같이 맨 마지막에 요소를 추가하는 경우, 변경된 요소만 추가하기에 문제 없다. 하지만 문제는 다음과 같은 경우이다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+
<ul>
+   <li>Duke</li>
+   <li>Villanova</li>
+</ul>
+
+<ul>
+   <li>Connecticut</li>
+   <li>Duke</li>
+   <li>Villanova</li>
+</ul>
+

위와 같이 요소를 맨 위에 추가했을 경우 문제가 된다. 첫번째 예시처럼 비교를 했을때, 세 <li> 모두 다르기에, 리액트는 세 자식을 모두 리렌더링 시킨다.

Keys

Recursing on children 에서 봤던 문제를 해결하기 위해 리액트는 key 속성을 지원한다. 자식들이 key를 가지고 있다면, 리액트는 key를 통해 기존 트리와 이후 트리의 자식들이 일치하는지 확인한다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+
<ul>
+   <li key="2015">Duke</li>
+   <li key="2016">Villanova</li>
+</ul>
+
+<ul>
+   <li key="2014">Connecticut</li>
+   <li key="2015">Duke</li>
+   <li key="2016">Villanova</li>
+</ul>
+

이렇게 키를 추가한다면, 리액트는 키가 같은 자식은 이전과 같은 자식이라고 인지하고 리렌더링하지 않는다. 오직 키가 변경된 요소만 리렌더링 하여 매우 효율적이 된다.

주의점

key는 형제 사이에서 유일해야한다. 즉, 같은 리스트 안에서 key는 유일해야 한다는 말이다.

key를 추가하라는 안내문이 뜬다는 이유로 나를 포함한 많은 이들이 다음과 같은 실수를 저지른다.

1
+2
+3
+
{state.list.map((todo, index) => (
+    <ToDo key={index} {...todo} />
+))}
+

이처럼 인덱스를 key로 사용하면, 만약 새로운 요소를 리스트의 맨 앞에 추가했을 경우 리스트의 key는 새로 추가된 요소부터 0 ~ n 의 값을 가진다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+
<ul>
+   <li key="0">Duke</li>
+   <li key="1">Villanova</li>
+</ul>
+
+<ul>
+   <li key="0">Connecticut</li>
+   <li key="1">Duke</li>
+   <li key="2">Villanova</li>
+</ul>
+

리액트는 key를 통해 리렌더 해야 할 요소를 뽑아낸다. 그런데 이 예시에서는 새로 맨 앞에 추가된 요소가 이전 리스트에 존재했던 key="0"을 갖는다. 이로 인해 리액트는 새로 추가된 요소를 리렌더링 하지 않고, 오히려 기존에 있던 맨 마지막 요소를 리렌더링 한다.(기존 리스트에선 2라는 key는 없었기에)

이와 같이 각 index를 key로 사용하면 의도하지 않는 방식으로 동작할 수 있다. 예시 예시 해결

리스트에 key를 지정해주지 않으면 리액트에서는 기본적으로 index를 key로 사용한다. 따라서 특별한 경우가 아니라면 key는 유일한 값으로 지정해주자.

특별한 경우

  1. 리스트가 변화되지 않을 경우
  2. 성능에 신경쓸만큼 긴 리스트가 아닌데, 식별자를 생성하기 어려운 경우
This post is licensed under CC BY 4.0 by the author.

JS 코테 대비 메소드 정리

클래스형 컴포넌트 vs 함수형 컴포넌트

Comments powered by Disqus.

diff --git "a/posts/react-native-\352\263\265\353\266\200/index.html" "b/posts/react-native-\352\263\265\353\266\200/index.html" new file mode 100644 index 000000000..297400067 --- /dev/null +++ "b/posts/react-native-\352\263\265\353\266\200/index.html" @@ -0,0 +1 @@ + React native 기초 | 디피의 개발일지
Posts React native 기초
Post
Cancel

React native 기초

개발환경 만들기

  • 필요한 것 : 안드로이드 스튜디오, expo, 안드로이드 emulator
  1. npm install -g expo-cli

  2. 안드로이드 스튜디오 설치 -> 설치 후 sdk도 설치

  3. 안드로이드 스튜디오을 키고, configuration에 들어가서 AVD manager을 누르고, create virtual device를 눌러 가상머신 만들기

프로젝트 시작

  1. expo init [프로젝트명]
  2. expo start 하면 실행됨
  3. 아까 만든 가상머신을 실행시키고, run on android device를 누름
  4. 가상머신 안에 있는 expo를 실행시키고, 주소를 카피하면 실행됨.
  5. crtl + m 으로 개발자메뉴 들어갈 수 있음

react-native 작동방식

  • 자바스크립트로, ios 또는 android 안에 있는 자바스크립트 엔진을 통해 실행됨
  • 이때 브릿지를 통해 명령을 전달하는데, react-native 컴포넌트들이 브릿지들이다.
  • 이 브릿지로 많은 데이터를 보내게 되면 부하가 걸리게 됨. 그래서 인스타그램 같은 앱을 만들기엔 적합하지만, 3d video 게임 같은 것은 부적합하다.
  • CSS
    • StyleSheet.create() 를 사용하여 css를 적용할 수 있음.
    • 하지만 웹에서처럼 작동하지는 않음 ex) 부모의 color를 받아오지 않음 등등

React native layout

  • 리액트네이티브의 flexDirection의 디폴트는 colum
  • flex: 1- 모든공간이 사용가능함을 의미
    • 자식들의 flex를 조절하면서 각 자식들이 차지할 공간을 조절하면 됨.

개발

  • 리액트 개발하는 방식대로 하면 된다.
  • css는 StyleSheet 사용해서 만들고, 기능들은 expo 페이지에 들어가서 라이브러리로 설치하면 됨.
  • gradient
    • 웹에서는 쉽게 만들 수 있지만, react-native에서는 다르게 해줘야함.
    • ex ) expo install expo-linear-gradient 같은 라이브러리를 설치하여 함.
This post is licensed under CC BY 4.0 by the author.
diff --git a/posts/react-query-cache/index.html b/posts/react-query-cache/index.html new file mode 100644 index 000000000..b8a1976d9 --- /dev/null +++ b/posts/react-query-cache/index.html @@ -0,0 +1,43 @@ + React-query 에서 cache를 사용하는 방법 | 디피의 개발일지
Posts React-query 에서 cache를 사용하는 방법
Post
Cancel

React-query 에서 cache를 사용하는 방법

useQuery 설정

React-Query 공식문서상 캐싱개념은 stale과 cachetime을 통해 이루어진다

useQuery의 옵션으로 staletimecachetime을 보낼 수 있다.

  • staletime : fetch를 통해 전달받은 데이터는 리액트 쿼리의 자료구조 내용중 캐시에 저장되는데, 이 캐시데이터의 ‘신선한 상태’가 언제까지 될지를 말해주는 옵션
    • default = 0, 받아오는 즉시 stale하다고 판단하여 캐싱데이터와 무관하게 계속해서 fetching을 날림
  • cachetime : 캐시 구조에 저잘된 데이터는 메모리상에 존재하게 된다. 이때 메모리에 저장되어있는 캐시데이터가 언제까지 유지될지 말해주는 옵션.


refetch 함수

  • 캐싱결과는 조회하지 않고, ajax 요청을 날리는 메서드


refetch 되는 조건

  1. 새로운 query instance가 마운트 될때
  2. 브라우저 화면을 이탈했다가 다시 focus할때
  3. 네트워크가 다시 연결될 때
  4. 특별히 설정한 refetch interval에 의한 경우

이때 stale 상태일때만 refetch 된다. 따라서 staletime으로 stale 상태를 관리해주고, cachetime으로 메모리에 남아있을 시간을 지정해준다.


QueryClient

캐싱과 관련된 내용을 담고있는 객체. useQueryClient 로 접근 가능하다.

사용하기 위해선 먼저 다음과 같이 <QueryClientProvider> 로 하위 컴포넌트를 감싸주어야한다. (React Query 자체를 사용하기 위해서도 감싸주어야한다)

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+
const queryClient = new QueryClient({
+    defaultOptions : {
+        queries : {
+            refetchOnWindowFocus:false,
+            refetchOnMount:false
+        }
+    }
+})
+
+return (
+	<QueryClientProvider client={queryClient}>
+    	<Main>
+        	...
+        </Main>
+    </QueryClientProvider>
+)
+

이후 아래와 같이 useQueryClient로 queryClient 객체를 가져오면 다음과 같이 queriesMap 형태로 캐시된 데이터를 확인할 수 있다.

1
+2
+
const queryClient = useQueryClient();
+console.log(queryClient);
+

img

사진에서 보이듯이 queriesMap에서는 useQuery에서 사용한 키값으로 캐싱되어있음을 알 수 있다. useQuery를 사용할때, staletimecachetime을 지정해주면, 그 설정값에 맞추어 키값에 맞는 캐싱된 데이터를 자동으로 가져온다. 이때 요청은 보내지 않는다.

staletime이 0이라면, 항상 데이터가 stale하다고 여겨지기에 항상 refetching을 진행한다.


캐싱 구현법

enabled:false

useQuery의 옵션으로 enabled:false를 설정해놓으면, 초기 마운트시에 해당 useQuery가 마치 useEffect처럼 첫 마운트시 fetcher 함수 호출을 하고, 실패했을 때는 계속 retry하는 행위를 사전 차단할 수 있다.

그러나 enabled:false는 useQuery 기능을 사용하지 않겠다고 말해주는 것과 같기 때문에, 수동적으로 호출해줘야한다. 그것이 바로 useQuery 함수 호출을 하여 리턴되는 객체에 포함되어있는 refetch 함수다.

refetch함수는 캐싱 데이터를 조회하지 않고 요청을 날린다.

즉, enabled:false 일 경우 초기 마운트 시에는 데이터를 가져오지 않고 refetch 함수를 호출해도 캐시데이터를 조회하지 않으니, enabled:false 일경우 캐시 사용이 불가능하다.


enabled:true

초기 마운트 때, 정해진 동작을 할 것이고 따라서 캐시 데이터를 조회할 것이다. 저장된 캐시데이터가 stale하다면 캐시를 가져오고, 아니면 재요청을 할 것이다.


참고

useQuery의 옵션에 사용하는 onError, onSuccess, onSettled 등의 ajax 요청 결과로 동작하는 이벤트는 캐싱값을 가져올때는 동작하지 않는다.

따라서 캐싱값을 사용할때는, 해당 데이터의 결과값은 변동된다는 사실을 이용하여 이 데이터를 이용한 조건부렌더링을 사용하여야한다.


enabled 속성 조작

enabled 속성을 조작하는 것으로 요청 동작을 막을 수 있다.

예를 들어 searchValue가 있어야 쿼리를 날리는 것이 의미있는 상황이라고 하자.

그럼 다음과 같이 enabled 옵션을 searchValue가 ‘‘이 아닌 상태일때만 true로 만들고, 그 외에는 false로 하여 초기 요청을 통한 retry로 오류를 생성하는 것을 막는다.

1
+2
+3
+
const {data, refetch, ...rest} = useQuery([page, "search"], ..., {
+                                          enabled : searchValue !== ''
+                                          })
+

그 뒤, 조건부로 enabled가 true로 변경되면서 요청을 날리게 되어 성공하면 data 프로퍼티에 그 값이 저장되고, 캐싱에도 저장될 것이다.


출처

https://velog.io/@chltjdrhd777/React-Query-%EC%BA%90%EC%8B%B1%EC%97%90-%EB%8C%80%ED%95%9C-%EA%B5%AC%ED%98%84

This post is licensed under CC BY 4.0 by the author.

DB 페이징 기법

선언형프로그래밍

Comments powered by Disqus.

diff --git a/posts/react-state/index.html b/posts/react-state/index.html new file mode 100644 index 000000000..17c5d0c72 --- /dev/null +++ b/posts/react-state/index.html @@ -0,0 +1,285 @@ + useState 원리 | 디피의 개발일지
Posts useState 원리
Post
Cancel

useState 원리

State 변경 시 어떤 일이 벌어질까?

리액트의 함수형 컴포넌트는 최초에 한번 실행이 되면서 초기값으로 설정해놓은 상태를 기억한다.

1
+
const [state, setState] = useState(0);
+

이후 setState가 호출되어 상태가 변경된다면 다시 함수형 컴포넌트가 실행되고 virtual DOM을 리턴한다. 그리고 이전에 리턴했던 virtual DOM과 비교해서 state 값이 달라졌다면 달라진 부분에 해당하는 DOM만 업데이트한다.

useState와 Closure

클래스형 컴포넌트는 render() 메서드를 통해 상태 변경을 감지할 수 있다. 반면 함수형 컴포넌트는 렌더링이 발생하면 함수 자체가 다시 호출된다. 그래서 상태 관리를 위해선 함수가 다시 호출되었을 때 이전 상태를 기억하고 있어야한다.

useState는 Closure를 통해 이 문제를 해결한다.

Closure는 내부함수에서 상위 함수 스코프의 변수에 접근할 수 있는 개념

다음은 클로저의 특징을 이용한 MyReact 모듈이다

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+
const MyReact = (function() {
+  let _val // hold our state in module scope
+  return {
+    render(Component) {
+      const Comp = Component()
+      Comp.render()
+      return Comp
+    },
+    useState(initialValue) {
+      _val = _val || initialValue
+      function setState(newVal) {
+        _val = newVal
+      }
+      return [_val, setState]
+    }
+  }
+})()
+
  • MyReact는 익명함수로부터 두개의 Closure를 반환받아 저장한다.
  • _val은 익명함수 scope 안에서 정의된다. 하지만 반환되는 함수의 Closure에서 사용되기에 익명함수가 종료되더라도 메모리에 유지된다.

MyReact 함수는 다음과 같이 사용할 수 있다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
function Counter() {
+  const [count, setCount] = MyReact.useState(0)
+  return {
+    click: () => setCount(count + 1),
+    render: () => console.log('render:', { count })
+  }
+}
+let App
+App = MyReact.render(Counter) // render: { count: 0 }
+App.click()
+App = MyReact.render(Counter) // render: { count: 1 }
+

Counter가 일종의 컴포넌트라고 생각해보자.

  • MyReact의 render를 이용해 첫 Counter를 렌더링한다.
  • 렌더링하면 MyReact의 useState가 실행된다. _val에 아무것도 없기에 초기값으로 설정된다.
  • useState는 반환값으로 상태와 setter를 낸다. 그 후 Counter는 clickrender가 들어있는 객체를 반환해 App에 저장한다.
  • App.click을 통해 상태를 업데이트한다. 이러면 리렌더링이 되는데, 현재 MyReact에는 이러한 로직이 구현되어있지 않다. 실제 React에서는 setter가 실행될 경우 컴포넌트를 리렌더링한다. 여기서는 click 이후 render를 실행함으로써 리렌더링을 보여준다.
  • Counter가 다시 실행되는데 이번엔 메모리에 저장된 _val에 값이 있기에 초기값을 설정하는 대신 업데이트된 값을 계속 유지한다.
  • 새롭게 반환된 객체는 count가 1인 상태로 동작한다.

하지만 위와 같은 useState엔 문제점이 있다. 바로 여러 useState를 사용할 경우 다 같은 _val을 바라본다는 점이다. 이를 해결하기 위해선 다음과 같이 할 수 있다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+
let state = [];
+let setters = [];
+let cursor = 0;
+let firstrun = true;
+
+const createSetter = (cursor) => {
+  return (newValue) => {
+    state[cursor] = newValue;
+  };
+};
+
+const useState = (initialValue) => {
+  if (firstrun) {
+    state.push(initialValue);
+    setters.push(createSetter(cursor));
+    firstrun = false;
+  }
+
+  const resState = state[cursor];
+  const resSetter = setters[cursor];
+  cursor++;
+
+  return [resState, resSetter];
+};
+
  • useState는 초깃값을 받는다. 그리고 최초 실행일 경우 초깃값을 state 배열에 삽입한다. 마찬가지로 setter도 추가한다. 이때 state 배열과 setters 배열은 useState 함수 외부에 위치한다.
  • 추가된 위치의 state와 setter를 반환하면, state 값은 useState 함수 외부에 위치하므로 closure가 적용되어 useState가 종료되더라도 값이 유지된다.


Hook 규칙

  1. 함수형 컴포넌트의 최상위에서만 hook을 호출해야한다.
    • state는 컴포넌트의 실행 순서대로 배열에 저장된다. 따라서 반복문, 조건문 혹은 중첩함수에서 hook을 호출하면, 컴포넌트의 실행 순서가 달라질 수 있다.
  2. 오직 React 함수 내에서 hook을 호출해야한다.
    • Hook을 일반적인 JS 함수에서 호출하면 안되고, 함수형 컴포넌트 또는 커스텀 훅 내에서만 호출할 수 있다.


useState 모듈 분석

1
+2
+3
+4
+5
+6
+
export function useState<S>(
+  initialState: (() => S) | S,
+): [S, Dispatch<BasicStateAction<S>>] {
+  const dispatcher = resolveDispatcher();
+  return dispatcher.useState(initialState);
+}
+
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
function resolveDispatcher() {
+  const dispatcher = ReactCurrentDispatcher.current;
+
+  if (__DEV__) {
+    if (dispatcher === null) {
+      console.error('Some error msg...');
+    }
+  }
+
+  return ((dispatcher: any): Dispatcher);
+}
+
1
+2
+3
+4
+5
+6
+7
+
const ReactCurrentDispatcher = {
+  /**
+   * @internal
+   * @type {ReactComponent}
+   */
+  current: (null: null | Dispatcher),
+};
+

ReactCurrentDispatcher은 전역에 선언된 객체의 프로퍼티이다. useState 리턴 값의 출처가 전역에서 온다는 것이다. 리액트가 실제로 클로저를 활용해 함수 외부의 값에 접근하는 사실을 알 수 있다.


useState 배치 프로세스

다음 예제에서 increase1의 결과는 1이고 increase2의 결과는 3이다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+
const Counter = () => {
+  const [count, setCount] = useState(0);
+
+  const increase1 = () => {
+    setCount(count + 1);
+    setCount(count + 1);
+    setCount(count + 1);
+  }
+
+  const increase2 = () => {
+    setCount((count) => count + 1);
+    setCount((count) => count + 1);
+    setCount((count) => count + 1);
+  }
+}
+
+export default Counter;
+

그 이유는 새로운 상태가 바로 이전의 상태를 통해 계산되어야하면 함수를 인자로 넣어야하기 때문인데, 리액트는 퍼포먼스 향상을 위해 특별한 배치 프로세스를 사용하기 때문이다. 여러 setState 업데이트를 한 번에 묶어서 처리한 후 마지막 값을 통해 state를 결정한다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+
{
+  memoizedState: 0, // first hook
+  baseState: 0,
+  queue: { /* ... */ },
+  baseUpdate: null,
+  next: { // second hook
+    memoizedState: false,
+    baseState: false,
+    queue: { /* ... */ },
+    baseUpdate: null,
+    next: { // third hook
+      memoizedState: {
+        tag: 192,
+        create: () => {},
+        destory: undefined,
+        deps: [0, false],
+        next: { /* ... */ }
+      },
+      baseState: null,
+      queue: null,
+      baseUpdate: null,
+      next: null
+    }
+  }
+}
+
  • 실제 hook을 변수에 할당하여 출력했을 때 나타나는 결과
  • next는 연결리스트의 일종으로 한 컴포넌트 안에서 여러번 실행되는 hook들을 연결해주는 역할
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
{
+  memoizedState: 0,
+  baseState: 0,
+  queue: {
+   last: {
+      expirationTime: 1073741823,
+      suspenseConfig: null,
+      action: 1, // setCount를 통해 설정한 값
+      eagerReducer: basicStateReducer(state, action),
+      eagerState: 1, // 상태 업데이트를 마치고 실제 렌더링되는 값
+      next: { /* ... */ },
+      priority: 98
+    },
+    dispatch: dispatchAction.bind(bull, currenctlyRenderingFiber$1, queue),
+    lastRenderedReducer: basicStateReducer(state, action),
+    lastRenderedState: 0,
+  },
+  baseUpdate: null,
+  next: null
+}
+

리액트의 배치 프로세스는 이렇게 묶인 hook들을 한 번에 처리한 뒤 last를 생성한다. 여기서 최종 반환될 eagerState를 계산하는 함수가 Reducer이다.

1
+2
+3
+
function basicStateReducer(state, action) {
+  return typeof action === 'function' ? action(state) : action;
+}
+

이 Reducer에 넘기는 action 타입이 함수일 때 이전 상태를 인자로 받는다. 그래서 기존 상태를 기반으로 새로운 상태를 업데이트할 수 있게 된다.


출처

https://thinkforthink.tistory.com/339 https://seokzin.tistory.com/entry/React-useState%EC%9D%98-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC%EC%99%80-%ED%81%B4%EB%A1%9C%EC%A0%80 https://hengxi.tistory.com/22

This post is licensed under CC BY 4.0 by the author.

typescript 조건부타입(extends)

beforeunload vs pagehide

Comments powered by Disqus.

diff --git a/posts/react-vite/index.html b/posts/react-vite/index.html new file mode 100644 index 000000000..2b706f11e --- /dev/null +++ b/posts/react-vite/index.html @@ -0,0 +1,21 @@ + Vite | 디피의 개발일지
Posts Vite
Post
Cancel

Vite

최근 create-react-app 대신에 vite를 공식으로 사용하자는 얘기가 나오고 있다.

  • https://github.com/reactjs/reactjs.org/pull/5487
  • 번역본

여기서 지적한 문제점들은 다음과 같다

  • SSR/SSG 지원의 부족
    • 빈 페이지 로드 -> 리액트 번들 로드 -> 리액트 실행 -> 필요한 데이터 패칭 순으로 이어지는 워터폴 문제 발생
  • 모든 앱 코드가 하나의 번들로 묶임
    • 상품 페이지를 로드할때, 장바구니 코드를 로드할 필요는 없는데 CRA는 하나로 묶는다.

리액트 팀에서 답변한 (잠재적인) CRA의 전환 방향은 CRA를 런쳐로 전환하고, 내부적으로는 vite를 사용하는 방법이다. 그러면서 몇가지 리액트 프레임워크를 추천하는 방식이다.

글을 읽으면서 CRA에 대한 이해가 부족하다는 점을 느꼈고, vite가 무엇인지에 대한 지식이 부족하다는 것을 느껴 이 포스트를 작성하게 되었다.

이 포스트에서는 vite에 대해 알아보고, CRA와의 차이점에 대해 작성하였다.


Vite

vite는 빠른 모던 웹 프로젝트 개발 경험을 제공하기 위한 빌드 툴이다. 다음 두가지 메이저 파트로 구성되어있다.

  • 개발 서버 : native ES modules를 넘어선 다양한 기능을 제공한다. (ex : 매우 빠른 Hot Module Replacement)
    • Hot Module Replacement
      • 페이지를 새로고침하거나, 어플리케이션 상태를 날리지 않고 업데이트 된 내용을 즉시 반영한다.
      • Vue Single File ComponentsReact Fast Refrash 등 first-party integration을 사용함.
      • create-vite를 통해 프로젝트를 생성하면 이미 설정되어있음.
  • 번들링 시, Rollup 기반의 다양한 빌드 커맨드를 사용할 수 있다.

Vite는 다양한 설정이 가장 적절한 값이 디폴트로 설정되어있지만, Plugin API나 Javascript API를 통해 설정이 가능하다. (타입스크립트도 지원)

Browser Support

디폴트로 설정된 빌드 타켓은 native ES Modules, native ESM dynamic import, import.meta을 지원하는 브라우저다. 하지만, 그 이전 브라우저를 위해 공식으로 @vitejs/plugin-legacy을 지원한다.

vite로 프로젝트 만들기

1
+2
+3
+4
+5
+
npm create vite@latest
+# or
+yarn create vite
+# or
+pnpm create vite
+

위 명령어 입력후 프롬프트 지시에 따라 생성할 수 있음. 기본적으로 다양한 템플릿을 제공하고, 필요한 것이 없다면 커뮤니티 템플릿도 사용가능하다.

커뮤니티 템플릿을 사용할 대는 다음과 같이 커맨드창에 입력한다.

1
+2
+3
+4
+5
+
npx degit user/project my-project
+cd my-project
+
+npm install
+npm run dev
+


create-react-app과의 차이점

1. 개발서버의 성능차이

가장 큰 차이는 개발서버에서 vite가 CRA보다 훨씬 빠르다는 것이다.

과거에는 브라우저에서 ESM(ES Module)을 지원하지 않았다. 따라서 javscript 모듈화를 위해서는 번들링이라는 우회방법을 사용해야했다. 이때 사용된 툴이 Webpack, Rollup, Parcel이다.

하지만 서비스 복잡도에 따라 모듈이 늘어날 수록 개발 서버를 가동하는 것은 매우 오래걸렸다. 따라서 Vite는 브라우저에서 지원하는 ESM 및 네이티브 언어로 작성된 Javascript 도구 등을 이용해 문제를 해결하였다.

서버구동 최적화

캐시가 없는 상태에서 실행하는 콜드-스타트 방식에서 번들리 기반의 도구는, 애플리케이션 내 모든 소스코드를 확인하고 번들링한다. 하지만, Vite는 다음 두가지로 이 문제를 해결하였다.

  • dependencies : 개발 시 내용이 바뀌지 않을 일반적인 javascript 소스코드. vite의 pre-bundling 기능은 Esbuild를 사용하여 webpack, parcel 과 같은 기존 번들러 대비 10~100배 빠른 번들링 속도를 보인다.
  • source code : 컴파일 과정이 필요하고, 수정이 잦은 코드들. vite는 Native ESM을 이용해 소스코드를 제공한다. 다시 말해 브라우저가 번들러이고, vite는 그저 브라우저의 판단 아래 특정 모듈에 대한 소스코드를 요청하면 이를 전달할 뿐이다. 따라서 조건부 동적 import 이후의 코드는 현재 화면에서 실제로 사용이 되어야만 처리가 된다.

느렸던 소스코드 갱신

기존 번들러 기반에서는 소스코드 업데이트시 번들링 과정을 다시 겪어야했다. 따라서 서비스가 커질수록 소스코드 갱신 시간 또한 증가했다. HMR(Hot Module Replacement)라는 대안이 나왔지만 명확한 해답은 아니었다.

vite는 HMR을 지원하지만, 번들러가 아닌 ESM을 이용해서 제공한다. 어떤 모듈이 수정되는 vite는 그저 수정된 모듈과 관련된 부분만을 교체하고 브라우저에서 해당 모듈을 요청 시 교체된 모듈을 전달할 뿐이다. 따라서 앱 사이즈가 커져도 HMR을 포함한 갱신 시간에는 영향을 끼치지 않는다.

vite는 또한 HTTP 헤더를 이용해 퍼포먼스를 높였다. 소스코드는 304 Not Modified로, 디펜던시는 Cache-Control: max-age=31536000,immutable을 이용해 캐시되도록 함으로써 한번이라도 요청을 덜보내도록 했다.


2. 번들러의 차이

create-react-app으로 만들어진 프로젝트는 webpack을 번들러로 사용한다. webpack은 각 모듈을 함수로 감싸 평가한다. 그렇

하지만, vite는 빌드시 Rollup이라는 번들러를 사용한다. Rollup에서는 코드들을 동일한 수준으로 호이스팅하고, 한번에 번들링을 진행하기에 webpack보다 빠르고, 번들링된 결과물도 훨씬 가볍다. 또한 빌드 결과물도 ES6모듈 형태로 출력할 수 있어서 라이브러리나 패키지에 활용할 수 있다.


3. index.html의 위치

vite로 만들어진 리액트 프로젝트는 index.html의 위치가 create-react-app과 달리 프로젝트 루트에 위치한다. 이는 추가적인 번들링 없이 index.html 파일이 앱의 진입점이 되게끔 하기 위함이다.

vite는 index.html을 JS 모듈 그래프를 구성하는 하나로 취급한다. 따라서, <script type="module" src="..."> 태그를 이용해 JavaScript 소스 코드를 가져올 수 있고, 인라인으로 작성된 <script type="module">이나 <link href>와 같은 CSS 역시 Vite에서 취급 가능하다. 또한 create-react-app과는 달리, index.html 내에서 url을 표시할 때 %PUBLIC_URL%과 같은 placeholder없이 사용할 수 있도록 URL 베이스를 자동으로 맞춰준다.

vite는 또한 정적 HTTP 서버와 비슷하게 루트 디렉터리라는 개념을 가지고 있다. <root>라고 언급된 개념이 있는데, 절대경로가 프로젝트 루트를 베이스로 연결된다는 것을 말한다. vite는 또한 프로젝트 루트 밖에 있는 의존성을 가져올 수 있는데, 이로써 모노리포 구성도 가능해진다.

또한 여러 .html 파일을 두어 multi-page apps 를 구현할 수도 있다.


CRA vs Vite 무엇을 써야할까?

CRA는 vite에 비해 오랜기간동안 사용되어왔고 그만큼 이슈가 많이 픽스되어왔다. 그리고 사용자수또한 CRA의 기반인 webpack이 vite의 기반인 rollup, esbuild보다 훨씬 많다. 그만큼 커뮤니티가 활성화가 되어있고, 개발중 맞닥뜨릴 이슈에 대한 해결책도 많이 제시될 것이다.

따라서 개인적으로는 기존에 잘 돌아가고 있는 프로젝트라면 현재 개발 서버에 큰 문제가 없을시엔 CRA를 유지하는 것이 좋아보인다. 또한 신규 프로젝트라하더라도 안정성이 중요하다면 CRA를 유지하는 것이 좋을 것같다.

반면 Vite는 기존 프로젝트 중에서 개발 서버의 성능문제가 심각한 경우 또는 신규 프로젝트 중에서 안정성이 상대적으로 덜 중요할 경우 Vite를 사용하는 것이 좋아보인다. (결과적으로 CRA도 vite를 내부적으로 사용하는 것을 생각하고 있다고 하니, vite를 사용하는 것이 트렌드이긴 한가보다.)


출처

  • https://vitejs-kr.github.io/guide/#index-html-and-project-root
  • https://junghan92.medium.com/%EB%B2%88%EC%97%AD-create-react-app-%EA%B6%8C%EC%9E%A5%EC%9D%84-vite%EB%A1%9C-%EB%8C%80%EC%B2%B4-pr-%EB%8C%80%ED%95%9C-dan-abramov%EC%9D%98-%EB%8B%B5%EB%B3%80-3050b5678ac8
  • https://yoon-dumbo.tistory.com/entry/%EB%A1%A4%EC%97%85%EA%B3%BC-%EC%9B%B9%ED%8C%A9%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90-rollup-vs-webpack
This post is licensed under CC BY 4.0 by the author.

Headless CMS

성능 최적화를 위한 next.js case study

Comments powered by Disqus.

diff --git "a/posts/react-\353\222\244\353\241\234\352\260\200\352\270\260-\353\247\211\352\270\260/index.html" "b/posts/react-\353\222\244\353\241\234\352\260\200\352\270\260-\353\247\211\352\270\260/index.html" new file mode 100644 index 000000000..dc6141c1b --- /dev/null +++ "b/posts/react-\353\222\244\353\241\234\352\260\200\352\270\260-\353\247\211\352\270\260/index.html" @@ -0,0 +1,193 @@ + React 새로고침/뒤로가기 막기 | 디피의 개발일지
Posts React 새로고침/뒤로가기 막기
Post
Cancel

React 새로고침/뒤로가기 막기

웹페이지에서 사용자가 무언가를 입력하고 뒤로가기를 눌렀을때 다음과 같은 경고창을 띄우는 걸 본적이 있다.

HTML DOM beforeunload 이벤트 - 제타위키

이러한 스펙을 개발하기위해서는 다음과 같이 두가지 상황으로 나누어야한다.

  • 새로고침/창닫기/링크이동(외부페이지 이동 or 링크 버튼 클릭)
  • 뒤로가기

그렇다면 리액트에서는 구체적으로 어떻게 이 스펙을 구현할 수 있을지 알아보자


새로고침/창닫기/링크이동

새로고침/창닫기/링크이동의 경우는 간단하다. 해당 이벤트들이 발생할때 window 객체에서 발생시키는 beforeunload 이벤트를 취소시키면 된다. 리액트에서 구현하려면 다음과 같이 useEffect 안에 window.onbeforeunload 함수를 등록해주면 된다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+
useEffect(() => {
+    window.onbeforeunload = (e) => {
+        e.preventDefault();
+        return ""
+    }
+
+    return () => {
+        window.onbeforeunload = null
+    }
+}, [])
+

하지만, 이 경우에는 페이지에 접속했을 경우 무조건 이벤트가 등록이 된다. 사용자가 입력했을때만 경고창을 띄우고 싶다면 다음과 같이 할 수 있다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+
const [hasInputFilled, setHasInputFilled] = useState(false);
+
+useEffect(() => {
+    window.onbeforeunload = () => {
+        if (hasInputFilled) {
+            return ""
+        }
+    }
+
+    return () => {
+        window.onbeforeunload = null
+    }
+}, [hasInputFilled])
+


뒤로가기 막기

뒤로가기 이벤트는 popstate 이벤트가 발생한다. 하지만 popstate 이벤트는 취소가 불가능하다. 따라서 취소를 하지 않고 뒤로가기를 막아야하는데, 방법을 말하기 전에 먼저 popstate 이벤트에 대해 간략히 알아보자.

우리가 브라우저에서 여러 웹 페이지를 오가다보면 방문기록이 쌓이게 된다. 이때 하나의 탭에서 여러 웹 페이지를 방문하면 그 기록이 차례로 쌓이고, 뒤로가기 시에는 해당 탭에서 마지막으로 방문했던 곳으로 이동하게 된다. 마치 스택처럼 동작한다. popstate는 뒤로가기 시 마지막으로 방문했던 페이지로 이동하기 위해 발생된다.

여기서 뒤로가기를 막기 위한 한가지 아이디어를 찾을 수 있다. 만약 마지막으로 방문했던 페이지가 현재 페이지면, 뒤로가기를 하더라도 현재 페이지에 그대로 남아있을 것이라는 아이디어다. 이를 위해서는 history.pushState()를 사용하면 된다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+
useEffect(() => {
+    const preventGoBack = () => {
+        history.pushState(null, "", location.href);
+    };
+  
+    history.pushState(null, "", location.href);
+    window.addEventListener("popstate", preventGoBack);
+
+    return () => {
+        window.removeEventListener("popstate", preventGoBack);
+    }
+}, [])
+
  1. 마운트 시,history.pushState()로 현재 페이지를 스택 마지막에 추가
  2. popstate 이벤트 콜백 등록
  3. popstate 이벤트 발생시, 스택이 하나 줄어듦. but 콜백 함수에서 다시 스택을 추가함

하지만 이와같은 방법에는 단점이 있는데, 사용자가 아무리 뒤로가기를 눌러도 뒤로가기가 동작되지 않는 것처럼 보이게 만든다는 점이다. 이를 해소하기 위해 다음과 같이 콜백함수를 수정할 수 있다.

1
+2
+3
+4
+5
+6
+7
+
const preventGoBack = () => {
+    if(confirm("정말 뒤로 가시겠습니까?")) {
+        history.back();
+    } else {
+        history.pushState(null, "", location.href);
+    }
+};
+

스택을 하나 추가하기 전에 사용자에게 경고창을 띄우고, 확인을 눌렀을 경우에는 한번 더 뒤로가기를 시도해 이전 페이지로 이동하는 방법이다.

여기서 새로고침/창닫기/링크이동에서 했던 것처럼 사용자가 입력을 했을때만 뒤로가기를 막도록 하면 다음과 같다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+
const [hasInputFilled, setHasInputFilled] = useState(false);
+
+useEffect(() => {
+    const preventGoBack = () => {
+        if(!hasInputFilled) {
+          return;
+        }
+      
+        if(confirm("정말 뒤로 가시겠습니까?")) {
+            history.back();
+        } else {
+            history.pushState(null, "", location.href);
+        }
+    };
+  
+    if(hasInputFilled) {
+        history.pushState(null, "", location.href);
+    }
+    window.addEventListener("popstate", preventGoBack);
+
+    return () => {
+        window.removeEventListener("popstate", preventGoBack);
+    }
+}, [hasInputFilled])
+

여기서 스택에 넣어진 현재 페이지의 길이를 저장하여 조금 더 안정적으로 동작하도록 할 수 있다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+
const [hasInputFilled, setHasInputFilled] = useState(false);
+const [pushedLength, setPushedLength] = useState(0)
+
+useEffect(() => {
+    const preventGoBack = () => {
+        if(pushedLength === 0) {
+          return;
+        }
+      
+        if(confirm("정말 뒤로 가시겠습니까?")) {
+            history.back();
+        } else {
+            history.pushState(null, "", location.href);
+        }
+    };
+  
+    if(hasInputFilled && pushedLength === 0) {
+        history.pushState(null, "", location.href);
+        setPushedLength(1)
+    } else if(!hasInputFilled && pushedLength === 1) {
+        history.back();
+        setPushedLength(0);
+    }
+  
+    window.addEventListener("popstate", preventGoBack);
+
+    return () => {
+        window.removeEventListener("popstate", preventGoBack);
+    }
+}, [hasInputFilled, pushedLength])
+
This post is licensed under CC BY 4.0 by the author.

IOS 16.5에서 video가 크기 변경이 적용되지 않는 이슈

next.js props drilling

Comments powered by Disqus.

diff --git "a/posts/react-\353\241\234\353\224\251-\354\262\230\353\246\254/index.html" "b/posts/react-\353\241\234\353\224\251-\354\262\230\353\246\254/index.html" new file mode 100644 index 000000000..8b59b3aa9 --- /dev/null +++ "b/posts/react-\353\241\234\353\224\251-\354\262\230\353\246\254/index.html" @@ -0,0 +1,147 @@ + React) JS 다운로드 시간 동안의 로딩 화면 | 디피의 개발일지
Posts React) JS 다운로드 시간 동안의 로딩 화면
Post
Cancel

React) JS 다운로드 시간 동안의 로딩 화면

최근 프로젝트에서 리액트 앱을 배포하였는데, 인터넷 속도가 느린 환경에서 자바스크립트를 다운로드 받는 시간이 오래 걸린다는 걸 눈치챘다.

눈치챘다는 표현이 어울리는데 왜냐하면 사실 당연한 건데 이제까지 신경쓰지 않았던 부분이기 때문이다. 리액트앱은 매우 큰 자바스크립트 파일 하나로 이루어져있고, 이를 다운로드 받는 것은 당연히 오래 걸리는 일이기 때문이다.

따라서 이 시간 동안 단순한 로딩 화면이라도 띄울수있다면 사용자 경험을 향상 시킬 수 있을 것이라 생각되어 관련된 공부를 하게 되었다.

리액트 앱이 띄워지기까지

사용자가 우리의 앱에 접근하고, 리액트가 사용자 브라우저에 띄워지기까지 다음과 같은 과정을 거친다.

  1. 사용자는 주소창으로 우리의 앱에 접근한다.

  2. 서버는 index.html을 사용자에게 보낸다.
  3. 사용자의 브라우저는 index.html을 읽고, 필요한 파일들을 파악한 후, 서버로 요청한다.
  4. 서버는 필요한 파일들을 보낸다. 이때 리액트 번들 파일도 보낸다.
  5. 다운로드 된 번들 파일을 실행하여 리액트 앱을 띄운다.

이때 우리가 로딩 처리해줄 수 있는 순간은 바로 4번뿐이다. 1 ~ 3번은 우리가 처리할 수 없고, 네트워크과 브라우저에게 맡겨야하는 부분이기 때문이다. 5번에서는 각종 초기화 작업들이 이루어 질텐데, 이미 리액트 앱은 실행된 상태이므로 앱 안에서 로딩처리하면 될 일이다.

자, 그럼 4번을 어떻게 처리해야할까? 이 주제에 관해 얘기하기 전에 먼저 정말로 처리해줘야하는지 알아보자.

다음은 크롬 개발자 도구를 사용하여 보급형 휴대전화에서 필자의 프로젝트의 다운로드 속도를 측정한 것이다.

image-20220203032433794

  1. yourlist.me (2.16초) : 서버로 접근하고, 응답을 받기까지 걸린 시간
  2. www.yourlist.me(2.15초) : index.html을 요청하고 받는데까지 걸린 시간
  3. main.~~~.js(10.47초) : 리액트 js 번들파일

위 결과를 보면 알겠지만, 보급형 휴대전화를 사용하는 사용자는 리액트 앱이 실행되기까지 15초 가량이 걸리게 된다. 만약 그 시간동안 흰화면만 띄워지고 아무런 인터랙션도 없다면, 필자라면 그냥 그 사이트를 나갈 것이다. 이는 유저이탈을 불러일으켜 서비스 운영에 악영향을 끼치게 된다. 따라서 우리는 시간동안 무엇이든 띄워야한다.

JS 다운로드 시간동안 로딩 처리 방법

방법은 간단하다.

먼저 리액트 프로젝트를 만들면 생성되는 public/index.html 파일의 초기 상태는 다음과 같다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+
<!DOCTYPE html>
+<html lang="ko">
+    <meta charset="utf-8" />
+    <link rel="icon" type="image/png" href="%PUBLIC_URL%/favicon.png" />
+    <meta name="viewport" content="width=device-width, initial-scale=1" />
+    <meta name="theme-color" content="#000000" />
+    <meta
+      name="description"
+      content="Web site created using create-react-app"
+    />
+    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
+    <title>React App</title>
+
+    </head>
+  <body>
+    <div id="root"></div>
+  </body>
+</html>
+

여기서 리액트 앱은 body 안의 “root”라는 id를 가지는 div 안에서 실행되니, 우리는 그 밖에서 무언가를 작업하면 js 파일이 다운로드 받기 전에도 무언가를 띄울 수 있다는 것을 짐작할 수 있다.

따라서 필자는 다음과 같이 index.html을 작성하여 로딩 엘리먼트를 띄웠다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+
<!DOCTYPE html>
+<html lang="ko">
+    <meta charset="utf-8" />
+    <link rel="icon" type="image/png" href="%PUBLIC_URL%/favicon.png" />
+    <meta name="viewport" content="width=device-width, initial-scale=1" />
+    <meta name="theme-color" content="#000000" />
+     <meta
+      name="description"
+      content="Web site created using create-react-app"
+    />
+    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
+    <title>React App</title>
+
+    <!--
+      index.html에 적용할 css 파일 import
+    -->
+    <link rel="stylesheet" type="text/css" href="%PUBLIC_URL%/index.css" />
+    <style>
+      /* css 파일을 연동하는 시간을 아끼기 위한 내부 스타일시트 선언*/
+      #loadingElement {
+        animation: 1s linear infinite rotateLoadingElement;
+      }
+      @keyframes rotateLoadingElement {
+        from {
+          transform:translate(-50%,-50%) rotate(0deg);
+        }
+        to {
+          transform:translate(-50%,-50%) rotate(360deg);
+        }
+      }
+    </style>
+    </head>
+  <body>
+    <div id="root"></div>
+
+    <!--
+      번들을 다운로드 받는 동안, 아래 loading화면을 띄움
+      css 파일을 연동하는 시간도 아끼기 위해 inline style도 사용함
+      번들 다운로드 후, 내부 api 작업을 위한 로딩은 #root 안에서 해결
+    -->
+    <div id="loading">
+        <img id="loadingElement" src="%PUBLIC_URL%/loading.png" style="position:absolute; width:100px; top:50%; left:50%;"/>
+    </div>
+  </body>
+</html>
+
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+
/* index.css */
+#root:not(:empty) ~ #loading {
+  display: none;
+}
+
+#loading {
+  background-color: #f9f9f9;
+  width: 100vw;
+  height: 100vh;
+}
+
  1. root 엘리먼트 아래에 로딩 엘리먼트를 추가하여 번들 다운로드 중에도 화면에 로딩 엘리먼트를 띄웠다.
  2. css를 사용하여, root가 비어있을때만 로딩 엘리먼트를 띄우도록 했다. 따라서 리액트 앱이 실행되면 자동으로 로딩 엘리먼트는 사라지게 된다.
  3. 인라인 스타일과 인터널 스타일시트를 사용하여, css 파일을 다운로드 받는 시간조차 아꼈다.

그 결과가 다음과 같다.

adasd

index.html이 다운로드 받아지고, js 번들파일이 다운로드 되기 전까지 로딩 엘리먼트가 제대로 띄워짐을 알 수 있다. (이후 다른 로딩엘리먼트가 띄워지는데, 이는 리액트 앱 내부에서 렌더되는 것이다.)

이로써 유저는 번들 파일이 다운받아지는 시간동안 최소한 작업이 진행되고 있다는 것을 알 수 있어 조금이라도 더 기다리게 할 수 있다.

여기에 단순 로딩엘리먼트만이 아닌, 작업 안내 페이지를 띄운다면 더욱 효과가 클 것이다.

This post is licensed under CC BY 4.0 by the author.

2631 줄세우기

데이터베이스 개요

Comments powered by Disqus.

diff --git "a/posts/react-\354\236\221\353\217\231\354\233\220\353\246\254\353\266\200\355\204\260-tailwindcss-\354\202\254\354\232\251\352\271\214\354\247\200/index.html" "b/posts/react-\354\236\221\353\217\231\354\233\220\353\246\254\353\266\200\355\204\260-tailwindcss-\354\202\254\354\232\251\352\271\214\354\247\200/index.html" new file mode 100644 index 000000000..668652d85 --- /dev/null +++ "b/posts/react-\354\236\221\353\217\231\354\233\220\353\246\254\353\266\200\355\204\260-tailwindcss-\354\202\254\354\232\251\352\271\214\354\247\200/index.html" @@ -0,0 +1,15 @@ + react 작동원리부터 tailwindcss 사용까지 | 디피의 개발일지
Posts react 작동원리부터 tailwindcss 사용까지
Post
Cancel

react 작동원리부터 tailwindcss 사용까지

개요

  • yourlist 웹 리뉴얼 프로젝트에서 tailwind css를 사용하기로 결정하였고, create-react-app을 사용하여 리액트앱을 만든 후, tailwind css를 추가하였다.
  • 이때 tailwind css는 먼저 css 파일을 컴파일 하고, 컴파일 이후의 css 파일을 import 하는 방식으로 동작한다.
  • 따라서 매번 리액트 앱을 실행하기 전에 새로 컴파일을 해줘야하는데, create-react-app에 tailwindcss 추가하는 가이드를 따라서 하면, 런타임 중에 세이브만으로도 서버를 껐다가 킬 필요없이(hot-reload) tailwind css 가 적용된다.
  • 어떤 원리로 위처럼 동작하는지 잘 이해가 되지 않아, react 작동원리부터 차근차근 살펴보기로 하였다.

Webpack

  • 모듈 번들러.
  • 개발을 할땐 편의를 위해 하나의 프로그램을 여러 모듈로 나누어 개발한다. 하지만, 브라우저에서 이러한 모듈 시스템은 네트워크의 낭비가 된다. 따라서 여러개의 모듈을 하나의 파일로 묶어서 보낼 모듈 번들러가 필요하고, 그것이 웹팩이다.
  • 웹팩에서는 자바스크립트, 스타일시트, 이미지 등 모든 것을 모듈로 봄.
  • 웹팩의 중요한 속성
    1. Entry : 웹팩에서 웹 자원을 변환하기 위해 필요한 최초 진입점이자 자바스크립트 파일 경로.
      • 웹팩은 entry를 통해 모듈을 로딩하고, 하나의 파일로 묶는다.
    2. Output : 웹팩에서 entry로 찾은 모듈을 하나로 묶은 결과물을 반환할 위치
    3. loader : 웹팩은 자바스크립트와 JSON만 빌드할 수 있는데, HTML, CSS, IMAGE 를 빌드할 수 있도록 도와주는 속성
    4. Plugin : 웹팩의 기본적 동작 외 추가적인 기능을 제공하는 속성
  • 개발 서버를 로컬호스트로 열어주는 역할도 한다.

react-scripts start

  • cra 으로 만들어진 리액트 앱은 웹팩, 바벨 설정을 감추고 react-scripts start 만으로 앱을 브라우저에 띄워주며, hot reloading 기능을 제공해준다.
  • 과연 react-script start를 실행하면 어떤일이 벌어지는 걸까?

과정

  1. 먼저 Webpack 이 실행되어 src/index.js 부터 살펴본다. 그리고 import 된 모듈을 따라가며 dependency graph를 전부다 살펴볼때까지 진행한다.

  2. 이후 모든 자바스크립트 파일들을 Babel로 넘겨주어 ES5+ 로 문법을 번역한다.

  3. 웹팩은 모든 자바스크립트 파일을 하나의 파일로 만들고, 서버를 실행한다.

  4. 이후 코드가 변경되고 저장이 되면, webpack에 연결된 hot-reload 모듈을 통해 다시 1번부터 과정을 반복한다.

postCSS

  • 위 과정까지 보았다면, 리액트 앱 또한 매번 컴파일 과정을 다시 반복하여 다시 서버를 여는 것을 알 수있다.
  • 또 개요에서, tailwindCSS를 사용하기 위해선 앱이 실행되기 전에 먼저 CSS 파일을 컴파일 해야한다는 것을 말했다.
  • 그럼 tailwindCSS를 사용하면서 기존에 리액트를 개발하던 것과 같이 hot raload를 사용하려면, react-scripts start 과정의 1번을 시작하기 전에 tailwindCSS를 자동으로 컴파일 하면 된다는 것을 알 수 있다.
  • 과연 어떻게하면 될까? 여기서 postCSS가 등장한다.

postCSS

  • JS 플러그인을 사용하여 CSS를 변환시키는 CSS postprocessor이다. CSS의 babel과 같은 것이다.

  • postprocessor이므로, preprocessor인 SASS와 다르게, CSS를 processing하여 플러그인을 거쳐 새로운 css로 변환해준다.

    1
    +
    CSS -> Parser -> Plugin -> Stringfier -> New CSS
    +
    • 단, postCSS 자체는 아무런 일을 하지 않고, 플러그인이 모든 변환작업을 한다.

postcss.config.js

  • 개요에 소개한 가이드를 따라가다보면, 프로젝트에 postcss.config.js 파일이 추가됨을 확인할 수 있다. 파일 내용은 다음과 같다.
1
+2
+3
+4
+5
+6
+
module.exports = {
+  plugins: {
+    tailwindcss: {},
+    autoprefixer: {},
+  },
+};
+
  • 위 코드를 보면 tailwindcss 플러그인과, autoprefixer 플러그인이 추가됐음을 알 수 있다.
    • tailwindcss : tailwind css를 컴파일 해주는 플러그인
    • autoprefixer : 브라우저별 호환성을 맞춰주도록 css를 변환시켜주는 플러그인.

Creat-react-app v5

  • 자, 그럼 가이드를 따라가면 어떻게 hot reload가 되는걸까?

기존의 방법

  • tailwindcss를 CRA에서 사용하는 방법을 검색해보면 수많은 포스트에서 CRACO를 사용해야한다고 나와있다.

    • CRACO : CRA의 설정(웹팩 등)을 npm eject를 하지 않더라도 변경해줄수 있도록 하는 라이브러리
  • 이 CRACO을 통해 postcss를 webpack의 hot-reload 과정에 등록하여 hot-reload가 되는 방식으로 하였다.

Creact-react-app v5

  • 개요의 가이드를 보면, CRA v5 로 만들어진 프로젝트를 기반으로 한다. 그리고 CRA v5 의 업데이트 목록에는 다음과 같은 tailwind support가 추가되었다고 한다.
    • https://github.com/facebook/create-react-app/pull/11717
  • 위 PR은, tailwind.config.js 파일이 있으면 이를 불러들여 자동으로 CRA의 webpack config를 변경하여 tailwind를 지원하도록 하는 내용이다.
  • 그리고 개요에 나와있는 ‘npx tailwindcss init -p’ 를 실행하면, tailwind.config.js 이 자동으로 생성된다.
  • 따라서 이러한 방식을 통해 개요의 가이드만을 따라가도, 다른 여러 복잡한 설정을 거치지 않아도 hot-reload가 가능한 것이다.

의문

  • 그럼 왜 postcss.config.js 파일을 생성하는 걸까?
  • CRA v5에서도 tailwind.config.js 가 필요하다. 이를 위해선 npx tailwindcss init -p 가 필요하고, 이 작업을 통해 postcss.config.js도 추가 된다.

  • 이때 “npx tailwindcss init -p” 를 사용하는 프로젝트에는 CRA v5로 만들어지지 않은 프로젝트도 존재할테니, postcss.config.js도 추가되도록 놔둔 것으로 생각된다.
This post is licensed under CC BY 4.0 by the author.

flutter (rn 개발자를 위한 정리 4)

1613 역사

Comments powered by Disqus.

diff --git "a/posts/react18\354\227\205\353\215\260\354\235\264\355\212\270/index.html" "b/posts/react18\354\227\205\353\215\260\354\235\264\355\212\270/index.html" new file mode 100644 index 000000000..8c2386100 --- /dev/null +++ "b/posts/react18\354\227\205\353\215\260\354\235\264\355\212\270/index.html" @@ -0,0 +1,31 @@ + React 18로 업데이트 방법, 문제점 정리 | 디피의 개발일지
Posts React 18로 업데이트 방법, 문제점 정리
Post
Cancel

React 18로 업데이트 방법, 문제점 정리

기존에 React 17을 사용하던 Forkie Player 프로젝트를 React 18로 업데이트 하는 기록

업데이트 하는 이유

  1. automatic batching
    • 동영상이 끝났을때 다음 영상으로 넘어가거나, 현재 영상의 시작부분으로 돌아가기 위해선 동영상의 timeupdate, ready, pause, ended 등등의 상태에 대응할 필요가 있다.
    • 이때 이벤트 핸들러 안에서 여러 상태업데이트를 할때, automatic batching이 없을때는 서로 다른 타이밍에 발생하는 업데이트를 대응하기가 까다로웠다.
    • react 18로 업데이트하면서 automatic batching이 적용되고 이러한 문제점이 사라지길 기대하며 업데이트를 하였다.
  2. Suspense

    • 현재 forkie에서는 SSR를 사용하지 않기 때문에, SSR을 위해 도입한 것은 아니다.
    • 하지만 검색, 플레이리스트 수정 등을 할때 Loading일 경우 fallback을 하도록 하는 좀 더 깔끔한 코드를 원했기 때문에 react 18을 도입하였다
  3. 항상 신기술을 원하는 개발자의 마음…
    • 새로운 것이 나오면 어쨌든 더 좋지 않겠어? 라는 마음으로 설치하게 되어버린다.
    • 만약 새로운 툴에 문제가 있다면 설치하지 않는 것이 좋으나, 페이스북에서 공들이고있는 React이고, 상당히 긴 알파/베타 기간을 거친 걸로 알고 있는데, 충분히 stable하다고 생각되어서 업데이트를 결정하였다.


업데이트 방법

공식문서에 나온대로 업데이트 하면 된다.

https://reactjs.org/blog/2022/03/08/react-18-upgrade-guide.html



업데이트 과정 중 문제점

typescript 에러

다른 분이 작성한 문서를 보며 해결하였다.


패키지 호환성문제

타입스트립트를 사용하지 않을 경우 큰 문제는 없다. 하지만, 타입스크립트를 사용할 경우 패키지와 사용하는 리액트 버전이 차이가 나기에 타입 에러가 발생할 수도 있다.

자주 업데이트가 되는 패키지의 경우 이미 react 18을 대응하여 업데이트를 해놨을테니 해당 버전으로 업데이트를 하면된다.

하지만 문제가 되는 경우가 두가지 있다.

  1. 새로운 버전에 큰 변화가 있어 앱에서 해당 패키지를 사용하는 부분을 함께 수정해야하는 경우
  2. 자주 업데이트되는 패키지가 아니어서, react 18을 대응한 버전이 없을 경우

위 두가지 문제를 해결하기 위해선 force-resolution을 하여야한다. 패키지 매니저로 npm을 사용하는 경우와 yarn을 사용하는 경우 다르게 대응하여야한다. 이 글은 yarn을 기준으로 적었다.

yarn 에서는 package.jsonresolution 을 추가함으로써 해결할 수 있다고 공식문서에서 말하고 있다.

forkie에서 적용한 예시는 다음과 같다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+
{
+  "name": "forkie-player_web_renewal",
+  "dependencies": {
+    "@types/react": "^18.0.10",
+    "@types/react-dom": "^18.0.5",
+    "react": "^18.1.0",
+    "react-dom": "^18.1.0"
+  },
+  "resolutions": {
+    "react": "^18.1.0",
+    "react-dom": "^18.1.0",
+    "@types/react": "^18.0.10",
+    "@types/react-dom": "^18.0.5"
+  }
+}
+

이후 yarn install을 하면 된다.

This post is licensed under CC BY 4.0 by the author.

velog 메모

virtual DOM와 리렌더링

Comments powered by Disqus.

diff --git a/posts/reactv18/index.html b/posts/reactv18/index.html new file mode 100644 index 000000000..12ac1a0bb --- /dev/null +++ b/posts/reactv18/index.html @@ -0,0 +1,429 @@ + React v18 주요 변경점 | 디피의 개발일지
Posts React v18 주요 변경점
Post
Cancel

React v18 주요 변경점

React v18의 정식 출시가 코앞에 있고, 가장 큰 변경점 중 하나가 서버사이드 렌더링에 관한 내용이라는 소식을 듣고 마침 SSR 에 대해 흥미가 있던 터라 한번 React v18의 주요 변경점이 뭔지 공부해보기로 했다.

먼저 기존의 React v17을 v18로 마이그레이션 하는 것은 문제 없다고 한다. 리액트 팀에서 이 부분을 특히 신경써서 만들었다고 하니 믿어도 될 것같다.

Automatic batching

먼저 리액트의 배치에 대해 알아야한다.

Batching

리액트에서 더 나은 성능과 예상치않는 버그의 방지를 위해 여러 개의 상태 업데이트를 한번의 리렌더링으로 묶는 작업

아래 예시를 보자

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+
function App() {
+  const [count, setCount] = useState(0);
+  const [flag, setFlag] = useState(false);
+
+  function handleClick() {
+    setCount(c => c + 1); // 아직 리렌더링 되지 않습니다.
+    setFlag(f => !f); // 아직 리렌더링 되지 않습니다.
+    // 리액트는 오직 마지막에만 리렌더링을 한 번 수행합니다. (배치 적용)
+  }
+
+  return (
+    <div>
+      <button onClick={handleClick}>Next</button>
+      <h1 style=>{count}</h1>
+    </div>
+  );
+}
+

handleClick 에서는 총 두번의 상태 업데이트를 했다. 하지만, 리액트에서는 언제나 배치를 수행하여 이 두번의 상태업데이트를 한번의 리렌더링으로 처리한다.

하지만 배치가 수행되지 않는 예외가 존재한다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
function App() {
+  const [count, setCount] = useState(0);
+  const [flag, setFlag] = useState(false);
+
+  function handleClick() {
+    fetchSomething().then(() => {
+      // 리액트 17 및 그 이전 버전에서는 배치가 수행되지 않습니다. 왜냐하면
+      // 이 코드들은 이벤트 이후의 콜백에서 실행되기 때문입니다.
+      setCount(c => c + 1); // 리렌더링 
+      setFlag(f => !f); // 리렌더링
+    });
+  }
+
+  return (
+    <div>
+      <button onClick={handleClick}>Next</button>
+      <h1 style=>{count}</h1>
+    </div>
+  );
+}
+

위처럼 리액트 v17까지는 이벤트 핸들러 안에서의 상태업데이트에서는 배치가 적용되지 않았다.

하지만 v18부터는 자동배치가 추가되어, 이벤트 핸들러 함수 안에서 상태 업데이트에 대해서도 자동으로 배치를 적용시켜준다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+
setTimeout(() => {
+  setCount(c => c + 1);
+  setFlag(f => !f);
+  // 리액트는 오직 마지막에만 리렌더링을 한 번 수행합니다. (배치 적용)
+}, 1000);
+
+fetch(/*...*/).then(() => {
+  setCount(c => c + 1);
+  setFlag(f => !f);
+  // 리액트는 오직 마지막에만 리렌더링을 한 번 수행합니다. (배치 적용)
+})
+
+elm.addEventListener('click', () => {
+  setCount(c => c + 1);
+  setFlag(f => !f);
+  // 리액트는 오직 마지막에만 리렌더링을 한 번 수행합니다. (배치 적용)
+});
+

이 자동 배치를 사용하기위해서는 리액트앱의 최상단에서 ReactDOM.render 함수 대신에 새로운 ReactDOM.createRoot 함수를 사용해야한다.

하지만 자동배치를 하고 싶지 않은 경우도 매우 드물게 있을 것이다. 이때는 다음과 같이 ReactDOM.flushSync라는 함수를 사용하면 해결된다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
import { flushSync } from 'react-dom';
+
+function handleClick() {
+  flushSync(() => {
+    setCounter(c => c + 1);
+  });  // 리액트는 즉시 DOM을 업데이트합니다.
+
+  flushSync(() => {
+    setFlag(f => !f);
+  });  // 리액트는 즉시 DOM을 업데이트합니다.
+}
+


startTransition

startTransition은 동시성(concurrent) API 안에 분류되어있는데, 아직 자세한 내용은 공개되지 않았다고 한다. 추후 조사가 필요함

리액트에서 발생하는 상태업데이트에는 두가지 분류가 있다.

  • 긴급업데이트 : 직접적인 상호 작용 반영. (타이핑, 오버, 스크롤링 등) 느려질 경우 UX에 안좋은 영향을 줌
  • 전환업데이트 : 하나의 뷰에서 다른 뷰로의 UI 전환. 느려도 어느정도 괜찮음

중요한 건 전환업데이트때문에 긴급업데이트가 방해되면 안된다.

React v17까지는 상태 업데이트를 긴급 혹은 전환으로 명시하는 방법은 없었다. 하지만 React v18부터는 startTansition API를 제공함으로써 전환업데이트를 명시적으로 구분하여 상태업데이트를 할 수 있게 되었다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+
import { useTransition } from 'react';
+
+function SearchBar() {
+	const [isPending, startTransition] = useTransition();
+
+  // ...
+
+	function handleChange(e) {
+		const input = e.target.value;
+
+		// 긴급 업데이트: 타이핑 결과를 보여준다.
+		setInputValue(input);
+
+		// 이 안의 모든 상태 업데이트는 전환 업데이트가 된다.
+		startTransition(() => {
+		  setSearchQuery(input);
+		});
+	}
+
+  // ...
+}
+

좀 더 이해를 돕기위해 다음 예시를 보자.

React v17 까지의 방식대로 아래와 같이 코드짰다고 해보자

1
+2
+3
+4
+5
+6
+
function handleChange(e) {
+	const input = e.target.value;
+
+	setInputValue(input);
+	setSearchQuery(input);
+}
+

이때, 리액트에서는 배치가 이루어져, setInputValue를 거치고, setSearchQuery를 기다렸다가 한번에 상태업데이트를 했다.

이는 긴급업데이트를 하는데 전환업데이트까지 기다려야하는 UX의 관점에서 안좋은 영향을 끼친다.

하지만, startTansition으로 아래와 같이 코드를 짰다고 해보자.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+
	function handleChange(e) {
+		const input = e.target.value;
+
+		// 긴급 업데이트: 타이핑 결과를 보여준다.
+		setInputValue(input);
+
+		// 이 안의 모든 상태 업데이트는 전환 업데이트가 된다.
+		startTransition(() => {
+		  // 전환 업데이트: 결과를 보여준다.
+		  setSearchQuery(input);
+		});
+	}
+

setInputValue을 거치고, startTransition가 수행된 후 바로, startTansition 외부에 있는 상태들이 업데이트되고, 내부에 있는 상태업데이트는 pending 상태가 된다. 따라서 긴급업데이트는 바로 되고, 전환업데이트는 기다렸다가 되도록 하여, UX의 관점에서 더 향상된다.

이때, 소수의 케이스에 대응하기 위해 Hook을 거치지 않고, React.startTransition을 사용할 수도 있다.

startTransition을 사용하는 2가지 케이스

  • 느린 렌더링 : 작업량이 많아 결과를 보여주기 위한 UI전환까지 시간이 걸리는 상태업데이트
  • 느린 네트워크 : 네트워크로부터 데이터를 기다리기 위한 시간이 걸리는 업데이트 -> Suspense와 연계가능


Suspense

Suspense는 SSR에 관한 컴포넌트이기 때문에 먼저 SSR에 대해서 알아야한다.

SSR을 사용하는 이유

React는 자바스크립트로 이루어져있고, 따라서 사용자 브라우저로 자바스크립트가 다운이 되고, 이 자바스크립트를 실행하고, 로드가 완료가 되어야 화면에 컴포넌트들이 보여진다. 즉, 이 자바스크립트를 로딩하는 동안 사용자가 볼수 있는 페이지는 빈페이지 뿐이고, 어플리케이션의 크기가 크다면 이 시간은 더 길어지게 된다.

하지만 SSR을 사용하면, 서버에서 리액트 컴포넌트를 HTML로 렌터링 하여 사용자에게 전송할 수 있다. 이때 자바스크립트는 로딩되지 않았으므로 인터랙션은 불가능하지만, 최소한 사용자가 컨텐츠는 볼 수 있도록 해준다.

자바스크립트가 모두 로딩되면, 이 HTML을 인터랙션이 가능한 상태로 만든다. 이때 우리는 리액트에게 서버에서 생성된 페이지가 있다. 여기에 이벤트 핸들러를 붙여라고 명령하고, 리액트는 이미 생성되어있는 HTML에 이밴트핸들러를 연결한다. 이 작업을 hydration이라고 한다.


기존 React에서의 SSR 문제점

위에서 설명한 SSR 과정을 정리하면 다음과 같다.

  1. 서버에서 앱 전체를 위한 데이터를 불러옴.
  2. 서버에서 앱 전체를 HTML로 렌터링 한 다음에 이를 응답으로 돌려보내준다.
  3. 클라이언트에서 앱 전체 자바스크립트 코드를 실행한다.
  4. 클라이언트에서 서버에서 만들어진 HTML과 자바스크립트 로직을 결합한다.(이를 hydration이라고 함.)

문제점 1 : 1번에서의 문제점

HTML을 렌더할때 필요한 데이터를 모두 가지고 있어야한다.

예를 들어, 댓글을 서버 HTML 결과물에 포함시킨다고 결정하면 댓글에 필요한 데이터를 API나 데이터베이스를 통해 불러오고 결과 HTML에 포함시켜야한다. 이때 이 댓글 데이터를 불러오는 것 때문에, 이미 작업이 끝난 다른 HTML들도 내려보내지 못한다.

만약 포함시키지 않으면 자바스크립트를 통해 렌더해야하므로 사용자는 자바스크립트 로딩이 끝날때까지 댓글을 볼 수 없다.

문제점 2 : hydration에서의 문제점 1

hydrating 과정에서 리액트는 컴포넌트들을 렌더링하는 동안, 서버에서 생성한 HTML을 순회하면서 이벤트핸들러를 HTML에 연결한다. 이 작업을 수행하려면 브라우저의 컴포넌트에서 생성된 트리가 서버에서 생성된 트리와 일치되어야한다. 그렇지 않으면 React는 이를 맞추지 못하게 된다. 결과적으로 클라이언트에 있는 모든 컴포넌트의 자바스크립트를 로딩해야 hydrate가 가능해진다.

예를 들어 댓글에 복잡한 기능이 포함되어있어 이를 위한 자바스크립트 로딩이 오래걸린다고 해보자.

서버에서 댓글을 HTML에 포함 시킬 경우, hydration은 한번에 일어나야하기떄문에 댓글 위젯에 있는 코드를 로딩하기 전까지는 다른 컴포넌트(ex : 사이드바, 게시글)에 대해서는 hydration을 할 수가 없다.

코드 분할을 통해서 해결할 수 있지만, 이때는 서버 HTML에서 댓글을 제거해야한다. 그렇지 않으면 리액트는 이 HTML을 어떻게 해야할지 모르고 hydration 중에 삭제해 버린다.

문제점 3 : hydration에서의 문제점 2

현재 리액트는 한번에 모든 트리에 hydration을 진행한다. 즉 일단 hydration을 시작하면, 트리 전체에서 이 작업이 완료할 때까지 멈출 수 없다. 따라서 모든 컴포넌트가 hydration을 할 때까지 기다려야만 사용자는 컴포넌트와 상호작용할 수 있다.

만약 사용자가 로딩이 오래 걸려 페이지에서 벗어나고 싶어해도, 브라우저는 hydration 작업으로 바쁘기 때문에 아직 로딩중이고, 다른 페이지로 이동할 수도 없다.


Suspense

기존 SSR에서의 문제점들의 공통점은 모든 작업들이 전체 앱을 기준으로 일어나고, 한 단계가 끝나야 다음 단계로 넘어가는 방식으로 되어있어 발생한다는 점이다. 따라서 React 18에서는 Suspense를 이용한 두가지 주요 SSR 기능을 제공한다.

  • 서버에서 HTML을 스트리밍한다. 이를 위해 renderToString 대신 pipeToNodeWritable을 사용해야한다.
  • 클라이언트에서 선택적 hydration : 이를 위해 createRoot를 사용하고, <Suspense> 로 감싼다.

모든 데이터를 불러오기전에, HTML을 스트리밍한다

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+
<main>
+  <nav>
+    <!--NavBar -->
+    <a href="/">Home</a>
+  </nav>
+  <aside>
+    <!-- Sidebar -->
+    <a href="/profile">Profile</a>
+  </aside>
+  <article>
+    <!-- Post -->
+    <p>Hello world</p>
+  </article>
+  <section>
+    <!-- Comments -->
+    <p>First comment</p>
+    <p>Second comment</p>
+  </section>
+</main>
+

현재 SSR 체계에서 위 HTML을 밭으면 클라이언트를 아래를 그릴 것이다.

1

그리고 코드를 로딩하고 hydration이 끝나면 아래와 같은 완전한 앱이 완성된다.

2

하지만 React 18에서는 페이지의 일부를 Suspense로 감쌀 수 있다.

예를들어, 댓글을 Suspense로 감싸고, 로딩되기전까지는 Spinner를 보여지게 할 수 있다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+
<Layout>
+  <NavBar />
+  <Sidebar />
+  <RightPane>
+    <Post />
+    <Suspense fallback={<Spinner />}>
+      <Comments />
+    </Suspense>
+  </RightPane>
+</Layout>
+

<Comments>를 <Suspense>로 감싼 덕분에, 리액트는 댓글 컴포넌트를 기다리지 않고, HTML을 스트리밍할 수 있다.

3

클라이언트가 받은 최초의 HTML은 다음과 같을 것이다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+
<main>
+  <nav>
+    <!--NavBar -->
+    <a href="/">Home</a>
+  </nav>
+  <aside>
+    <!-- Sidebar -->
+    <a href="/profile">Profile</a>
+  </aside>
+  <article>
+    <!-- Post -->
+    <p>Hello world</p>
+  </article>
+  <section id="comments-spinner">
+    <!-- Spinner -->
+    <img width="400" src="spinner.gif" alt="Loading..." />
+  </section>
+</main>
+

그리고 댓글 데이터가 준비 된다면, 리액트는 같은 스트림으로 추가적인 HTML을 보낼 텐데, 여기에는 HTML을 올바른 위치에 삽입하기 위한 최소한의 인라인 script 태그가 포함되어있다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
<div hidden id="comments">
+  <!-- Comments -->
+  <p>First comment</p>
+  <p>Second comment</p>
+</div>
+<script>
+  // This implementation is slightly simplified
+  document
+    .getElementById('sections-spinner')
+    .replaceChildren(document.getElementById('comments'))
+</script>
+

그 결과 리액트 자체가 클라이언트에 로드되기 이전에, 뒤늦게 댓글용 HTML 코드가 삽입될 것이다.

4213

이로써 첫번째 문제를 해결할 수 있다. 더이상 모든 데이터를 fetch할 때까지 기다릴 필요가 없다. 화면 일부분이 초기 HTML을 지연시킨다면, 모든 HTML을 지연시키거나 HTML에서 일부를 제외시킬 필요가 없다. HTML 스트림에서 나중에 해당 부분을 별도로 삽입할 수 있다.


모든 코드가 로드되기 전에 페이지를 hydration 하기

초기 HTML은 일찍 보낼 수 있지만, 아직 전체 hydration이 끝나야 사용자가 인터랙션할 수 있다는 것에는 변함이 없다.

따라서 큰 번들을 피하기 위해 “코드 스플릿”을 통해 코드의 일부를 동기적으로 로딩하지 않게하거나, 혹은 번들러가 별도의 script 태그로 분할하는 방법도 있다.

이때 React.lazy 로 코드를 분할하여 메인번들에서 댓글 코드를 아래처럼 분리할 수 있다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+
import { lazy } from 'react'
+
+const Comments = lazy(() => import('./Comments.js'));
+
+// ...
+
+<Suspense fallback={<Spinner />}>
+  <Comments />
+</Suspense>
+

과거에는 이 방법은 동작하지 않았다. 왜냐하면, 기존의 SSR에서는 코드스플릿 컴포넌트를 제외하거나 코드를 모둔 로딩한 후 hydration을 하거나 둘 중 하나일뿐이었다. 따라서 코드스플릿의 목적이 다소 손상되었다. 하지만 React v18에서는 Suspense을 통해 댓글 위젯이 로드되기 전에 애플리케이션을 hydration 할 수 있다.

즉, <Comments>를 <Suspense>로 감쌈으로써, 리액트에 페이지의 나머지 부분을 스트리밍하는 것을 차단하지 않게하고, hydration 과정 또한 차단하지 않게 할 수 있다. 이로써 첫번째 두번째 문제점은 해결되었다.


모든 컴포넌트가 hydrate 되기 전에 페이지와의 상호작용

그럼 세번째 문제점인, 전체 앱이 hydration 되기 전까지는 다른 컴포넌트와 인터랙션 할 수 없다는 문제는 어떻게 될까?

React 18에서는 hydration 작업이 브라우저의 다른 작업을 하는 것을 막지 않음으로써 해결된다.

React 18에서는, Suspense 바운더리 내에 있는 hydration 콘텐츠는 브라우저가 이벤트를 처리할 수 있는 한에서만 수행된다. 따라서 사용자가 다른 부분을 클릭하면 그것이 먼저 처리되고, 페이지에서 벗어나고 싶을때도 바로 처리된다.


여러개의 Suspense가 있을 때

만약 한 페이지에 여러개의 Suspense로 감싸진 컴포넌트가 있으면 어떻게 동작할까?

예를들어, sidebar와 comments가 Suspense로 감싸져 있다고 해보자. 그러면 sidebar와 comments를 제외한 부분의 작업이 hydration까지 모두 마쳐 다음과 같은 모습을 보이게 될 것이다.

4

그 다음, sidebar와 comments를 모두 포함하는 번들이 로딩되고, 두 컴포넌트에 대해 hydration을 시도 할 것이다.

step2

이때 만약, 사용자가 댓글위젯에 인터랙션을 시도했다고 해보자. 그럼 아래와 같이 hydration의 우선순위가 바뀌어서 리액트는 comments 부분을 먼저 hydration 하도록 하게된다.

6

7

아래와 같이 Suspense를 사용하게 되면 어떻게 될까?

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+
<Layout>
+  <NavBar />
+  <Suspense fallback={<BigSpinner />}>
+    <Suspense fallback={<SidebarGlimmer />}>
+      <Sidebar />
+    </Suspense>
+    <RightPane>
+      <Post />
+      <Suspense fallback={<CommentsGlimmer />}>
+        <Comments />
+      </Suspense>
+    </RightPane>
+  </Suspense>
+</Layout>
+

초기 HTML은 <NavBar>만을 포함하지만, 나머지는 사용자가 상호작용한 부분을 우선시하여 관련 코드가 로딩되는 즉시 스트리밍하여 컴포넌트에서 hydration을 할 것이다.

“어떻게 애플리케이션이 전체적으로 hydration 되지 않았는데 동작할 수 있는걸까? 리액트는 개별 컴포넌트에 개별적으로 hydration 하는 것이 아닌 <Suspense> 바운더리에 대해 hydration을 발생시킨다<Suspense>는 당장 나타나지 않는 컨텐츠에 사용되므로, 코드는 이 자식 컨텐츠가 즉시 이용할 수 없는 상태에 대해 탄력적으로 대처할 수 있다. 리액트는 항상 부모 컴포넌트를 우선순위로 hydration 하므로, 컴포넌트는 항상 props set을 가지고 있을 수 있다. 리액트는 이벤트가 발생될 때 이벤트 지점에서 전체 상위 트리에 hydration이 진행될 때 까지 이를 보류 시켜 둔다. 마지막으로 상위 항목이 그럼에도 hydration이 되지 않는다면, 리액트는 이를 숨기고 코드가 로드 될 때 까지 fallback으로 화면을 바꿔 둔다. 이렇게 하면 트리가 일관되게 유지된다.”


데모

https://codesandbox.io/s/festive-star-9hfqt?file=/src/App.js

위 데모코드에서 sever/delays.js을 조작함으로써 React 18의 SSR을 확인할 수 있다.

  • APP_DELAY : 댓글을 가져오는 시간을 오래걸리게 하여 HTML의 나머지 부분을 초기에 전송하는 것을 보여줌
  • JS_BUNDLE_DELAY : script 태그가 로딩되는 것을 지연하여 댓글 HTML이 나중에 삽입되는 것을 볼 수 있음.
  • ABORT_DELAY :서버에서 가져오는 시간이 너무 길어질 경우, 서버가 렌더링을 포기하고 클라이언트에서 렌더링하는 것을 볼수 있음.


요약

리액트 18은 SSR을 위한 두가지 주요 기능을 제공한다.

  • HTML 스트리밍: 개발자가 원하는 만큼 HTML을 조기에 스트리밍 할 수 있게 해주며, 나중에 로딩된 HTML을 올바른 위치에 놓아주는 <script>태그와 함께 추가적으로 스트리밍할 수 있다.
  • 선택적 hydration: HTML과 자바스크립트 코드의 나머지 부분이 완전히 다운로드 되기전에 가능한 빨리 애플리케이션이 hydration 할 수 있도록 한다. 또한 사용자가 상호작용하는 컴포넌트에 hydration 하는 것을 우선시하여, 즉각적으로 hydration 되는 것과 같은 착각을 불러일으킨다.

이 두가지 기능은 SSR과 관련된 아래 세가지 문제를 해결해준다.

  • HTML을 내보내기전에 서버에서 모든 데이터가 로딩될 때 까지 기다릴 필요가 없다. HTML을 보낼 수 있는 상황이라면 바로 HTML을 보내고, 나머지부분은 준비되는 대로 스트리밍 할 수 있다.
  • hydration을 하기 위해 모든 자바스크립트 코드가 로드 될 때 까지 기다릴 필요가 없다. SSR과 함께 코드 스플릿을 사용할 수 있다. 이렇게 하면 서버 HTML은 그대로 보존할 수 있고, 리액트는 관련 코드가 로드될 때 추가로 hydration 한다.
  • 페이지와 상호작용하기 위해 모든 컴포넌트가 hydration 되는 것을 기다릴 필요가 없다. 대신 선택석 hydration을 사용하여 사용자가 상호작용하는 컴포넌트에 우선순위를 지정하고 조기에 hydration을 수행할 수 있다.

<Suspense>는 이러한 모든 기능에 대한 옵트인 역할을 한다. 이 개선사항은 리액트 내부에서 자동으로 수행되며, 기존 리액트 코드의 대부분과 함께 작동 될 것으로 보인다. 이는 로딩중 상태를 선언적으로 표현하는 역할을 한다. if (isLoading)과 크게 달라보이지 않을 수 있지만, <Suspense>는 이러한 모든 개선사항을 실현해낸다.



New Hooks

useId

클라이언트와 서버 사이의 hydraton missmatch를 피하면서 unique ID를 생성해주는 Hook. SSR을 할때 사용함. 물론 그냥 Id 를 사용하고 싶을때 사용해도 됨.


useTransition

startTransition을 만들기 위한 Hook


useDeferredValue

1
+
const deferredValue = useDeferredValue(value);
+

value를 받고, 그 복사본을 반환함. 이 복사본은 좀 더 긴급한 업데이트가 끝날때까지 연기하기위해 사용됨.

user input 같은 긴급한 업데이트가 있다면, deferredValue는 이전의 값을 가리키고, 긴급 업데이트가 끝난 후에 업데이트 됨.

긴급한 업데이트가 진행중일때 자식 컴포넌트가 리렌더링되는 것을 막으려면 useMemo 등과 함께 사용되어야한다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
function Typeahead() {
+  const query = useSearchQuery('');
+  const deferredQuery = useDeferredValue(query);
+
+  // Memoizing tells React to only re-render when deferredQuery changes,
+  // not when query changes.
+  const suggestions = useMemo(() =>
+    <SearchSuggestions query={deferredQuery} />,
+    [deferredQuery]
+  );
+
+  return (
+    <>
+      <SearchInput query={query} />
+      <Suspense fallback="Loading results...">
+        {suggestions}
+      </Suspense>
+    </>
+  );
+}
+
  • suggestions는 query가 아니라 deferredQuery가 업데이트 되었을때 업데이트 된다.


useSyncExternalStore

redux store, 글로벌 변수, dom 상태 등 external store를 구독하여 concurrent read를 지원함.

React 18부터는 렌더링이 렌더링을 잠시 중단할 수 있게된다. 그러면서 각 컴포넌트별로 업데이트 속도가 달라서 발생하는 Tearing(시각적 불일치) 문제가 발생했는데 이를 해결하기 위한 Hook이다.

external data를 구독할때 useEffect 대신에 사용할 수 있다.

1
+
const state = useSyncExternalStore(subscribe, getSnapshot[, getServerSnapshot]);
+
  • subscribe : 변화가 생겼을때 호출되는 callback
  • getSnapshot : 구독할 store의 값


useInsertionEffect

css-in-js 라이브러리가 렌더링 도중에 스타일을 삽입할때 성능문제를 해결할 수 있는 새로운 Hook

모든 DOM mutation이 발생하기 전에 동기적으로 호출됨.

DOM에 스타일을 삽입할때 사용함.

한정된 영역에서 사용되므로, refs나 schedule update가 불가능하다.

1
+
useInsertionEffect(didUpdate)
+

useLayoutEffect 로 dom-layout를 읽기 전에 사용한다.



그외 변경점

일관된 useEffect 타이밍

클릭, 키다운과 같은 이벤트 중에 업데이트가 발생할때, useEffect가 일관된 타이밍에 호출되도록 함.

이전에는 일관되지 않았음.

New js environment requirements

이제 React는 Promise, Symbol, Object.assign과 같은 최신JS에 의존한다. 따라서 IE와 같은 오래된 브라우저에 대응하려면 bundled application에 polyfill를 사용해야한다.

컴포넌트가 undefined를 렌더할 수 있게 됨.

이전에는 undefined를 렌더할 경우 throw가 발생했으나, 이제는 undefined도 된다.(null은 원래 됐음)



React v18로의 업데이트 방법

클라이언트

React 18부터는 react-dom의 render함수는 deprecated 되고, createRoot 가 사용됨.

1
+2
+3
+4
+5
+6
+
// ~React 17
+ReactDOM.render(<App />, container);
+
+// React 18
+const root = ReactDOM.createRoot(container);
+root.render(<App />);
+

기존의 redner 함수를 사용하게 되면, 컴포넌트 트리를 React 17과 그 이전의 동작과 동일한 레거시 모드로 동작하도록 함.

새로운 createRoot 함수는 컴포넌트 트리를 동시성 기능들과 자동배치 등 React 18의 기능들이 동작하도록 함.

서버

React 18에서는 렌더링을 위한 3가지 API가 존재함

  • renderToString : 유지 (제한된 Suspense 지원)
  • renderToNodeStream : Deprecated (Full suspence를 지원하나, 스트리밍 되지 않음)
  • pipeToNodeWritable : 신규 API, 사용추천. (스트리밍으로 Full Suspense 지원)

출처

https://medium.com/naver-place-dev/react-18%EC%9D%84-%EC%A4%80%EB%B9%84%ED%95%98%EC%84%B8%EC%9A%94-8603c36ddb25

https://yceffort.kr/2021/09/react-18-ssr-architecture#%EC%98%A4%EB%8A%98%EB%82%A0-ssr%EC%9D%98-%EB%AC%B8%EC%A0%9C%EC%A0%90%EC%9D%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80

https://velog.io/@moonelysian/React-18-%EC%82%B4%ED%8E%B4%EB%B3%B4%EA%B8%B0

This post is licensed under CC BY 4.0 by the author.

좋은 코드란 무엇인가?

Object Oriented Programming

Comments powered by Disqus.

diff --git a/posts/redux-saga/index.html b/posts/redux-saga/index.html new file mode 100644 index 000000000..691589ecc --- /dev/null +++ b/posts/redux-saga/index.html @@ -0,0 +1,375 @@ + redux-saga concepts | 디피의 개발일지
Posts redux-saga concepts
Post
Cancel

redux-saga concepts

Declarative Effects

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
import { takeEvery } from "redux-saga/effects";
+import Api from "./path/to/api";
+
+function* watchFetchProducts() {
+  yield takeEvery("PRODUCTS_REQUESTED", fetchProducts);
+}
+
+function* fetchProducts() {
+  const products = yield Api.fetch("/products");
+  console.log(products);
+}
+
1
+2
+
const iterator = fetchProducts()
+assert.deepEqual(iterator.next().value, ??)
+
  1. iterator 에는 제너레이터 객체가 반환됨.
  2. next() 를 하면, Api.fetch 가 실행되면서 Promise 객체가 반환됨.

이때 위 featchProducts()를 테스트하기 위해선, API를 호출해야하는데, 실제로 호출할 순 없으므로 다른 방법을 통해 API를 호출하지 않고 테스트를 진행해야한다.

effects 사용

1
+2
+3
+4
+5
+6
+
import { call } from "redux-saga/effects";
+
+function* fetchProducts() {
+  const products = yield call(Api.fetch, "/products");
+  // ...
+}
+
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
import { call } from "redux-saga/effects";
+import Api from "...";
+
+const iterator = fetchProducts();
+
+// expects a call instruction
+assert.deepEqual(
+  iterator.next().value,
+  call(Api.fetch, "/products"),
+  "fetchProducts should yield an Effect call(Api.fetch, './products')"
+);
+
  • 위에서와는 달리, iterator.next() 는 call 함수를 반환하게 됨.
  • 실제 API를 호출하지 않으므로, 테스트하기가 훨씬 용이해진다.

api 호출 effects

1
+2
+
yield call([obj, obj.method], arg1, arg2, ...);		// 주어진 object의 주어진 method를 호출
+yield call(fn, arg1, arg2, ...);
+
1
+
yield apply(obj, obj.method, [arg1, arg2, ...])  // call과 같고 arguments 형식만 반대
+
1
+
yield cps(fn, arg1, arg2, ...)
+
  • node 스타일 함수를 호출할때 사용 ((error, result) => ())

Dispatching Actions

제너레이터 함수 안에서, dispatch를 하기 위해 아래와 같이 직접 dispatch를 호출할 수 있다.

1
+2
+3
+4
+
function* fetchProducts(dispatch) {
+  const products = yield call(Api.fetch, "/products");
+  dispatch({ type: "PRODUCTS_RECEIVED", products });
+}
+

하지만 이는 위에서와 같이, 테스트를 하기 위해선 dispatch 함수를 mock 해야할 필요가 있다.

따라서 redux-saga에서는 아래와 같이 “put” effect를 통해 dispatch를 할 수 있다.

1
+2
+3
+4
+5
+6
+7
+8
+
import { call, put } from "redux-saga/effects";
+// ...
+
+function* fetchProducts() {
+  const products = yield call(Api.fetch, "/products");
+  // create and yield a dispatch Effect
+  yield put({ type: "PRODUCTS_RECEIVED", products });
+}
+

Error handling

사가에서는 아래와 같은 방식으로 error handling 가능

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+
import Api from "./path/to/api";
+import { call, put } from "redux-saga/effects";
+
+// ...
+
+function* fetchProducts() {
+  try {
+    const products = yield call(Api.fetch, "/products");
+    yield put({ type: "PRODUCTS_RECEIVED", products });
+  } catch (error) {
+    yield put({ type: "PRODUCTS_REQUEST_FAILED", error });
+  }
+}
+

아래와 같은 형태로도 가능하다

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+
import Api from "./path/to/api";
+import { call, put } from "redux-saga/effects";
+
+function fetchProductsApi() {
+  return Api.fetch("/products")
+    .then((response) => ({ response }))
+    .catch((error) => ({ error }));
+}
+
+function* fetchProducts() {
+  const { response, error } = yield call(fetchProductsApi);
+  if (response) yield put({ type: "PRODUCTS_RECEIVED", products: response });
+  else yield put({ type: "PRODUCTS_REQUEST_FAILED", error });
+}
+

Saga helpers

  • takeEvery : 모든 dispatch 액션을 수용함
  • takeLatest : 가장 나중에 들어온 액션만 수용함
    • 이전에 들어와 수행중인 task는 중단되고, 나중에 들어온 task 가 수행되는 방식
1
+2
+3
+4
+5
+6
+7
+8
+
import { takeEverym, takeLatest } from "redux-saga/effects";
+
+function* watchFetchData() {
+  yield takeEvery("FETCH_REQUESTED", fetchData);
+}
+function* watchFetchData() {
+  yield takeLatest("FETCH_REQUESTED", fetchData);
+}
+

channel

아래와 같이 take/fork 를 통해 여러 요청을 동시적으로 수행할 수 있다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+
import { take, fork, ... } from 'redux-saga/effects'
+
+function* watchRequests() {
+  while (true) {
+    const {payload} = yield take('REQUEST')
+    yield fork(handleRequest, payload)
+  }
+}
+
+function* handleRequest(payload) { ... }
+

이때 REQUEST 요청을 순차적으로 첫번째부터 수행하고 싶을때는, queue를 만들어서 하나가 끝나면 다음 것을 꺼내어 수행하는 방식으로 하면 된다.

이때 redux-sage에서는 actionChannel(pattern)이라는 effect를 사용하여 구현할 수 있다. actionChannel은 saga가 API call 등으로 막혀있을때도 메세지를 받아 큐에 넣을 수 있다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+
import { take, actionChannel, call, ... } from 'redux-saga/effects'
+
+function* watchRequests() {
+  // 1- Create a channel for request actions
+  const requestChan = yield actionChannel('REQUEST')
+  while (true) {
+    // 2- take from the channel
+    const {payload} = yield take(requestChan)
+    // 3- Note that we're using a blocking call
+    yield call(handleRequest, payload)
+  }
+}
+
+function* handleRequest(payload) { ... }
+

composing sagas

watch에서 call을 yield하면 saga는 call이 끝날때까지 기다린다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
function* fetchPosts() {
+  yield put(actions.requestPosts());
+  const products = yield call(fetchApi, "/products");
+  yield put(actions.receivePosts(products));
+}
+
+function* watchFetch() {
+  while (yield take("FETCH_POSTS")) {
+    yield call(fetchPosts); // waits for the fetchPosts task to terminate
+  }
+}
+

다음과 같이 all 이펙트를 사용하여 여러 제너레이터를 동시에 수행하고, 모두 끝날때까지 기다릴 수도 있다.

1
+2
+3
+4
+
function* mainSaga(getState) {
+  const results = yield all([call(task1), call(task2), ...])
+  yield put(showResults(results))
+}
+

concurrency

takeEvery와 takeLatest가 어떻게 동시성을 제어하는가?

takeEvery

1
+2
+3
+4
+5
+6
+7
+8
+9
+
import { fork, take } from "redux-saga/effects";
+
+const takeEvery = (pattern, saga, ...args) =>
+  fork(function* () {
+    while (true) {
+      const action = yield take(pattern);
+      yield fork(saga, ...args.concat(action));
+    }
+  });
+

해당하는 pattern이 들어오면 fork를 통해 분리하여 처리한다.

takeLatest

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+
import { cancel, fork, take } from "redux-saga/effects";
+
+const takeLatest = (pattern, saga, ...args) =>
+  fork(function* () {
+    let lastTask;
+    while (true) {
+      const action = yield take(pattern);
+      if (lastTask) {
+        yield cancel(lastTask); // cancel is no-op if the task has already terminated
+      }
+      lastTask = yield fork(saga, ...args.concat(action));
+    }
+  });
+

새로운 것이 들어오면 이전에 들어온 것을 취소하고, 새로운 것을 수행한다.

Fork model

redux-saga에서는 다음 두 이펙트를 통해 fork를 다룬다

  • fork : attached fork를 만듦
  • spawn : datached fork를 만듦

attached fork

Saga는 자기 안의 모든 명령어가 수행되고, 모든 attached forks가 종료되면 종료된다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+
import { fork, call, put, delay } from "redux-saga/effects";
+import api from "./somewhere/api"; // app specific
+import { receiveData } from "./somewhere/actions"; // app specific
+
+function* fetchAll() {
+  const task1 = yield fork(fetchResource, "users");
+  const task2 = yield fork(fetchResource, "comments");
+  yield delay(1000);
+}
+
+function* fetchResource(resource) {
+  const { data } = yield call(api.fetch, resource);
+  yield put(receiveData(data));
+}
+
+function* main() {
+  yield call(fetchAll);
+}
+

따라서 위 call(fetchAll)은 delay(1000)이 끝나고, fork 된 task1, task2 가 끝나야 종료된다.

이는 아래와 같이 parallel effect와 같은 동작을 한다.

1
+2
+3
+4
+5
+6
+7
+
function* fetchAll() {
+  yield all([
+    call(fetchResource, "users"), // task1
+    call(fetchResource, "comments"), // task2,
+    delay(1000),
+  ]);
+}
+

이때 위 코드는 3개의 child effect 중 하나가 실패하면, Error가 넘어가면서 3개의 child가 전부 실패하고(완료되지 않은 작업들만), Error는 call로 fetchAll을 호출한 함수로 throw된다.

이는 fork를 이용한 모델에서도 똑같이 동작하며 아래와 같이 main에서 try/catch를 통해 에러를 받을 수 있다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
//... imports
+
+function* fetchAll() {
+  const task1 = yield fork(fetchResource, "users");
+  const task2 = yield fork(fetchResource, "comments");
+  yield delay(1000);
+}
+
+function* fetchResource(resource) {
+  const { data } = yield call(api.fetch, resource);
+  yield put(receiveData(data));
+}
+
+function* main() {
+  try {
+    yield call(fetchAll);
+  } catch (e) {
+    // handle fetchAll errors
+  }
+}
+

Cancellation

Saga를 cancel하면, 아래 것들이 cancel된다

  • saga가 현재하고 있는 effect부터 cancel됨
  • 모든 attached fork 가 cancle됨

Detached forks

detached fork는 그 자체러 하나의 수행환경이다. 따라서, 부모는 detached fork의 종료를 기다리지 않고, 에러는 상위 부모로 올라가지 않는다.

또 cancle을 해도 detached fork는 영향을 받지 않는다.

This post is licensed under CC BY 4.0 by the author.

generator 문법

css 선택자

Comments powered by Disqus.

diff --git a/posts/rumbaugh/index.html b/posts/rumbaugh/index.html new file mode 100644 index 000000000..94ba94a28 --- /dev/null +++ b/posts/rumbaugh/index.html @@ -0,0 +1 @@ + 럼바우 분석기법 | 디피의 개발일지
Posts 럼바우 분석기법
Post
Cancel

럼바우 분석기법

럼바우 분석기법

모델링 기법중 하나로 그래픽으로 표현한 분석기법

객체 모델링 기법이라고도 한다.

3단계로 구성

  1. 객체 모델링

  2. 동적 모델링
  3. 기능 모델링


1. 객체 모델링(object modeling)

객체 다이어그램을 표시함.

정보 모델링이라고도 하며, 시스템에서 요구되는 객체를 찾아내어 속성과 연산 식별 및 객체들 간의 관계를 규정하여 클래스 다이어그램으로 표현한 것


2. 동적 모델링

상태 다이어그램을 통해 시간의 흐름에 따라 객체들을 모델링한다.

객체들관계 관계에는 제어흐름, 상호작용, 동적순서 등의 관계가 있다.


3. 기능 모델링

자료흐름도를 이용하여 프로세스 간 자료를 중심으로 한 모델링

어떤 데이터를 입력 -> 어떤 결과가 나올 것인가를 표현한다.

자료 흐름도

  • 자료 흐름, 처리과정을 도형으로 작성한 다이어그램
  • 자료가 처리될 때마다 새로운 이름을 부여한다.
  • 자료 흐름도 최하위 처리는 소단위 명세서를 가짐.

자료 사전(Data Dictionary)

  • 자료흐름도의 자료를 설명하는 사전(메타데이터)



출처

https://velog.io/@kipsong/%EC%A0%95%EB%B3%B4%EC%B2%98%EB%A6%AC%EA%B8%B0%EC%82%AC-%EB%9F%BC%EB%B0%94%EC%9A%B0Rumbaugh-%EB%B6%84%EC%84%9D%EA%B8%B0%EB%B2%95

This post is licensed under CC BY 4.0 by the author.

UML

통합 테스트

Comments powered by Disqus.

diff --git a/posts/scheme/index.html b/posts/scheme/index.html new file mode 100644 index 000000000..0b4c7d7b9 --- /dev/null +++ b/posts/scheme/index.html @@ -0,0 +1 @@ + 스키마 | 디피의 개발일지
Posts 스키마
Post
Cancel

스키마

내부 스키마

데이터베이스가 실제로 저장되는 방법을 정의한 것.

개념 스키마

데이터베이스 전체의 논리적 구조를 정의한 것. 데이터베이스 하나에 하나만 존재.

개체, 관계, 제약조건, 보안정책 등을 정의한다.

외부 스키마

사용자에게 필요한 데이터베이스를 정의한 것

This post is licensed under CC BY 4.0 by the author.

블랙박스/화이트박스 테스트

응집도와 결합도

Comments powered by Disqus.

diff --git a/posts/spring-@ResponseStatus-vs-ResponseEntity/index.html b/posts/spring-@ResponseStatus-vs-ResponseEntity/index.html new file mode 100644 index 000000000..fed54176e --- /dev/null +++ b/posts/spring-@ResponseStatus-vs-ResponseEntity/index.html @@ -0,0 +1,23 @@ + ResponseStatus vs ResponseEntity | 디피의 개발일지
Posts ResponseStatus vs ResponseEntity
Post
Cancel

ResponseStatus vs ResponseEntity

@ResponseStatus

응답으로 보낼 데이터의 HttpStatus를 명시해주는 방법이다. 컨트롤러에서 Body 데이터만 반환하는 경우 HttpStatus를 명시하기 위해 사용한다.

1
+2
+3
+4
+5
+6
+
@ResponseStatus(HttpStatus.OK)
+@GetMapping("/article")
+public ArticleListDto getArticles(SelectArticlesQuery query) {
+  ArticleListDto articleListDto = articleService.getArticleList(query);
+  return articleListDto;
+}
+


ResponseEntity

스프링에선 HTTP 요청 혹은 응답을 나타내기 위해 제공하는 HttpEntity라는 클래스가 존재하며, HttpEntity는 HttpHeader와 HttpBody를 포함하는 클래스이다.

이러한 HttpEntity를 상속하여 추가적으로 HttpStatus 속성을 더 가지는 클래스가 RequestEntity, ResponseEntity 클래스이다.

제네릭으로 선언되어있으며 해당 부분은 HttpBody 타입을 나타낸다.

1
+2
+3
+4
+5
+
@GetMapping("/article")
+public ResponseEntity<ArticleListDto> getArticles(SelectArticlesQuery query) {
+  ArticleListDto articleListDto = articleService.getArticleList(query);
+  return ResponseEntity.ok(response);
+}
+


@ResponseStatus의 단점

@ResponseStatus는 단 하나의 응답 상태 코드를 지정하는 것 외에 다른 작업이 불가능하다는 것이 가장 큰 단점이다. 따라서 보통 @ResponseStatus는 에러 핸들링 컨트롤러에서 주로 사용하고, 실제 서비스를 담당하는 컨트롤러에서는 사용하기에 불편하다.

ResponseEntity의 경우 필요하다면, 컨트롤러 메서드 내부에서 오류 상태 코드를 지정하여 오류를 보낼 수 있다. 또한 Header에 대한 설정도 가능하다. 따라서 @ResponseStatus에 비해 유연성이 좋다.


출처

https://joojimin.tistory.com/54

This post is licensed under CC BY 4.0 by the author.

단일책임원칙(SRP)

빈 스코프

Comments powered by Disqus.

diff --git a/posts/spring-@Valid-vs-@Validated/index.html b/posts/spring-@Valid-vs-@Validated/index.html new file mode 100644 index 000000000..1b0ef397a --- /dev/null +++ b/posts/spring-@Valid-vs-@Validated/index.html @@ -0,0 +1,61 @@ + Valid vs Validatedated | 디피의 개발일지
Posts Valid vs Validatedated
Post
Cancel

Valid vs Validatedated

@Valid

  • JSR-303 표준 객체 제약조건 검증 어노테이션
  • ArgumentResolver에 의해 처리된다.
  • 검증에 오류가 있다면 MethodArgumentNotValidException 예외가 발생하며 디스패처 서블릿에 기본으로 등록된 예외 리졸버인 DefaultHandlerExceptionResolver에 의해 400 에러가 발생한다.
  • 스프링에서는 ArgumentResolver에 의해 처리되기에 @Valid는 기본적으로 컨트롤러에서만 동작하며 다른 계층에서는 검증되지 않는다. 다른 계층에서 파라미터를 검증하기 위해선 @Validated와 결합하여야한다.

@Validated

  • 스프링에서 제공하며, AOP기반으로 메소드의 요청을 가로채서 유효성 검증을 해준다.

  • 유효성 검증에 실패하면 ConstraintViolationException 예외가 발생한다.

  • @Validated를 클래스로 등록하고, @Valid를 파라미터에 등록하면

    • 해당 클래스에 유효성 검증을 위한 인터셉터가 등록된다. 해당 클래스의 메소드들이 호출될 때 AOP 포인트 컷으로써 요청을 가로채서 유효성 검증을 진행한다. 따라서 스프링 빈이라면 컨트롤러가 아니더라도 다른 계층에서도 동작가능하다.
  • 클래스레벨이 아닌 메소드 파라미터에 단독으로 등록하는 것 또한 가능하다.

  • @Valid와 다르게 Group Validation까지 가능하도록 구현되었다.

    • Group Validation이란 객체 유효성 검증에서, 모든 필드를 검사하지 않고 특정 그룹의 필드만 검증하도록 설정하는 것을 얘기한다.

    • 이때 그룹을 지정할 때는 그룹 인터페이스를 정의하고 필드에 그룹을 추가해야한다.

      1
      +2
      +3
      +4
      +5
      +6
      +7
      +8
      +9
      +10
      +11
      +12
      +13
      +14
      +15
      +16
      +17
      +
      public class AccountLevelManagementCommand {
      +    @NotBlank(
      +            message = "Account ID cannot be empty.",
      +            groups = {AccountLevelManagementRequestValidationGroup.class,
      +                      AccountLevelManagementSubmitValidationGroup.class})
      +    private String id;
      +
      +    @NotNull(
      +            message = "Level cannot be null.",
      +            groups = {AccountLevelManagementSubmitValidationGroup.class})
      +    private ManagerLevel levelName;
      +
      +    @NotNull(
      +            message = "Operation cannot be null.",
      +            groups = {AccountLevelManagementSubmitValidationGroup.class})
      +    private AccountLevelManagementOperation operation;
      +}
      +

      이 그룹 인터페이스는 비어있어도 좋다.

      1
      +2
      +
      public interface AccountLevelManagementRequestValidationGroup {
      +}
      +

      상속도 가능하며 이때는 자식 그룹 인터페이스를 등록하면, 부모 그룹까지 모두 등록되게 된다.

      1
      +2
      +3
      +4
      +
      public interface SubmitValidation {
      +}
      +public interface AccountLevelSubmit1 extends SubmitValidation {
      +}
      +

BindingResult

@Valid, @Validated를 사용하는 메서드에 파라미터로 주어, 유효성 검사에 실패하면 그에 대한 에러 정보를 포함하고 있는 객체이다.

BindingResult가 없다면 400 오류를 발생하면서 컨트롤러가 호출되지 않지만, 있다면 에러정보를 담고 컨트롤러를 호출한다.

1
+2
+3
+4
+5
+6
+7
+
@PostMapping("/article/create")
+public String create(@Valid ArticleCreate article, BindingResult bindingResult) {
+  if(bindingResult.hasErrors()) {
+    return "errorpage";
+  }
+ // ... 작업
+}
+

참고문서

참고문서2

This post is licensed under CC BY 4.0 by the author.

클래스 초기화 블록

ArgumentResolver 활용

Comments powered by Disqus.

diff --git a/posts/spring-AOP/index.html b/posts/spring-AOP/index.html new file mode 100644 index 000000000..3872b88d7 --- /dev/null +++ b/posts/spring-AOP/index.html @@ -0,0 +1 @@ + AOP | 디피의 개발일지
Posts AOP
Post
Cancel

AOP

AOP = Aspect Oriented Programming = 관점 지향 프로그래밍

관점 지향이란 어떤 로직을 기준으로 핵심적인 관점, 부가적인 관점으로 나누어서 보고 그 관점을 기준으로 모듈화 하겠다는 것이다. 여기서 모듈화란 어떤 공통된 로직이나 기능을 하나의 단위로 묶는 것을 말한다.

AOP에서 각 관점을 기준으로 모듈화한다는 것은 코드들을 부분적으로 나누어서 모듈화하겠다는 의미다. 개발을 하다보면 서로 다른 파일에서 같은 코드를 반복해서 쓰는 경우가 있는데 이것을 흩어진 관심사(crosscutting concerns)라고 한다. 이처럼 흩어진 관심사를 Aspect로 모듈화하고 핵심적인 비즈니스 로직에서 분리하여 재사용하겠다는 것이 AOP의 취지이다.

crosscutting concerns


AOP의 주요 개념

  • Aspect : 흩어진 관심사를 모듈화한 것. 주로 부가기능을 모듈화함 (로깅 등)
  • Target : Aspect를 적용하는 곳
  • Advice : 실질적으로 어떤 일을 해야할 지에 대한 것, 실질적인 부가기능(공통모듈)을 담은 구현체
  • JointPoint : Advice가 적용될 위치, 끼어들 수 있는 지점. 메서드 진입 지점, 생성자 호출 시점, 필드에서 값을 꺼내올 때 등 다양한 시점에 적용가능
  • PointCut : JointPoint의 상세한 스펙을 정의한 것. ‘A란 메서드의 진입 시점에 호출할 것’과 같이 더욱 구체적으로 Advice가 실행될 지점을 정할 수 있음


다양한 AOP 적용 방법

  • 컴파일
  • 바이트 코드 조작
    • 타겟을 뜯어 고쳐서 부가기능을 직접 넣어주는 직접적인 방법을 뜻한다.
    • 컴파일된 Target 클래스 파일 자체를 수정하거나 클래스가 JVM에 로딩되는 시점을 가로채 바이트코드를 조작하는 방법을 사용한다.
    • 사용 이유
      1. 스프링과 같은 DI 컨테이너의 도움을 받지 않아도 AOP 적용이 가능하다. 따라서 스프링과 같은 컨테이너가 사용되지 않는 환경에서도 쉽게 AOP 적용이 가능해진다.
      2. 프록시 패턴보다 강력하고 유연한 AOP가 가능하다
        • 프록시 패턴에서는 부가기능(공통 모듈)을 부여할 대상은 클라이언트가 호출할 때 사용하는 메소드로 제한된다.
        • 하지만 바이트 코드 조작방식은 오브젝트 생성, 필드 값 조회 및 조작, static 초기화 등 다양한 작업에 부가 기능을 명시할 수 있다.
  • 프록시 패턴(스프링 AOP에서 사용하는 방법)
    • 공통 모듈을 프록시로 만들어서 DI로 연결된 빈 사이에 적용해 Target 메소드 호출 과정에 참여하여 부가기능(공통 모듈)을 제공해줌
    • 독립적으로 개발한 부가기능 모듈을 다양한 타깃 오브젝트의 메소드에 다이내믹하게 적용해주기 위해 가장 중요한 역할을 맡고 있는 게 프록시이다.


스프링 AOP 특징

  • 프록시 패턴 기반의 AOP 구현체, 프록시 객체를 사용. 접근 제어 및 부가기능을 추가하기 위함
  • 스프링 빈에만 AOP 적용가능
  • 모든 AOP 기능을 제공하는 것이 아닌 스프링 IoC와 연동하여 애플리케이션에서 겪는 흔한 문제에 대한 해결책을 지원하는 것이 목적

spring aop


참고문서

참고문서2

This post is licensed under CC BY 4.0 by the author.

ModelAttribute와 NoArgsConstructor 추가 시 아무것도 안들어가는 이슈

API 예외처리

Comments powered by Disqus.

diff --git "a/posts/spring-API-\354\230\210\354\231\270\354\262\230\353\246\254/index.html" "b/posts/spring-API-\354\230\210\354\231\270\354\262\230\353\246\254/index.html" new file mode 100644 index 000000000..3d47e4e8c --- /dev/null +++ "b/posts/spring-API-\354\230\210\354\231\270\354\262\230\353\246\254/index.html" @@ -0,0 +1,237 @@ + API 예외처리 | 디피의 개발일지
Posts API 예외처리
Post
Cancel

API 예외처리

예외 발생 시에도 JSON 응답 보내기

ResponseEntity를 사용해서 응답을 보내면 된다.

1
+2
+3
+4
+
public ResponseEntity<String> errorPage500Api() {
+	String result = "error";
+	return new ResponseEntity(result, HttpStatus.BAD_REQUEST.value());
+}
+

ResponseEntity를 사용해서 응답하기 때문세 메시지 컨버터가 동작하면서 클라이언트에 JSON이 반환된다.


스프링부트 예외 처리

BasicErrorController

BasicErrorController은 스프링부트에서 기본으로 오류를 처리해주는 컨트롤러이다. 따로 등록한 것이 없다면 스프링부트는 예외가 컨트롤러 밖으로 예외가 던져진 경우 /error를 호출한다. 그리고 /error는 BasicErrorController로 매핑되어 아래 메서드를 호출한다.

1
+2
+3
+4
+5
+
@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
+public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {}
+
+@RequestMapping
+public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {}
+
  • errorHtml : produces=MediaType.TEXT_HTML_VALUE 으로 매핑되어, 클라이언트 요청의 Accept 헤더 값이 text/html인 경우 호출하여 에러 view를 제공한다.
  • error : 그 외의 경우 호출되며 ResponseEntity로 HTTP Body에 JSON 데이터를 반환한다.

error 발생 시 스프링부트가 ResponeEntity body에 넣어주는 정보는 다음과 같다

  • timestamp : 발생 시각
  • path : 클라이언트 요청 경로
  • status : 에러 상태
  • message : 에러 메세지
  • error : 에러 코드
  • exception : 발생한 예외
  • errors : 발생한 에러
  • trace : 예외 trace

application.properties에서 다음과 같이 응답에 정보를 담을지 말지 선택할 수 있다.

  • server.error.include-exception
  • server.error.include-message
  • server.error.include-stacktrace
  • server.error.include-binding-errors

BasicErrorController은 HTML 에러 페이지지를 제공할 때는 매우 편리하다. 하지만 API 오류를 내보낼 때는 컨트롤러마다 다른 오류 처리 방법에 대응하기가 어렵다. 따라서 API 오류를 처리할 때는 @ExceptionHandler를 사용하는 것이 좋다.


HandlerExceptionResolver

스프링 MVC는 컨트롤러 밖으로 예외가 던져진 경우, 이 예외를 잡고 처리하는 HandlerExceptionResolver를 제공해준다.

image-20221103143127595

이때 HandlerExceptionResolver는 예외를 해결해도 postHandle()은 호출되지 않는다.

HandlerExceptionResolver 인터페이스

1
+2
+3
+4
+
public interface HandlerExceptionResolver {
+	ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response,
+		Object handler, Exception ex);
+}
+
  • resolveException이 ModelAndView를 반환하는 이유는 Exception을 처리해서 정상 흐름처럼 변경하는 것이 목적이기 때문이다.

HandlerExceptionResolver 구현

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+
@Slf4j
+public class MyHandlerExceptionResolver implements HandlerExceptionResolver {
+  @Override
+  public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
+    try {
+      if (ex instanceof IllegalArgumentException) {
+        response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage());
+        return new ModelAndView();
+      }
+    } catch (IOException e) {
+      log.error("resolver ex", e);
+    }
+    return null;
+  }
+}
+

resolveException 반환 값에 따른 동작방식

  • 빈 ModelAndVIew : new ModelAndView()를 반환하는 경우. 뷰를 렌더링하지 않고, 정상 흐름으로 서블릿이 리턴된다. 즉, API 오류 처리를 할 때 사용한다.
  • ModelAndVIew 지정 : ModelAndVIew에 View, Model 등의 정보를 지정해서 반환하면 뷰를 렌더링한다.
  • null : 다음 ExceptionResolver를 찾아서 실행한다. 다음이 없으면 예외처리가 안되고 기존에 발생한 예외를 서블릿 밖으로 던진다.

ExceptionResolver 활용

  • 예외 상태 코드 변환
    • 예외를 response.sendError() 호출로 변경해서 서블릿에서 상태코드에 따른 오류를 처리하도록 위임
    • 이후 WAS는 서블릿 오류 페이지를 찾아서 내부 호출.
  • 뷰 템플릿 처리
    • ModelAndView에 값을 채워서 예외에 따른 새로운 오류화면 뷰 렌더링해서 고객에게 제공
  • API 응답처리
    • response.getWriter().println() 처럼 HTTP응답 바디에 직접 데이터를 넣어주는 것도 가능. 여기에 JSON으로 응답하면 API 응답 처리를 할 수 있다.

ExceptionResolver 등록

  • WebMvcConfigurer의 extendHandlerExceptionResolvers를 통해 다음과 같이 등록하면 된다.
1
+2
+3
+4
+
@Override
+public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
+	resolvers.add(new MyHandlerExceptionResolver());
+}
+
  • configureHandlerExceptionResolvers 로도 등록이 되지만, 스프링이 기본으로 등록하는 ExceptionResolver가 제거되므로 주의한다. extendHandlerExceptionResolvers는 확장.


스프링이 제공하는 ExceptionResolver

스프링 부트가 기본으로 제공하는 ExceptionResolver와 우선순위는 다음과 같다.(HandlerExceptionResolverComposite에 다음과 같이 등록)

  1. ExceptionHandlerExceptionResolver
    • @ExceptionHandler를 처리한다.
  2. ResponseStatusExceptionResolver
    • HTTP 상태 코드를 지정해준다.
  3. DefaultHandlerExceptionResolver
    • 스프링 내부 기본 예외를 처리한다.


ExceptionHandlerExceptionResolver

@ExceptionHandler 애노테이션을 사용한 예외처리 방법.

컨트롤러 안에서 @ExceptionHandler 와 처리하고 싶은 예외를 선언해주면 된다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+
@RestController
+public class ApiExceptionV2Controller
+    @ResponseStatus(HttpStatus.BAD_REQUEST)
+    @ExceptionHandler(IllegalArgumentException.class)
+    public ErrorResult illegalExHandle(IllegalArgumentException e) {
+        return new ErrorResult("BAD", e.getMessage());	//  @ResponseBody 적용됨
+    }
+
+    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
+    @ExceptionHandler
+    public ResponseEntity<ErrorResult> exHandle(Exception e) {
+      ErrorResult errorResult = new ErrorResult("EX", "내부 오류");
+      return new ResponseEntity<>(errorResult, HttpStatus.BAD_REQUEST);
+    }
+  	...
+}
+
+

예외가 발생하면 컨트롤러 안의 @ExceptionHandler 애노테이션이 붙은 메서드를 확인하고 예외를 처리한다. 지정한 예외와 그 예외의 자식 클래스는 모두 잡을 수 있다.

부모의 자식 예외가 모두 등록되어있을 때는, 자식 예외 처리가 먼저 호출된다.

다음과 같이 다양한 예외를 한번에 등록할 수도 있다.

1
+
@ExceptionHandler({AException.class, BException.class})
+

다음과 같이 생략할 경우에는 메서드 파라미터의 예외로 지정된다.

1
+2
+
@ExceptionHandler
+public ResponseEntity<ErrorResult> userExHandle(UserException e) {}
+


ResponseStatusExceptionResolver

예외에 따라서 HTTP 상태 코드를 지정해주는 역할을 한다. 다음 두 가지 경우를 처리한다.

  • @ResponseStatus가 달려있는 예외
  • ResponseStatusException 예외

@ResponseStatus가 달려있는 예외

다음과 같이 예외에 @ResponseStatus 애노테이션을 적용하면 HTTP 상태코드를 변경해준다.

1
+2
+3
+
@ResponseStatus(code = HttpStatus.NOT_FOUND, reason = "찾을 수 없음")
+public class NotFoundException extends RuntimeException {
+}
+

NotFoundException 커스텀 예외가 컨트롤러 밖으로 넘어가면 ResponseStatusExceptionResolver가 해당 애노테이션을 확인해서 오류 코드를 404로 변경하고 메시지를 담는다.

reason에는 MessageSource를 찾는 기능도 제공한다.

1
+
@ResponseStatus(code = HttpStatus.NOT_FOUND, reason = "error.notfound")
+
1
+2
+
# messages.properties
+error.notfound=찾을 수 없습니다.
+

ResponseStatusException

@ResponseStatus는 개발자가 직접 변경할 수 없는 예외에는 적용할 수 없다. 이때는 ResponseStatusException를 사용하면 된다.`

1
+2
+3
+4
+
@GetMapping("/api/response-status-ex2")
+public String responseStatusEx2() {
+	throw new ResponseStatusException(HttpStatus.NOT_FOUND, "error.notfound", new Exception());
+}
+


DefaultHandlerExceptionResolver

스프링 내부에서 발생하는 스프링 예외를 해결한다.

  • ex) 파라미터 바인딩 실패시 발생하는 TypeMismatchException 예외를 처리하여 400으로 응답


@ControllerAdvice

@ExceptionHandler를 컨트롤러 안에서 사용하여 예외를 잡을 수 있지만, 컨트롤러 내의 정상 코드와 예외 처리 코드가 섞여있다. 이를 분리하기 위해선 @ControllerAdvice 또는 @RestControllerAdvice를 사용하면 둘을 분리할 수 있다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+
@RestControllerAdvice
+public class ExControllerAdvice {
+      @ResponseStatus(HttpStatus.BAD_REQUEST)
+      @ExceptionHandler(IllegalArgumentException.class)
+      public ErrorResult illegalExHandle(IllegalArgumentException e) {
+          return new ErrorResult("BAD", e.getMessage());
+      }
+      ...
+}
+
+

@ControllerAdvice는 대상으로 지정한 여러 컨트롤러에 @ExceptionHandler, @InitBinder 기능을 부여해주는 역할을 한다. 대상을 지정하지 않으면 모든 컨트롤러에 적용한다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
@ControllerAdvice(annotations = RestController.class)
+public class ExampleAdvice1 {}
+
+
+// Target all Controllers within specific packages
+@ControllerAdvice("org.example.controllers")
+public class ExampleAdvice2 {}
+
+// Target all Controllers assignable to specific classes
+@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})
+public class ExampleAdvice3 {}
+

@RestControllerAdvice는 @ControllerAdvice와 같은 역할을 하며, 내부의 메서드에 @ResponseBody 애노테이션을 추가시켜준다.


@ExceptionHandler, @ControllerAdvice 원리

@ControllerAdvice에서 Interceptor에서 날아온 에러도 잡는 것을 보고 원리가 신경 쓰여 DispatcherServletdoDispatch() 메서드 코드를 살펴봤다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
+  ...
+  try {
+    ...
+      try {
+        ...
+        // Determine handler for the current request.
+        mappedHandler = getHandler(processedRequest);
+
+        // Determine handler adapter for the current request.
+        HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
+        
+        ...
+          
+        if (!mappedHandler.applyPreHandle(processedRequest, response)) {
+          return;
+        }
+
+        // Actually invoke the handler.
+        mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
+        
+        ...
+          
+        mappedHandler.applyPostHandle(processedRequest, response, mv);
+      }
+      catch (Exception ex) {
+        dispatchException = ex;
+      }
+      catch (Throwable err) {
+        dispatchException = new NestedServletException("Handler dispatch failed", err);
+      }
+      processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
+    }
+  	...
+}
+
  • 핸들러(컨트롤러)와 인터셉터를 실행 중 예외가 발생하면 dispatchException에 예외를 저장하고, processDispatchResult로 넘겨준다.
  • 위 코드에는 나오지 않지만, processDispatchResult에서는 예외가 있고, ModelAndViewDefiningException이 아닌 경우 processHandlerException를 호출한다.
    • ModelAndViewDefiningException은 예외 페이지를 보여주도록하는 예외
  • processHandlerException에서는 DispatcherServlet에 등록된 HandlerExceptionResolver 을 하나씩 수행한다.
  • HandlerExceptionResolver은 위에도 나와있지만 디폴트로 다음 세가지가 등록된다.
    • ExceptionHandlerExceptionResolver
    • ResponseStatusExceptionResolver
    • DefaultHandlerExceptionResolver
  • 세 가지 Resolver 중 ExceptionHandlerExceptionResolver가 ExceptionHandler를 실행한다.
  • @ControllerAdvice로 ExceptionHandler를 등록하면, 디폴트로 모든 컨트롤러에 등록된다. 따라서 interceptor에서 날린 예외도 받을 수 있는 것이다.
  • 서블릿 필터의 경우는 디스패처 서블릿 이전에 수행되기에, ExceptionHandler로 받지 않는다.

More..

  • DispathcerServlet의 코드를 보면 컨트롤러 안에서 선언한 @ExceptionHandler 메서드도 인터셉터에서 날린 예외를 받을 수 있는 걸로 보인다.
    • 그리고 실제로 실험을 해보니 인터셉터의 preHandle에서 날린 예외를 컨트롤러에서 선언한 @ExceptionHandler에서 처리하였다.
  • 인터셉터의 afterCompletionprocessDispatchResult에서 실행되는데 따라서 afterCompletion에서 예외를 날려도 @ControllerAdvise@ExceptionHandler는 받지 못한다.
    • 실험 결과, 실제로 ExceptionHandler에서는 처리하지 못하였지만 응답은 제대로 왔다. 대신 콘솔에 오류가 기록되었다. afterCompletion에서 발생한 예외는 스프링 내에서 처리하는 것이다.
  • processHandlerException에서 예외 처리 이후에도 afterCompletion은 실행된다.


정리

HTML 에러 화면을 보여줘야하는 경우

  • BasicErrorController 사용

API 에러 응답을 보내야하는 경우

  • ExceptionHandlerExceptionResolver (@ExceptionHandler) 사용
  • @ControllerAdvise는 예외처리 코드와 정상코드를 분리시키고자 할 때 사용


출처

This post is licensed under CC BY 4.0 by the author.

AOP

사이드카 패턴

Comments powered by Disqus.

diff --git "a/posts/spring-ArgumentResolver-\355\231\234\354\232\251/index.html" "b/posts/spring-ArgumentResolver-\355\231\234\354\232\251/index.html" new file mode 100644 index 000000000..dbaacb562 --- /dev/null +++ "b/posts/spring-ArgumentResolver-\355\231\234\354\232\251/index.html" @@ -0,0 +1,67 @@ + ArgumentResolver 활용 | 디피의 개발일지
Posts ArgumentResolver 활용
Post
Cancel

ArgumentResolver 활용

ArgumentResolver 활용

ArgumentResolver는 스프링 MVC 구조의 어댑터 핸들러에서 핸들러에 필요한 파라미터를 만들어주는데 호출하는 부분이다. 이 ArgumentResolver를 직접 구현하여 활용하면 다양한 상황에서 편리하게 적용할 수 있다.

로그인 회원 정보를 편리하게 받아오는 예제

컨트롤러

1
+2
+
@GetMapping("/")
+public String homeLoginV3ArgumentResolver(@Login Member loginMember, Model model)
+

Login 어노테이션

1
+2
+3
+4
+
@Target(ElementType.PARAMETER)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Login {
+}
+
  • @Target(ElementType.PARAMETER) : 파라미터에만 적용
  • @Retention(RetentionPolicy.RUNTIME) : 리플렉션 등을 활용할 수 있도록 런타임까지 애노테이션 정보가 남아있음.

LoginMemberArgumentResolver

HandlerMethodArgumentResolver를 구현하여 만든다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
public class LoginMemberArgumentResolver implements HandlerMethodArgumentResolver {
+  @Override
+	public boolean supportsParameter(MethodParameter parameter) {
+    boolean hasLoginAnnotation = parameter.hasParameterAnnotation(Login.class);
+    boolean hasMemberType = Member.class.isAssignableFrom(parameter.getParameterType());
+    return hasLoginAnnotation && hasMemberType;
+  }
+
+  @Override
+  public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
+                                NativeWebRequest webRequest,
+                                WebDataBinderFactory binderFactory) throws Exception {
+    HttpServletRequest request = (HttpServletRequest)webRequest.getNativeRequest();
+    HttpSession session = request.getSession(false);
+    if (session == null) {
+      return null;
+    }
+    return session.getAttribute(SessionConst.LOGIN_MEMBER);
+  }
+}
+
  • supportsParameter : @Login 애노테이션이 있으면서 Member 타입이면 해당 ArgumentResolver가 사용되도록 적용
  • resolveArgument : 컨트롤러 호출 직전에 호출되어 필요한 파라미터 정보를 생성해준다.

WebMvcConfigurer에 설정 추가

1
+2
+3
+4
+5
+6
+7
+
@Configuration
+public class WebConfig implements WebMvcConfigurer {
+  @Override
+  public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
+  resolvers.add(new LoginMemberArgumentResolver());
+  }
+}
+

출처

This post is licensed under CC BY 4.0 by the author.

Valid vs Validatedated

controller에서 enum을 인자로 받기

Comments powered by Disqus.

diff --git "a/posts/spring-DI\353\212\224-IOC\353\245\274-\355\225\204\354\232\224\353\241\234\355\225\230\354\247\200-\354\225\212\353\212\224\353\213\244(\354\240\225\353\246\254)/index.html" "b/posts/spring-DI\353\212\224-IOC\353\245\274-\355\225\204\354\232\224\353\241\234\355\225\230\354\247\200-\354\225\212\353\212\224\353\213\244(\354\240\225\353\246\254)/index.html" new file mode 100644 index 000000000..4589e5a95 --- /dev/null +++ "b/posts/spring-DI\353\212\224-IOC\353\245\274-\355\225\204\354\232\224\353\241\234\355\225\230\354\247\200-\354\225\212\353\212\224\353\213\244(\354\240\225\353\246\254)/index.html" @@ -0,0 +1,113 @@ + Pure DI - IoC가 없는 DI | 디피의 개발일지
Posts Pure DI - IoC가 없는 DI
Post
Cancel

Pure DI - IoC가 없는 DI

이 포스트는 참고문서를 필자가 이해하기 쉽게 정리한 내용입니다.

DI는 IoC를 사용하지 않아도 된다

DI(Dependency Injection)

다음 PizzaStore 클래스는 정해진 개수의 Pizza인스턴스를 가지고 있다. 만약 Pizza가 더 필요하면 PizzaStore를 직접 수정해야하다. 이는 OCP를 위반하며 DI로 해결해줄 수 있다

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+
public class PizzaStore
+{
+    private readonly Pizza[] pizzas =
+        new Pizza[]
+        {
+            new Pizza(),
+            new Pizza(),
+            new Pizza()
+        };
+
+    public void Sell(int count)
+    {
+

다음과 같이 인수를 취하는 형태의 DI로 해결 가능하다

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
public class PizzaStore
+{
+    private readonly Pizza[] pizzas;
+
+    public PizzaStore(Pizza[] pizzas)
+    {
+        this.pizzas = pizzas;
+    }
+
+    public void Sell(int count)
+    {
+

이처럼 DI는 추상화를 해치지 않고 의존성을 인수로 넘겨주는 방법을 뜻한다.

Rúnar Bjarnason : Dependency Injection is really just a pretentious way to say ‘taking an argument’.

DIP(Dependency Inversion Principle)

고차원 모듈은 저차원 모듈에 의존해서는 안된다. 이 둘은 추상화된 것에 의존해야한다.

추상화된 것은 구체적인 것에 의존하면 안된다.

PizzaStore 클래스는 현재 Pizza 구현체에 의존하고 있다. 이 형태는 DIP를 위반하고 있는 것으로, 다음과 같이 변경되어야한다.

dip위반

dip 지킴

DI는 DIP를 사용한다고 생각하지만 이 둘은 별개다. DI를 사용하면서 DIP가 필요없는 경우도 많다. 아래 CachedUserStore 클래스에서는 일정기간(duration) 동안 캐쉬하는 기능을 제공한다. 이때 duration은 DI를 통해 주입되지만 DIP가 필요한 것은 아니다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+
public interface IUserStore
+{
+    User Find(string userId);
+}
+
+public class CachedUserStore : IUserStore
+{
+    ...
+
+    public CachedUserStore(IUserStore innerStore, int duration)
+    {
+        ...
+    }
+
+    public User Find(string userId)
+    {
+


Inversion of Control

IoC는 라이브러리와 프레임워크의 차이에서 알아볼 수 있다.

라이브러리 : 개발자가 원하는 때에 가져와서 사용할 수 있음. 개발자에게 제어권이 있다

프레임워크 : 개발자가 프레임워크에 맞추어 구성요소를 등록하면 프레임워크가 프로그램을 동작시킴. 제어권이 프레임워크에 있다.

다음과 같이 IoC 컨테이너가 의존성을 주입해준다면 이는 제어권이 IoC 컨테이너에 있는 것으로 IoC가 발생한 사레이다

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+
public static void Main()
+{
+    Type[] pizzaTypes = Assembly.GetEntryAssembly()
+        .GetExportedTypes()
+        .Where(typeof(IPizza).IsAssignableFrom);
+
+    var builder = new IoCContainerBuilder();
+    buildr.RegisterTypes(pizzaTypes);
+
+    IoCContainer container = builder.Build();
+
+    IPizza[] allKindsOfPizzas = container.Resolve<IPizza[]>();
+
+    var pizzaStore = new PizzaStore(allKindsOfPizzas);
+
+    ...
+}
+


Pure DI

Mark Seemann은 IoC 컨테이너를 사용하지 않는 DI에 대해 Pure DI라고 했다

DI를 사용한다고 무조건 IoC 컨테이너가 필요한 것은 아니다. 보통은 의존성 등록비용(개발자가 직접 의존성을 주입하는 것) 때문에 IoC 컨테이너로 의존성 주입하지만, IoC 컨테이너를 사용하는 방법에는 Weakly typed 문제가 있다.

Weakly typed 란, IoC 컨테이너에 의존성을 잘못 구성되었을 경우 컴파일 에러대신 런타임 에러가 발생하는 것이다.

따라서 Weakly typed 비용과 의존성 등록 비용 중 어떤 것이 더 큰지 생각하여 IoC 컨테이너로 DI를 구현할지 Pure DI를 사용할지 결정해야한다. (참고문서의 필자는 weakly typed 비용이 더 크다고 함)

중요한 것은 뭘 사용해야할지보다 IoC 컨테이너 없이 Pure DI를 사용할 수 있고, Pure DI에도 장점이 있다는 것이다.

요약

DI는 어떠한 고차원 모듈이 저차원 모듈에 의존해서는 안된다는 DIP와 변경에는 닫혀있고 확장에는 열려있어야하는 OCP를 지키기 위해, 외부에서 의존하는 모듈을 주입해주는 것이다. 보통 DI는 IoC 컨테이너로 주입해주지만, weakly typed 문제가 발생할 수 있다. 따라서 직접 의존성 등록할 때 발생하는 비용과 weakly typed 비용을 잘 비교하여 사용하면 좋다.

참고문서

This post is licensed under CC BY 4.0 by the author.

converter

HTTP 메시지 컨버터

Comments powered by Disqus.

diff --git "a/posts/spring-HTTP-\353\251\224\354\213\234\354\247\200-\354\273\250\353\262\204\355\204\260/index.html" "b/posts/spring-HTTP-\353\251\224\354\213\234\354\247\200-\354\273\250\353\262\204\355\204\260/index.html" new file mode 100644 index 000000000..edcfb6fbb --- /dev/null +++ "b/posts/spring-HTTP-\353\251\224\354\213\234\354\247\200-\354\273\250\353\262\204\355\204\260/index.html" @@ -0,0 +1,5 @@ + HTTP 메시지 컨버터 | 디피의 개발일지
Posts HTTP 메시지 컨버터
Post
Cancel

HTTP 메시지 컨버터

스프링에서 컨트롤러를 개발하다보면, url 파라미터를 long 으로 받아도 문제없이 작동된다.

1
+2
+
@GetMapping(path = "/article/{articleId}")
+public String getArticleDetail(@PathVariable long articleId) {
+

위와 같은 컨트롤러가 있을 때, 유저는 /article/123라는 요청을 보내면 자동으로 getArticleDetail의 매개변수인 articleId123 값이 문제없이 할당된다.

URL 파라미터는 기본적으로 문자열로 인식다. 하지만 long에 할당이 일어나는 것은 무언가가 중간에서 문자열 123을 long 타입 123으로 변환시켜주는 것이다. 그 무언가가 스프링 MVC에서는 HTTP 메시지 컨버터이다.

스프링 MVC는 다음의 경우에 HTTP 메시지 컨버터를 적용한다

HTTP 요청 : @RequestBody, HttpEntity(RequestEntity)

HTTP 응답 : @ResponseBody, HttpEntity(ResponseEntity)

요청 매핑

HttpMessageConverter는 HTTP 메시지 컨버터의 인터페이스로 다음과 같은 메서드를 포함한다

  • canRead(), canWrite() : 메시지 컨버터가 해당 클래스, 미디어타입을 지원하는지 체크
  • read(), write() : 메시지 컨버터를 통해서 메시지를 읽고 쓰는 기능

스프링 부트는 다양한 메시지 컨버터를 제공하는데 다음과 같은 메시지 컨버터가 주요하며 적용 우선순위는 나열 순서와 같다

  1. ByteArrayHttpMessageConverter : byte[] 데이터를 처리한다
    • 클래스 타입 : byte[] / 미디어타입 : */*
  2. StringHttpMessageConverter : String 데이터를 처리한다
    • 클래스타입 : String, 미디어타입 : */*
  3. MappingJackson2HttpMessageConverter : application/json 바디처리
    • 클래스타입 : 객체 또는 HashMap. 미디어타입 : application/json


동작순서

메시지 컨버터가 동작하는 순서는 다음과 같다.

HTTP 요청

  1. HTTP 요청이 오고, 컨트롤러에서 @RequestBody, HttpEntity 파라미터를 사용한다
  2. 메시지 컨버터가 canRead()를 호출하여 메시지를 읽을 수 있는지 확인한다.
    • 대상 클래스 타입을 지원하는가, HTTP 요청의 Content-type 미디어 타입을 지원하는가
  3. 읽을 수 있으면 read()를 호출해서 객체를 생성하고 반환한다.

HTTP 응답

  1. 컨트롤러에서 @ResponseBody, HttpEntity로 값이 반환된다.
  2. 메시지컨버터가 메시지를 쓸 수 있는지 확인하기 위해 canWrite()를 호출한다.
    • 대상 클래스 타입을 지원하는가, HTTP 요청의 Accept 미디어 타입을 지원하는가
  3. 쓸 수 있으면 write()를 호출해서 HTTP 응답 메시지 바디에 데이터를 생성한다.


Argument Resolver, ReturnValueHandler

Http 메시지 컨버터는 언제 사용될까? 이것을 알아보기저에 먼저 Argument ResolverReturnValueHandler를 알아보자.

애노테이션 기반의 컨트롤러(@RequestMapping을 사용하는 컨트롤러)를 처리하는 핸들러 어댑터는 RequestMappingHandlerAdapter이다. 이 RequestMappingHandlerAdapter는 핸들러 사이에 Argument ResolverReturnValueHandler를 다음 그림과 같이 둔다.

RequestMappingHandlerAdapter

ArgumentResolver

애노테이션 기반의 컨트롤러는 매우 다양한 파라미터를 사용할 수 있다. HttpServletRequest, Model, @RequestPaaram, @ModelAttribute 등과 같은 애노테이션과 @RequestBody, HttpEntity와 같은 HTTP 메시지도 처리해준다. 이렇게 유연하게 처리해주는 것이 바로 ArgumentResolver이다.

애노테이션 기반 컨트롤러를 처리하는 RequestMappingHandlerAdapter는 이 ArgumentResolver를 호출하여 컨트롤러가 필요로하는 다양한 파라미터의 값을 생성한다.

스프링은 30개가 넘는 ArgumentResolver를 제공한다. 이 ArgumentResolver를 통해 각 파라미터를 처리하는 것이다.

가능한 파라미터 목록

ReturnValueHandler

ArgumentResolver와 마찬가지로 컨트롤러에서 반환되는 다양한 값을 변환하여 ModelAndView 객체로 만들어 RequestMappingHandlerAdapter에게 전달하는 역할을 맡는다.

가능한 응답값 목록


HTTP 메시지 컨버터

위와 같은 구조에서 Http 메시지 컨버터는 Argumen Resolver와 ReturnValueHandler가 호출될때 사용된다.

HTTP 메시지 컨버터 위치

요청

  • @RequestBody를 처리하는 ArgumentResolver와 HttpEntity를 처리하는 ArgumentResolver가 있다.
  • 이 ArgumentResolver들이 HTTP 메시지 컨버터를 사용해서 필요한 객체를 생성한다.

응답

  • @ResponseBody와 HttpEntity를 처리하는 ReturnValueHandler가 있다.
  • 이 ReturnValueHandler가 HTTP 메시지 컨버터를 사용하여 응답 결과를 만든다.
This post is licensed under CC BY 4.0 by the author.

Pure DI - IoC가 없는 DI

interceptor

Comments powered by Disqus.

diff --git a/posts/spring-Interceptor/index.html b/posts/spring-Interceptor/index.html new file mode 100644 index 000000000..e852fb64e --- /dev/null +++ b/posts/spring-Interceptor/index.html @@ -0,0 +1,67 @@ + interceptor | 디피의 개발일지
Posts interceptor
Post
Cancel

interceptor

스프링 인터셉터는 스프링 MVC가 제공하는 기술로, 서블릿 필터하고는 적용순서, 범위, 사용방법이 다르다.


소개

스프링 인터셉터 흐름

HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 스프링 인터셉터 -> 컨트롤러

  • 스프링 인터셉터는 디스패처 서블릿과 컨트롤러 사이에서 컨트롤러 호출 직전에 호출됨.

  • 스프링 인터셉터는 스프링 MVC가 제공하는 기능으로, 디스패처 서블릿 이후에 등장하게 된다. 스프링 MVC의 시작점이 디스패처 서블릿이라고 생각하면 됨.

  • 스프링 인터셉터도 URL 패턴을 적용할 수 있다. 서블릿 URL 패턴과는 다르고 매우 정밀하게 설정가능

  • 스프링 인터셉터에서 적절하지 않은 요청이라고 판단하면 거기서 요청을 차단할 수 있다. 컨트롤러까지 가지 않도록 한다.

  • 다음과 같이 체인을 구성할 수도 있다.

    서블릿 -> 인터셉터1 -> 인터셉터2 -> 컨트롤러

  • 인터셉터는 싱글톤처럼 사용된다. 따라서 멤버변수를 사용하여 메소드간 값을 공유하면 위험하다.

스프링 인터셉터 인터페이스

스프링 인터셉터를 사용하려면 HandlerInterceptor 인터페이스를 구현하면 된다. 해당 인터페이스에는 다음과 같이 세가지 메서드가 있다

  • preHandle : 컨트롤러 호출 전에 호출됨. true 반환 시 다음으로 진행, false 반환 시 컨트롤러 호출하지 않음.
  • postHandle : 컨트롤러 호출 후에 호출된다. preHandle에서 false 반환 시 호출되지 않음. 예외가 발생했을 때에도 호출되지 않음
  • afterCompletion : 뷰가 렌더링 된 다음에 호출됨. preHandle에서 false 가 반환되더라도 항상 호출됨.
    • afterCompletion은 예외가 발생해도 호출된다. 따라서 공통 처리를 할 때는 afterCompletion을 사용한다.
    • 예외가 발생했을 때는 예외 정보(ex)를 포함해서 호출된다. (파라미터로 받음)

스프링 인터셉터의 메서드에서는 request, response, handler 정보를 받을 수 있다. handler는 호출되는 컨트롤러를 뜻한다.

인터셉터 등록

인터셉터는 WebMvcConfigurer에 다음과 같이 등록해줘야한다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+
@Configuration
+public class WebConfig implements WebMvcConfigurer {
+      @Override
+      public void addInterceptors(InterceptorRegistry registry) {
+          registry.addInterceptor(new LogInterceptor())
+                  .order(1)
+                  .addPathPatterns("/**")
+                  .excludePathPatterns("/css/**", "/*.ico", "/error");
+      }
+}
+
  • order : 인터셉터의 호출 순서를 지정. 낮을수록 먼저 호출된다.
  • addPathPatterns ; 인터셉터를 적용할 URL 패턴을 지정한다
  • excludePathPatterns : 인터셉터에서 제외할 패턴을 지정한다.

PathPatterns

스프링이 제공하는 URL 경로 기술로, 매우 자세하게 설정할 수 있다.

path patterns 공식문서


interceptor에서 controller로 값 전달하기

다음과 같이 전달할 값을 필드로 지닌 클래스를 만들고, request 스코프로 빈에 등록하는 것이 가장 좋다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+
@Component
+@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
+public class CurrentUser {
+    private User currentUser;
+
+    public User getCurrentUser() {
+        return currentUser;
+    }
+
+    public void setCurrentUser(User currentUser) {
+        this.currentUser = currentUser;
+    }
+}
+

alternative : HttpServletRequest에 추가

필드가 딱 하나만 있거나, 컨트롤러에서도 HttpServletRequest를 사용한다면 다음과 같이 HttpServletRequest에 값을 추가하는 것도 방법이다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+
@Component
+public class MyInterceptor implements HandlerInterceptor {
+   @Override
+   public boolean preHandle(
+     HttpServletRequest request,
+     HttpServletResponse response, Object handler) throws Exception {
+      request.setAttribute("email", "login@domain.com");
+      return true;
+   }
+}
+

참고


ExceptionHandler와의 조합

interceptor에서 날아간 예외는 어떻게 처리하는 것이 좋을까?

이에 관해선 API 예외처리 문서의 @ExceptionHandler, @ControllerAdvice 원리 섹션에서 자세히 정리해놓았지만, 간단히 요약하자면 아래와 같다.

  • interceptor의 preHandle와 postHandle에서 던진 예외는 ExceptionHandler에서 잡을 수 있다.
    • @ExceptionHandler로 선언한 핸들러와 @ControllerAdvice로 등록한 핸들러 모두 적용됨
  • afterCompletion에서 던진 예외 ExceptionHandler에서 처리되지 않고 스프링에서 처리된다. 다만 이때 클라이언트 요청의 응답은 제대로 가고, 스프링 내부에서 예외가 처리된다.


참고

This post is licensed under CC BY 4.0 by the author.

HTTP 메시지 컨버터

InvalidDefinitionException

Comments powered by Disqus.

diff --git a/posts/spring-InvalidDefinitionException/index.html b/posts/spring-InvalidDefinitionException/index.html new file mode 100644 index 000000000..7bed46989 --- /dev/null +++ b/posts/spring-InvalidDefinitionException/index.html @@ -0,0 +1,13 @@ + InvalidDefinitionException | 디피의 개발일지
Posts InvalidDefinitionException
Post
Cancel

InvalidDefinitionException

@ResquestBody로 받는 매개변수의 클래스에 @NoArgsConstructor를 빼니 InvalidDefinitionException 에러가 발생했다.

1
+2
+
@PostMapping("/login")
+public String login(@Valid @RequestBody LoginRequestBody loginRequestBody)
+
1
+2
+3
+4
+
@Getter
+@AllArgsConstructor
+@ToString
+public class LoginRequestBody {
+

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of com.ntscorp.springserver.user.dto.controller.request.LoginRequestBody (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)

반면에 @NoArgsConstructor를 추가하니 제대로 controller가 실행되었다.

그 이유는 해당 포스트에서 자세히 설명되어있다

  1. spring에서는 Http 메시지 바디를 읽기위해 HttpMessageConverter를 사용한다.
  2. application/json을 읽기위해 사용되는 컨버터는 Jackson2HttpMessageConverter다.
  3. Jackson2HttpMessageConverter의 read() 에서는 ObjectMapper가 사용된다.
  4. ObjectMapper의 변환과정은 다음과 같다
    1. Object 생성 : 대부분 기본 생성자 이용
    2. Object 필드 인식 : setter 또는 getter 사용
    3. Object 필드에 값 넣기 : Reflection 이용 (setter 이용 x)

위 과정 중 4-1번에서 기본 생성자를 사용하는 과정이 있으므로 기본생성자가 없을 경우 예외가 발생한 것이었다.

물론 이 포스트에서 설명한 것과 같이 기본 생성자없이도 동작가능하도록 할 수 있으나, 웬만하면 기본 설정을 사용하는 것이 나아보인다.

This post is licensed under CC BY 4.0 by the author.

interceptor

mybatis dynamic field

Comments powered by Disqus.

diff --git "a/posts/spring-Json-\354\235\221\353\213\265-\354\213\234-\355\212\271\354\240\225-\355\225\204\353\223\234-\353\271\274\352\263\240-\353\263\264\353\202\264\352\270\260/index.html" "b/posts/spring-Json-\354\235\221\353\213\265-\354\213\234-\355\212\271\354\240\225-\355\225\204\353\223\234-\353\271\274\352\263\240-\353\263\264\353\202\264\352\270\260/index.html" new file mode 100644 index 000000000..acb8c7a5e --- /dev/null +++ "b/posts/spring-Json-\354\235\221\353\213\265-\354\213\234-\355\212\271\354\240\225-\355\225\204\353\223\234-\353\271\274\352\263\240-\353\263\264\353\202\264\352\270\260/index.html" @@ -0,0 +1,57 @@ + json 응답 시 특정 필드 빼고 보내기 | 디피의 개발일지
Posts json 응답 시 특정 필드 빼고 보내기
Post
Cancel

json 응답 시 특정 필드 빼고 보내기

스프링에서는 Http 메시지 컨버터를 통해 사용자가 보낸 데이터와 서버에서 내보내는 데이터를 자동으로 변환해준다. 만약 다음과 같은 객체를 응답으로 보낼 경우 Content-Typeapplication/json이라면, 스프링은 다음 객체를 자동으로 Json 형식으로 바꾸어 내보낸다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+
@AllArgsConstructor
+@Getter
+public class UserDto {
+  private String userId;
+  private String password;
+}
+
+//
+  
+UserDto user = new UserDto("userId", "password");
+
1
+2
+3
+4
+
{
+  "userId":"userid",
+  "password":"password"
+}
+

그런데 만약 예시에서 password는 민감한 데이터이므로 응답으로 보내고 싶지 않다면 어떻게 하면 될까? 응답을 보낼 때 password 필드의 값을 null로 비워서 보낼 수도 있겠지만, 개발자가 실수로 비우지 않을 수도 있고 코드도 깔끔하지 않을 것이다.

@JsonIgnore

@JsonIgnore은 Jackson 패키지의 애노테이션으로, Json의 직력화 및 역직렬화에서 속성을 무시하는데 사용한다.

위와 같은 상황에서 사용하는 것이 @JsonIgnore으로, 클래스 필드에 해당 애노테이션을 추가해주면 HTTP 메시지 컨버터에서 Json으로 직렬화할 때, @JsonIgnore가 붙은 필드는 빼고 변환한다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
@AllArgsConstructor
+@Getter
+public class UserDto {
+  private String userId;
+  @JsonIgnore
+  private String password;
+}
+
+//
+  
+UserDto user = new UserDto("userId", "password");
+
1
+2
+3
+
{
+  "userId":"userid"
+}
+
This post is licensed under CC BY 4.0 by the author.

junit - spy

lombok 사용시 주의점

Comments powered by Disqus.

diff --git "a/posts/spring-PathPattern\352\263\274-servletPath/index.html" "b/posts/spring-PathPattern\352\263\274-servletPath/index.html" new file mode 100644 index 000000000..cd290524b --- /dev/null +++ "b/posts/spring-PathPattern\352\263\274-servletPath/index.html" @@ -0,0 +1,25 @@ + PathPattern과 servletPath | 디피의 개발일지
Posts PathPattern과 servletPath
Post
Cancel

PathPattern과 servletPath

[spring] PathPattern과 servletPath

문제

다음과 같이 interceptor를 등록할 때, addPathPatterns()/api/**를 등록하였지만, /api/article을 요청했을 때 인터셉터가 제대로 동작하지 않았다. 하지만 /**로 등록하니 제대로 동작하였다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
public void addInterceptors(InterceptorRegistry registry) {
+		registry.addInterceptor(authInterceptor)
+			.order(1)
+			.addPathPatterns("/api/**");		// not work
+}
+
+public void addInterceptors(InterceptorRegistry registry) {
+		registry.addInterceptor(authInterceptor)
+			.order(1)
+			.addPathPatterns("/**");			// work
+}
+


단서

내 앱에서 application.propertiesserver.servlet.contextPath=/api 로 모든 servlet의 prefix 주소를 /api로 설정한 것을 떠올렸다.

1
+
server.servlet.contextPath=/api
+

이 단서를 중심으로 찾아보니 pathPattern에 등록할 땐 전체 요청 주소에서 contextPath를 땐 servletPath를 사용해야한다는 것을 알게되었다.

만약 /api/article로 요청한다면 contextPath로 /api를 설정하였으므로 servletPath는 /article이 되어 addPathPatterns()에 등록한 /api/**이 작동하지 않았던 것이다.


왜 servletPath를 써야할까?

그렇다면 왜 스프링에서는 contextPath를 떼서 검사하는 것일까? 그것은 context path와 servlet path의 정의와 역할이 다르기 때문이다.

  • context path : 어플리케이션의 루트. 디폴트는 “/”
  • servlet path : prefix는 dispatcher servlet의 주소를 의미(디폴트는 “/”). prefix를 포함한 전체 주소는 스프링에서 처리되는 주소.

스프링 인터셉터를 등록하기위해 구현한 인터페이스는 WebMvcConfigurer이다. WebMvcConfigurer은 다음과 같이 DispatherServlet 이후에 스프링 MVC 설정을 위해 쓰인다.

WebMvcConfigurer

인터셉터가 적용되거나(addPathPatterns로 등록된 주소), 적용되지 않는 주소(excludePathPatterns로 등록된 주소)를 검사하는 곳은 스프링 MVC이므로, 스프링에서 처리되는 주소인 Servlet Path가 검사 대상이 된다.

생각해보면 어플리케이션의 루트주소 이후부터 검사하는 것이 타당하긴 하다..


출처

https://www.baeldung.com/spring-context-vs-servlet-path

https://lalwr.blogspot.com/2018/04/spring-annotation.html

This post is licensed under CC BY 4.0 by the author.

checked vs unchecked exception

스프링 캐시

Comments powered by Disqus.

diff --git a/posts/spring-RestTemplate/index.html b/posts/spring-RestTemplate/index.html new file mode 100644 index 000000000..35b4928ed --- /dev/null +++ b/posts/spring-RestTemplate/index.html @@ -0,0 +1,47 @@ + RestTemplate | 디피의 개발일지
Posts RestTemplate
Post
Cancel

RestTemplate

스프링에서 지원하는 객체로, 간편하게 Rest 방식 API를 호출할 수 있는 스프링 내장 클래스다.

스프링 3.0부터 지원하였고, json, xml 응답을 모두 받을 수 있다.

특징

  • Blocking I/O 기반의 동기방식을 사용하는 템플릿
  • RESTful 형식에 맞추어진 템플릿
  • Header, Content-Type 등을 설정하여 외부 API 호출
  • Server to Server 통신에 사용

동작원리

RestTemplate

  1. 애플리케이션 내부에서 REST API에 요청하기 위해 RestTemplate의 메서드를 호출한다.
  2. RestTemplate은 MessageConverter를 이용해 java object를 request body에 담을 message(JSON etc.)로 변환한다. 메시지 형태는 상황에 따라 다름
  3. ClientHttpRequestFactory에서 ClientHttpRequest을 받아와 요청을 전달한다.
  4. 실질적으로 ClientHttpRequest가 HTTP 통신으로 요청을 수행한다.
  5. RestTemplate이 에러핸들링을 한다.
  6. ClientHttpResponse에서 응답 데이터를 가져와 오류가 있으면 처리한다.
  7. MessageConverter를 이용해 response body의 message를 java object로 변환한다.
  8. 결과를 애플리케이션에 돌려준다.


API 클래스/메서드 종류

클래스

  • RestTemplate : Spring 3부터 지원하였고, API 호출 이후 응답을 받을 때까지 기다리는 동기방식
  • AsyncRestTemplate : spring 4에 추가된 비동기 RestTemplate. spring 5에서는 deprecated 됨.
  • WebClient : spring 5에서 추가된 논블럭, 리액티브 웹 클라이언트로 동기, 비동기 방식을 지원함

메서드

images%2Fsoosungp33%2Fpost%2Fd02efacc-56e6-4abc-a390-12ecbb565c6b%2Fimage


사용예시

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+
@Component
+public class sendData {
+    private static RestTemplate restTemplate;
+
+    public static ResponseEntity<SubmitData> sendEngine() {
+        int SubNum = 123;
+        int Pnum = 1;
+        Object Pcode = "코드";
+        SubmitData requestDto = SubmitData.builder()
+                .SubNum(SubNum)
+                .Pnum(Pnum)
+                .Pcode(Pcode)
+                .build();
+
+        HttpHeaders headers = new HttpHeaders();
+        headers.add("Content-Type", "application/json");
+        HttpEntity<SubmitData> entity = new HttpEntity<>(requestDto, headers);
+
+        String url = "http://localhost:8080/send";
+        
+        return restTemplate.exchange(url, HttpMethod.POST, entity, SubmitData.class);
+    }
+}
+


출처

https://blog.naver.com/hj_kim97/222295259904

https://velog.io/@soosungp33/%EC%8A%A4%ED%94%84%EB%A7%81-RestTemplate-%EC%A0%95%EB%A6%AC%EC%9A%94%EC%B2%AD-%ED%95%A8

This post is licensed under CC BY 4.0 by the author.

lombok 사용시 주의점

단일책임원칙(SRP)

Comments powered by Disqus.

diff --git a/posts/spring-SpEL/index.html b/posts/spring-SpEL/index.html new file mode 100644 index 000000000..9753062d4 --- /dev/null +++ b/posts/spring-SpEL/index.html @@ -0,0 +1,321 @@ + SpEL - Spring Expression Language | 디피의 개발일지
Posts SpEL - Spring Expression Language
Post
Cancel

SpEL - Spring Expression Language

이 문서는 개인적인 목적이나 배포하기 위해서 복사할 수 있다. 출력물이든 디지털 문서든 각 복사본에 어떤 비용도 청구할 수 없고 모든 복사본에는 이 카피라이트 문구가 있어야 한다. (출처)


SpEL - Spring Expression Language

객체를 조회하고 조작하는 기능을 제공하며, 메서드 호출, 물자열 템플릿 기능 등의 여러가지 추가기능을 제공하는 표현식 언어이다. OGNL 등 자바에서 사용가능한 여러 EL이 있지만, SpEL은 Spring 프로젝트 전반에 걸쳐 사용하기 위해 만들어졌으며 스프링 3.0부터 지원한다.

SpEL 표기법

1
+
#{SpEL 표현식}
+
  • #{} 안의 표현식을 evaluation 한다.
    • 스프링에서 사용되는 ${} 은 SpEL이 아니라 프로퍼티를 참조할 때 사용하는 표기이다. SpEL은 기본적으로 #{}으로 표기한다


SpEL 파싱

Expression을 이용한 SpEL 파싱

1
+2
+3
+4
+5
+6
+7
+
ExpressionParser parser = new SpelExpressionParser();
+Expression expression = parser.parseExpression("1+1");
+Object value1 = expression.getValue();
+System.out.println(value1);    // 2
+
+int value2 = expression.getValue(Integer.class);
+System.out.println(value2);  // 2
+
  • ExpressionParser의 구현체인 SpelExpressionParser로 SpEL의 내용을 파싱하고, Expression의 getValue() 메서드를 사용해 파싱된 결과물을 Object 타입으로 얻을 수 있다.
  • getValue() 메서드에 클래스를 넣으면 타입 캐스팅도 가능하다.

EvaluationContext를 이용한 SpEL 파싱

단순 표현식 파싱이 아닌 객체 정보들의 context가 필요한 경우 EvaluationContext를 사용할 수 있다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+
// name, nationality를 파라미터로 갖는 생성자
+Inventor tesla = new Inventor("Nikola Tesla","Serbian");
+
+ExpressionParser parser = new SpelExpressionParser();
+Expression exp = parser.parseExpression("name"); // name 프로퍼티
+
+// Context에 tesla객체를 넣어준다.
+EvaluationContext context = new StandardEvaluationContext(tesla);
+String name1 = (String) exp.getValue(context); //name = "Nikola Tesla"
+System.out.println(name1);  // Nikola Tesla
+
+// getValue 메서드 호출 시 StandardEvaluationContext를 사용하지 않고 객체를 직접 지정
+String name2 = (String) exp.getValue(tesla);
+System.out.println(name2);  // Nikola Tesla
+
  • StandardEvaluationContext에 name 프로퍼티가 평가될 객체를 지정한다.
  • 두번째 방법처럼 StandardEvaluationContext를 사용하지 않고 getValue()에 직접 객체를 지정할 수 있다. 하지만 StandardEvaluationContext를 사용하면 객체 생성 비용은 들지만, 필드에 대해 캐싱하기 때문에 반복적으로 사용하면 표현식 파싱이 더 빠르다는 장점이 있다.


SpEL 기능 개요

리터럴 표현식

1
+2
+3
+
String helloWorld = (String) parser.parseExpression("'Hello World'").getValue();
+double avogadrosNumber  = (Double) parser.parseExpression("6.0221415E+23").getValue();
+int maxValue = (Integer) parser.parseExpression("0x7FFFFFFF").getValue();
+

프로퍼티, 배열, 리스트, 맵에 대한 접근

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+
int year = (Integer) parser.parseExpression("Birthdate.Year + 1900").getValue(context);
+
+String name = parser.parseExpression("Members[0].Name")
+  .getValue(societyContext, String.class);
+
+// Officer의 딕션어리(맵 접근)
+Inventor pupin = parser.parseExpression("Officers['president']")
+  .getValue(societyContext, Inventor.class);
+
+// 값을 설정한다
+parser.parseExpression("Officers['advisors'][0].PlaceOfBirth.Country")
+  .setValue(societyContext, "Croatia");
+
+// 안전한 탐색 연산자
+city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, String.class);
+
  • javascript의 optional chaining 처럼 null이 아닌지 확인하며 프로퍼티에 접근하는 문법도 사용가능하다.

메서드 호출

1
+2
+3
+
String c = parser.parseExpression("'abc'.substring(2, 3)").getValue(String.class);
+boolean isMember = parser.parseExpression("isMember('Mihajlo Pupin')")
+  .getValue(societyContext, Boolean.class);
+
  • 메서드 호출을 지원하며, 가변인자도 지원한다.

관계 연산자

1
+2
+3
+4
+5
+6
+7
+
boolean trueValue = parser.parseExpression("2 == 2").getValue(Boolean.class);
+boolean falseValue = parser.parseExpression("2 < -5.0").getValue(Boolean.class);
+boolean falseValue = parser.parseExpression("'xyz' instanceof T(int)")
+  .getValue(Boolean.class);
+// 정규표현식과 비교하기 위해서 mathes 키워드를 사용한다.
+boolean trueValue = parser.parseExpression("'5.00' matches '^-?\\d+(\\.\\d{2})?$'")
+  .getValue(Boolean.class);
+

논리 연산자

1
+2
+3
+
boolean falseValue = parser.parseExpression("true and false").getValue(Boolean.class);
+boolean trueValue = parser.parseExpression("true or false").getValue(Boolean.class);
+boolean falseValue = parser.parseExpression("!true").getValue(Boolean.class);
+

수식 연산자

1
+2
+3
+4
+5
+6
+
int two = parser.parseExpression("1 + 1").getValue(Integer.class); // 2
+int four =  parser.parseExpression("1 - -3").getValue(Integer.class); // 4
+int six =  parser.parseExpression("-2 * -3").getValue(Integer.class); // 6
+int minusTwo =  parser.parseExpression("6 / -3").getValue(Integer.class); // -2
+int three =  parser.parseExpression("7 % 4").getValue(Integer.class); // 3
+int minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Integer.class); // -21
+

할당

1
+2
+3
+4
+5
+6
+7
+8
+
Inventor inventor = new Inventor();
+StandardEvaluationContext inventorContext = new StandardEvaluationContext(inventor);
+
+parser.parseExpression("Name").setValue(inventorContext, "Alexander Seovic2");
+
+// 대신에 getValue()로도 할당이 가능하다.
+String aleks = parser.parseExpression("Name = 'Alexandar Seovic'")
+  .getValue(inventorContext, String.class);
+

생성자 호출

1
+2
+3
+4
+5
+6
+7
+8
+
Inventor einstein = p.parseExpression(
+  "new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')")
+  .getValue(Inventor.class);
+
+//리스트의 add 메서드내에서 새로운 inventor 인스턴스를 생성한다
+p.parseExpression(
+  "Members.add(new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German'))")
+  .getValue(societyContext);
+
  • 생성자를 새로 만들 수도 있다. primitive 타입과 String 외에는 모두 정규화된 클래스명을 사용해야한다.

타입

1
+2
+3
+4
+5
+6
+7
+8
+
Class dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class.class);
+
+Class stringClass = parser.parseExpression("T(String)").getValue(Class.class);
+
+boolean
+ trueValue = parser.parseExpression(
+  "T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR")
+  .getValue(Boolean.class);
+
  • T 연산자를 클래스 인스턴스를 지정하는데 사용할 수 있다. 정적 메서드도 이 연산자를 사용해서 호출할 수 있다.
  • StandardEvaluationContext는 타입을 찾으려고 TypeLocator를 사용하고 StandardTypeLocator(교체할 수 있다)는 java.lang 패키지로 만들어진다. 즉, java.lang 내에서 타입을 참조하는 T()는 정규화될 필요는 없지만 다른 모든 타입참조는 정규화되어야 한다.

빈(Bean) 참조

1
+2
+3
+4
+5
+6
+
ExpressionParser parser = new SpelExpressionParser();
+StandardEvaluationContext context = new StandardEvaluationContext();
+context.setBeanResolver(new MyBeanResolver());
+
+// 평가하는 동안 MyBeanResolver에서 resolve(context,"foo")를 호출할 것이다
+Object bean = parser.parseExpression("@foo").getValue(context);
+
  • context가 빈 리졸버로 설정되어있다면 @ 기호를 사용해서 표현식에서 빈을 검색하는 것이 가능하다.

배열 생성

1
+2
+3
+
int[] numbers1 = (int[]) parser.parseExpression("new int[4]").getValue(context);
+int[] numbers2 = (int[]) parser.parseExpression("new int[]{1,2,3}").getValue(context);
+int[][] numbers3 = (int[][]) parser.parseExpression("new int[4][5]").getValue(context);
+

인라인 리스트

1
+2
+
// 4개의 숫자를 담고 있는 자바 리스트로 평가된다
+List numbers = (List) parser.parseExpression("{1,2,3,4}").getValue(context);
+

삼항 연산자

1
+2
+3
+4
+5
+
String falseString = parser.parseExpression("false ? 'trueExp' : 'falseExp'")
+  .getValue(String.class);
+
+// 엘비스 문법 a ? a : b --> a ?: b
+String name = parser.parseExpression("null?:'Unknown'").getValue(String.class);
+
  • 삼항연산자 사용이 가능하고, 단축형인 엘비스 문법도 사용할 수 있다.

변수

1
+2
+3
+4
+5
+6
+7
+
Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
+StandardEvaluationContext context = new StandardEvaluationContext(tesla);
+context.setVariable("newName", "Mike Tesla");
+
+parser.parseExpression("Name = #newName").getValue(context);
+
+System.out.println(tesla.getName()) // "Mike Tesla"
+
  • context에 변수를 선언하여 표현식 내에서 변수를 참조할 수 있다.
  • 다만 #this#root 변수는 항상 정의되어있다. 각각 현재 평가객체, 루트 컨텍스트 객체를 참조한다.

사용자 정의 함수

1
+2
+3
+4
+5
+6
+7
+8
+
ExpressionParser parser = new SpelExpressionParser();
+StandardEvaluationContext context = new StandardEvaluationContext();
+
+context.registerFunction("reverseString",
+                         StringUtils.class.getDeclaredMethod("reverseString",
+                                                             new Class[] { String.class }));
+String helloWorldReversed =
+          parser.parseExpression("#reverseString('hello')").getValue(context, String.class);
+
  • context에 함수를 등록해서 SpEL을 확장할 수 있다.

컬렉션 투영(Collection projection)

1
+
List placesOfBirth = (List)parser.parseExpression("Members.![placeOfBirth.city]");
+
  • 하위 표현식을 평가해서 새로운 컬렉션을 반환한다. 컬렉션의 특정 필드만으로 리스트를 만들고 싶을 떄 사용한다.
  • 맵에서도 투영기능을 사용할 수 있고 맵의 경우 투영 표현식은 맵의 각 엔트리마다(자바 Map.Entry로 표현되는) 평가된다. 맵의 투영결과는 맵의 각 엔트리에 대한 투영 표현식의 평가결과로 이루어진 리스트이다

컬렉션 선택

1
+2
+3
+4
+5
+
List<Inventor> list = (List<Inventor>)parser.parseExpression(
+  "Members.?[Nationality == 'Serbian']")
+  .getValue(societyContext);
+
+Map newMap = parser.parseExpression("map.?[value<27]").getValue();
+
  • 컬렉션을 필터링해서 원래 요소의 서브셋을 가진 새로운 컬렉션을 반환한다.

표현식 템플릿

1
+2
+3
+
String randomPhrase =
+   parser.parseExpression("random number is #{T(java.lang.Math).random()}",
+                          new TemplateParserContext()).getValue(String.class);
+
  • #{}로 구분하여 하나 이상의 평가 블럭을 가진 리터럴 문자를 섞을 수 있다.


사용예시

@Value 애노테이션에서 SpEL 사용

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+
@Value("#{1+1}")
+int value;
+
+@Value("#{'hello ' + 'world'}")
+String greeting;
+
+@Value("#{1 eq 5}")
+boolean trueOrFalse;
+
+@Value("Literal String")
+String literalString;
+
+@Override
+public void run(ApplicationArguments args) throws Exception {
+    System.out.println(value);					// 2
+    System.out.println(greeting);				// hello world
+    System.out.println(trueOrFalse);		// false
+    System.out.println(literalString);	// Literal String
+}
+
  • 빈이 만들어질 때, @Value() 안의 값이 #{} 표기로 감싸져 있으면, SpEL로 파싱하고 평가해서 결과값을 변수에 할당함

SpEL과 프로퍼티

1
+
my.value=100
+
1
+2
+3
+4
+5
+6
+7
+
@Value("#{'${my.value}' eq '100'}")
+boolean isEqual;
+
+@Override
+public void run(ApplicationArguments args) throws Exception {
+    System.out.println(isEqual);		// true
+}
+

빈 참조

1
+2
+3
+4
+5
+6
+7
+8
+
@Component
+public class Sample {
+  private int value = 123;
+
+  public int getValue() {
+    return value;
+  }
+}
+
1
+2
+
@Value("#{sample.Value}")
+int sampleValue;
+

참고

SpEL도 해당하는 타입으로 변환할 때 ConversionService를 사용한다


출처

  • https://blog.outsider.ne.kr/835
  • https://blog.outsider.ne.kr/837
  • https://atoz-develop.tistory.com/entry/Spring-SpEL-Spring-Expression-Language
This post is licensed under CC BY 4.0 by the author.

빈 스코프

좋은 로깅을 위해 알아야할 13가지

Comments powered by Disqus.

diff --git a/posts/spring-Validation/index.html b/posts/spring-Validation/index.html new file mode 100644 index 000000000..df40cd166 --- /dev/null +++ b/posts/spring-Validation/index.html @@ -0,0 +1,201 @@ + Spring 파라미터 Validation | 디피의 개발일지
Posts Spring 파라미터 Validation
Post
Cancel

Spring 파라미터 Validation

[spring] 파라미터 validation

BindingResult

HTTP 메세지 컨버터에서 발생한 데이터 바인딩 오류를 담아 컨트롤러에서 이용할 수 있는 객체. 다음과 같이 바인딩 오류가 발생할 수 있는 파라미터 바로 다음에 파라미터로서 추가한다.

1
+2
+
@PostMapping("/add")
+public String addItemV1(@ModelAttribute Item item, BindingResult bindingResult)
+

컨트롤러에서 데이터 바인딩 오류가 발생하면 400 오류를 클라이언트로 전달한다. 하지만 위와 같이 BindingResult를 파라미터에 추가하면, 400 오류를 바로 보내지 않고 컨트롤러를 호출하여 개발자에게 오류를 해결하도록 한다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+
@PostMapping("/add")
+public String addItemV1(@ModelAttribute Item item, BindingResult bindingResult) {
+  // 없으면 바로 400 오류를 보냄
+  if (bindingResult.hasErrors()) {
+    log.info("errors={}", bindingResult);
+    return "validation/v2/addForm";
+  }
+  ...
+}
+

BindingResult 객체는 thymeleaf 등에서 사용할 수 있다. th:errors="*{itemName}" 과 같이 접근할 수 있는데, 이를 위해서 개발자가 임의로 BindingResult에 오류를 추가해 줄 수 있다.

1
+2
+3
+4
+5
+
// FieldError : 하나의 필드에 오류가 있을 때 추가하는 객체. 스프링에서 바인딩 오류시 넣어줌
+bindingResult.addError(new FieldError("item", "itemName", "상품 이름은 필수입니다."));
+
+// ObjectError : 특정 필드가 아닌 여러 필트에 걸쳐 발생하거나, 글로벌 오류일 때 추가
+bindingResult.addError(new ObjectError("item", "가격 * 수량의 합은 10,000원 이상이어야 합니다. 현재 값 = " + resultPrice));
+

각각 타임리프에서 접근은 다음과 같이 한다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
<!-- FieldError -->
+<div class="field-error" th:errors="*{itemName}">
+상품명 오류
+</div>
+
+<!-- ObjectError -->
+<div th:if="${#fields.hasGlobalErrors()}">
+      <p class="field-error" th:each="err : ${#fields.globalErrors()}" th:text="${err}">
+        전체 오류 메시지
+  		</p> 
+</div>
+


Errors

BindingResult은 인터페이스이고, Errors 인터페이스를 상속받고 있다. 스프링에서는 BeanPropertyBindingResult라는 구현체로 둘 다 구현하여 넘겨주는데, 따라서 BindingResult 대신 Errors 인터페이스를 사용해도 된다.

하지만 Errors는 단순 오류 저장, 조회만을 포함하고 BindingResult는 좀 더 다양한 기능을 제공한다. 또한 관례적으로 BindingResult를 사용하니 BindingResult를 사용하는 것이 좋다.


Validator

스프링은 검증을 체계적으로 제공하기 위해 다음 인터페이스를 제공한다

1
+2
+3
+4
+5
+6
+
public interface Validator {
+ 	 	// 이 validator에서 지원하는 클래스인지 확인
+    boolean supports(Class<?> clazz);
+  	// 검증 로직
+    void validate(Object target, Errors errors);
+}
+

이 인터페이스를 사용해서 다음과 같이 구현하였다고 해보자.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+
@Component
+public class ItemValidator implements Validator {
+	@Override
+	public boolean supports(Class<?> clazz) {
+		return Item.class.isAssignableFrom(clazz);
+	}
+  
+	@Override
+	public void validate(Object target, Errors errors) {
+		Item item = (Item) target;
+    
+		if (item.getPrice() != null && item.getQuantity() != null) {
+			int resultPrice = item.getPrice() * item.getQuantity();
+			if (resultPrice < 10000) {
+				errors.reject("totalPriceMin", new Object[]{10000, resultPrice}, null);
+      } 
+    }
+  }
+}
+

이렇게 구현한 validator는 스프링의 WebDataBinder를 통해 스프링의 도움을 받을 수 있다. 컨트롤러에 다음과 같이 @InitBinder 설정을 한 메서드를 추가하면, 해당 컨트롤러에서는 validator를 자동으로 적용할 수 있다.

1
+2
+3
+4
+
@InitBinder
+public void init(WebDataBinder dataBinder) {
+	dataBinder.addValidators(itemValidator);
+}
+

이렇게 등록된 검증기를 사용하려면 컨트롤러 메서드 파라미터에 @Validated 또는 @Valid 애노테이션을 추가하면 된다,

1
+2
+
@PostMapping("/add")
+public String addItemV6(@Validated @ModelAttribute Item item, BindingResult bindingResult)
+

글로벌로 모든 컨트롤러에 등록할 수도 있다. @SpringBootApplication 애노테이션이 붙은 루트 클래스에서 validator를 추가해주면 된다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+
@SpringBootApplication
+public class ItemServiceApplication implements WebMvcConfigurer {
+		public static void main(String[] args) {
+			SpringApplication.run(ItemServiceApplication.class, args);
+		}
+  
+  	// 글로벌 적용
+		@Override
+		public Validator getValidator() {
+			return new ItemValidator();
+		}
+}
+


Bean Validation

애노테이션으로 쉽게 검증 로직을 적용할 수 있는 Bean Validation 2.0(JSR-380) 기술 표준이다. 검증 애노테이션과 여러 인터페이스의 모음이다.

스프링에서 Bean Validation을 사용하기 위해선 spring-boot-starter-validation라이브러리를 의존성에 추가해야한다. 추가 후 사용할 땐, 검증하고자하는 필드 위에 검증할 애노테이션을 추가하면 된다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
@Data
+public class Item {
+	private Long id;
+    
+	@NotBlank
+	private String itemName;
+    
+	@NotNull
+	@Range(min = 1000, max = 1000000)
+	private Integer price;
+}
+

그리고 컨트롤러에 @Validated 또는 @Valid를 추가해주면 된다.

1
+2
+
@PostMapping("/add")
+public String addItem(@Validated @ModelAttribute Item item, BindingResult bindingResult)
+

javax.validation.constraints.NotNull

org.hibernate.validator.constraints.Range

javax.validation 으로 시작하면 특정 구현에 관계없이 제공되는 표준 인터페이스이고, org.hibernate.validator 로 시작하면 하이버네이트 validator 구현체를 사용할 때만 제공되는 검증 기능이다. 실무에서 대부분 하이버네이트 validator를 사용하므로 자유롭게 사용해도 된다.

스프링은 Bean Validator를 이미 스프링에 완전히 통합해두었다. 스프링부트는 spring-boot-starter-validation 라이브러리를 넣으면 자동으로 Bean Validator를 인지하고 스프링에 통합한다. 이때 스프링부트는 자동으로 Bean Validator를 글로벌 Validator로 등록한다.

이때 개발자가 직접 글로벌 Validator를 등록하면 스프링부트가 추가하는 Bean Validator를 적용되지 않는다. 따라서 Bean Validator를 사용할 때는 직접 등록한 글로벌 Validator를 지워주자.

검증 순서

Bean Validator는 바인딩 된 데이터들을 검증한다. 따라서 애초에 바인딩에서 실패한다면 Bean Validator는 실행되지 않고 FieldError를 추가한다.

검증 순서

  1. @ModalAttribute 각각의 필드에 타입 변환시도
    1. 성공하면 다음으로
    2. 실패하면 typeMismatch로 FieldError 추가
  2. Validator 적용

오브젝트 오류

Bean Validator에서 특정 필드가 아닌 ObjectError를 처리하기 위해선 @ScriptAssert()를 사용하면 된다.

1
+2
+3
+4
+5
+
@Data
+@ScriptAssert(lang = "javascript", script = "_this.price * _this.quantity >= 10000")
+public class Item {
+	//...
+}
+

하지만 이 방법은 제약이 많고 복잡하다. 그리고 실무에서는 검증기능이 해당 객체 범위를 넘어서는 경우도 종종 등장한다(DB 조회 등). 따라서 Bean Validator에서 ObjectError는 @ScriptAssert()를 사용하기 보단 컨트롤러에서 직접 자바코드로 작성하는 것을 권장한다.

groups

다음과 같이 검증 애노테이션을 그룹지어 경우에 따라 다른 검증 애노테이션이 동작하도록 설정할 수 있다

1
+2
+3
+4
+5
+6
+7
+8
+9
+
@Data
+public class Item {
+	@NotNull(groups = UpdateCheck.class) 
+	private String itemName;
+  
+	@NotNull(groups = {SaveCheck.class, UpdateCheck.class})
+	@Range(min = 1000, max = 1000000, groups = {SaveCheck.class, UpdateCheck.class})
+	private Integer price;
+}
+
1
+2
+3
+
// SaveCheck 그룹의 애노테이션만 validation 적용
+@PostMapping("/add")
+public String addItemV2(@Validated(SaveCheck.class) @ModelAttribute Item item, BindingResult bindingResult)
+

이때 @Valid 애노테이션에는 groups 기능이 없다. 따라서 이때는 @Validated를 사용해야한다.

하지만 groups 기능을 사용하면 코드의 복잡도가 증가해서 보통 groups 기능을 사용하기보다는 검증을 달리해야하는 그룹마다 따로 객체를 생성해서 처리하는 편이다.

@ModelAttribute vs @RequestBody

Bean Validation은 @RequestBody를 적용하여 파라미터를 받아올 때도 동작한다. 하지만 객체를 바인딩할 떄의 동작은 다소 다르다.

@ModelAttribute에서는 필드 단위로 바인딩이 적용된다. 따라서 한 필드에서 바인딩 실패가 발생했을 경우엔 나머지 필드는 정상 처리하고 validation을 수행한다.

하지만 @RequestBody은 HttpMessageConver 단계에서 바인딩할 떈 전체 객체 단위로 적용된다. 따라서 한 필드에서 바인딩 오류가 발생한다면 전체 필드가 처리되지 않고 예외가 발생한다.

출처

김영한의 스프링 MVC 2편

This post is licensed under CC BY 4.0 by the author.

유닛테스트 구현 검증이란?

스프링 파일업다운로드

Comments powered by Disqus.

diff --git "a/posts/spring-controller\354\227\220\354\204\234-enum\354\235\204-\354\235\270\354\236\220\353\241\234-\353\260\233\352\270\260/index.html" "b/posts/spring-controller\354\227\220\354\204\234-enum\354\235\204-\354\235\270\354\236\220\353\241\234-\353\260\233\352\270\260/index.html" new file mode 100644 index 000000000..78ef1521d --- /dev/null +++ "b/posts/spring-controller\354\227\220\354\204\234-enum\354\235\204-\354\235\270\354\236\220\353\241\234-\353\260\233\352\270\260/index.html" @@ -0,0 +1,65 @@ + controller에서 enum을 인자로 받기 | 디피의 개발일지
Posts controller에서 enum을 인자로 받기
Post
Cancel

controller에서 enum을 인자로 받기

검색 API를 개발하는 과정에서 다음과 같이 검색 타입을 enum 으로 지정하였고, controller의 param으로 받으려고 했다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+
@Getter
+@AllArgsConstructor
+public enum SearchType {
+	TITLE("title"),
+	CONTENT("content"),
+	HASHTAG("hashtag"),
+	NICKNAME("nickname");
+
+	private final String type;
+}
+
1
+2
+3
+4
+5
+6
+7
+8
+9
+
public class ArticleSearchQuery {
+	public Optional<Integer> offset;
+	public Optional<Integer> limit;
+	public Optional<Integer> category;
+	@NotBlank
+	public String keyword;
+	@NotNull
+	public SearchType type;
+}
+
1
+2
+3
+4
+5
+
@GetMapping(path = "/search")
+public ResponseEntity<ResponseWrapper<ArticleListDto>> searchArticles(
+	@Valid ArticleSearchQuery articleSearchQuery) {
+   ...
+}
+

하지만 SearchType을 요청을 ModelAttribute로 변환하는 과정에서 다음과 같은 오류가 났다.

1
+
Resolved [org.springframework.web.method.annotation.\ModelAttributeMethodProcessor$1: org.springframework.validation.\BeanPropertyBindingResult: 1 errors<EOL>Field error in object \'articleSearchQuery' on field 'type': rejected value [hashtag];
+

Spring’s @RequestParam with Enum 질문의 답변을 보면 Converter를 구현하고 그것을 빈에 등록하면, 스프링 부트가 자동으로 컨버터를 로드하여 데이터를 변환할 때 쓴다고 한다.

1
+2
+3
+4
+5
+6
+7
+
@Component
+public class SearchTypeConverter implements Converter<String, SearchType> {
+	@Override
+	public SearchType convert(String value) {
+		return SearchType.valueOf(value.toUpperCase());
+	}
+}
+
This post is licensed under CC BY 4.0 by the author.

ArgumentResolver 활용

converter

Comments powered by Disqus.

diff --git a/posts/spring-converter/index.html b/posts/spring-converter/index.html new file mode 100644 index 000000000..57d605c2e --- /dev/null +++ b/posts/spring-converter/index.html @@ -0,0 +1,23 @@ + converter | 디피의 개발일지
Posts converter
Post
Cancel

converter

controller 인자로 enum 받는 문제를 해결했던 것은 enum 타입에 맞춘 Converter를 bean에 등록하는 것이었다. 단순히 bean에 등록하는 것으로 controller에서 httpConverter 동작할 때 이것을 적용한 것이다. 이 뒤에 무엇이 있을까 하여 조사해보았다.

스프링에서는 기본적인 타입 변환은 자동으로 지원한다. 따라서 파라미터로 넘어온 문자 데이터가 컨트롤러에서 직접 Integer 타입으로 변환된다.

1
+2
+3
+
@GetMapping(path = "/{articleId}")
+public String getArticleDetail(@PathVariable long articleId) {
+  ...
+

위와 같이 파라미터로 넘어온 문자데이터를 long 타입으로 받아도 스프링에서는 자동으로 변환시켜주기에 문제 없다.

하지만 지원하지 않는 타입도 존재한다. 이럴땐 Converter 인터페이스를 사용하면 된다. 이때 주의할 것이 아래 경로의 Converter를 가져와야한다.

org.springframework.core.convert.converter.Converter

Converter는 두 개의 제네릭을 받는다. 첫번째는 입력 타입이고, 두번째는 반환 타입이다.

1
+
public class SearchTypeConverter implements Converter<String, SearchType>
+

Converter를 적용하려면 convert 메서드를 오버라이드하고, 스프링 빈에 등록해야한다.

1
+2
+3
+4
+5
+6
+7
+
@Component
+public class SearchTypeConverter implements Converter<String, SearchType> {
+	@Override
+	public SearchType convert(String value) {
+		return SearchType.valueOf(value.toUpperCase());
+	}
+}
+

이렇게 등록을 하고 나면 스프링에서 해당 입력 타입을 반환타입으로 convert 해야할 때 등록한 메서드가 사용된다.

This post is licensed under CC BY 4.0 by the author.

controller에서 enum을 인자로 받기

Pure DI - IoC가 없는 DI

Comments powered by Disqus.

diff --git a/posts/spring-mybatis-dynamic-field/index.html b/posts/spring-mybatis-dynamic-field/index.html new file mode 100644 index 000000000..649c42a2b --- /dev/null +++ b/posts/spring-mybatis-dynamic-field/index.html @@ -0,0 +1,11 @@ + mybatis dynamic field | 디피의 개발일지
Posts mybatis dynamic field
Post
Cancel

mybatis dynamic field

게시글 검색을 개발하는 도중 검색의 대상이 되는 field(title | nickname | content)를 동적으로 mybatis 쿼리문 상에 사용해야할 일이 있었다. 따라서 다음과 같이 #{} 를 사용하여 변수를 넣었으나 오류가 발생했다.

1
+2
+3
+4
+5
+
<where>
+	<when test="type != 'hashtag'">
+		atc.#{type} LIKE concat('%', #{keyword}, '%')
+	</when>
+</where>
+

스택오버플로우 질문의 답변에서 #{}은 mybatis에서 자동으로 값을 ''로 감싸기에 그냥 replace하는 ${}를 사용하라는 답변을 얻었고 해결할 수 있었다.

This post is licensed under CC BY 4.0 by the author.

InvalidDefinitionException

CORS

Comments powered by Disqus.

diff --git "a/posts/spring-mybatis.type-aliases-package-\353\217\231\354\236\221\354\233\220\353\246\254/index.html" "b/posts/spring-mybatis.type-aliases-package-\353\217\231\354\236\221\354\233\220\353\246\254/index.html" new file mode 100644 index 000000000..6b47ae643 --- /dev/null +++ "b/posts/spring-mybatis.type-aliases-package-\353\217\231\354\236\221\354\233\220\353\246\254/index.html" @@ -0,0 +1,31 @@ + mybatis.type-aliases-package | 디피의 개발일지
Posts mybatis.type-aliases-package
Post
Cancel

mybatis.type-aliases-package

mybatis mapper에서 requestType이나 parameterType으로 클래스를 참조할 때 다음과 같이 패키지명을 다 써줘야하는 불편함이 있다.

1
+2
+3
+
<mapper namespace="com.hello.spring.article.mapper.ArticleMapper">
+    <select id="selectArticles" resultType="com.hello.spring.article.dto.ArticleDto">
+      ...
+

이럴 땐 application.properties에서 다음과 같이 설정해주면 쿼리 태그에서는 패키지명을 써주지 않고 클래스명만 써줘도 된다.

1
+
mybatis.type-aliases-package=com.hello.spring
+
1
+2
+3
+
<mapper namespace="com.hello.spring.article.mapper.ArticleMapper">
+    <select id="selectArticles" resultType="ArticleDto">
+      ...
+

스프링에서 mybatis.type-aliases-package에 설정된 패키지를 보고 그 하위 경로에서 클래스를 찾는 방식이다.


주의점

mybatis.type-aliases-package에서 설정한 패키지 하위 경로를 모두 살펴보는 방식으로 동작하므로, 같은 클래스가 두 개 이상 존재하면 에러가 난다.

1
+2
+3
+4
+5
+
// 파일구조
+dto
+ > test
+   > ArticleDto
+ > ArticleDto
+
1
+
Caused by: org.apache.ibatis.type.TypeException: The alias 'ArticleDto' is already mapped to the value 'com.hello.spring.article.dto.ArticleDto'.
+

이럴 땐 아래와 같이 둘 중 하나를 @Alias 애노테이션으로 다른 이름으로 지정해주면 해결된다.

1
+2
+
@Alias("ArticleDto2")
+public class ArticleDto
+


참고

  • https://yjh5369.tistory.com/entry/Spring-boot-mybatis-alias-%EC%82%AC%EC%9A%A9%EB%B2%95
This post is licensed under CC BY 4.0 by the author.

좋은 로깅을 위해 알아야할 13가지

STOMP

Comments powered by Disqus.

diff --git "a/posts/spring-\353\271\210-\354\212\244\354\275\224\355\224\204/index.html" "b/posts/spring-\353\271\210-\354\212\244\354\275\224\355\224\204/index.html" new file mode 100644 index 000000000..0ab205305 --- /dev/null +++ "b/posts/spring-\353\271\210-\354\212\244\354\275\224\355\224\204/index.html" @@ -0,0 +1,97 @@ + 빈 스코프 | 디피의 개발일지
Posts 빈 스코프
Post
Cancel

빈 스코프

빈 스코프는 말 그대로 빈이 존재할 수 있는 범위를 뜻한다. 스프링에서는 다음과 같은 스코프를 지원한다

  • 싱글톤 : 기본 스코프. 스프링 컨테이너의 시작과 종료까지 유지되는 가장 넓은 범위의 스코프
  • 프로토타입 : 스프링 컨테이너는 프로토타입 빈의 생성과 의존관계 주입까지만 관여하고 더는 관리하지 않는 매우 짧은 범위의 스코프이다.
  • 웹 관련 스코프
    • request : 웹 요청이 들어오고 나갈때까지 유지되는 스코프
    • session : 웹 세션이 생성되고 종료될 때까지 유지되는 스코프
    • application : 웹의 서블릿 컨텍스트와 같은 범위로 유지되는 스코프


프로토타입 스코프

싱글톤 스코프의 빈을 조회하면 스프링 컨테이너는 항상 같은 인스턴스의 스프링 빈을 반환한다. 반면에 프로토타입 스코프를 스프링 컨테이너에 조회하면 스프링 컨테이너는 항상 새로운 인스턴스를 생성해서 반환한다.

  1. 프로토타입 스코프의 빈을 스프링 컨테이너에 요청한다.
  2. 스프링 컨테이너는 이 시점에 프로토타입 빈을 생성하고, 필요한 의존관계를 주입한다.

  1. 스프링 컨테이너는 생성한 프로토타입 빈을 클라이언트에 반환한다.
  2. 이후에 스프링 컨테이너에 같은 요청이 오면 항상 새로운 프로토타입 빈을 생성해서 반환한다.

스프링 컨테이너는 프로토타입 빈을 관리하지 않는다. 프로토타입 빈을 관리할 책임은 프로토타입 빈을 받은 클라이언트에게 있다. 따라서 @PreDestroy 같은 종료 메서드가 호출되지 않는다.

싱글톤 빈과 함께 사용시 문제점

프로토타입 빈은 매번 새로운 객체 인스턴스를 생성하다는 점에서 싱글톤 빈과 함께 사용시 의도한 대로 동작하지 않을 수 있다.

만약 싱글톤으로 관리되는 빈에서 프로토타입 빈을 의존한다면, 싱글톤 빈이 생성되는 시점에 프로토타입 빈은 단 한번 주입된다. 따라서 클라이언트 요청이 얼마나 많든 싱글톤 빈에 주입된 프로토타입 빈은 새로 생성되지 않는다.

만약 프로토타입 빈은 매 요청마다 새롭게 생성하는 것을 의도하여 사용되는데, 이렇게 된다면 의도한대로 동작하는 것이 아닐 것이다.

참고 : 여러 빈에서 같은 프로토타입 빈을 주입 받으면, 주입 받는 시점에 각각 새로운 프로토타입 빈이 생성된다. 예를 들어서 clientA, clientB가 각각 의존관계 주입을 받으면 각각 다른 인스턴스의 프로토타입 빈을 주입 받는다.

  • clientA prototypeBean@x01

  • clientB prototypeBean@x02

    물론 사용할 때 마다 새로 생성되는 것은 아니다

싱글톤 빈과 함께 사용시 Provider로 문제 해결

싱글톤 빈과 프로토타입 빈을 함께 사용할 때, 사용할 때마다 항상 새로운 프로토타입 빈을 생성하기 위해선 DI가 아닌 직접 필요한 의존관계를 찾는 것을 의미하는 Dependency Lookup(DL)이 필요하다. DL을 위해선 다음과 같은 방법들이 있다.

  • 스프링 컨테이너에 요청 : ApplicationContext 클래스로 사용할 때마다 프로토타입 빈 직접 가져오기

  • ObjectFactory, ObjectProvider

    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +
    @Autowired
    +private ObjectProvider<PrototypeBean> prototypeBeanProvider;
    +  
    +public int logic() {
    +      PrototypeBean prototypeBean = prototypeBeanProvider.getObject();
    +      prototypeBean.addCount();
    +      int count = prototypeBean.getCount();
    +      return count;
    +}
    +
    • 둘 다 스프링에 의존하는 클래스로, ObjectFactory은 과거 버전이고, ObjectProvider은 ObjectFactory에 편의기능이 더해진 클래스이다.
    • ObjectProvider은 getObject()를 호출하면 내부에서 스프링 컨테이너를 통해 해당 빈을 찾아서 반환한다.
    • 스프링컨테이너에 직접 요청하는 것보다 단위테스트를 만들거나 mock 코드를 만드는 것이 쉬워진다.
  • JSR-330 Provider

    • javax.inject.Provider 이라는 JSR-330 자바표준이다
    • 사용하려면 javax.inject:javax.inject:1 라이브러리를 추가해줘야함
    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +
    @Autowired
    +private Provider<PrototypeBean> provider;
    +  
    +public int logic() {
    +      PrototypeBean prototypeBean = provider.get();
    +      prototypeBean.addCount();
    +      int count = prototypeBean.getCount();
    +      return count;
    +}
    +

ObjectProvider와 JSR-330 Provider 중에선 만약 코드를 스프링이 아닌 다른 컨테이너에서도 사용할 수 있어야한다면 JSR-330 Provider를 사용하면 된다. 하지만 그럴일은 거의 없다.


웹 스코프

웹 스코프는 다음과 같은 특징이 있다.

  • 웹 환경에서만 동작한다.
  • 프로토타입과 다르게 스프링이 해당 스코프의 종료시점까지 관리한다. 따라서 종료 메서드가 호출된다.

그리고 종류는 다음과 같다.

  • request : HTTP 요청하나가 들어오고 나갈 때까지 유지되는 스코프. 각각의 HTTP 요청마다 별도의 빈 인스턴스가 생성되고 관리된다.
  • session : HTTP session과 동일한 생명주기를 가지는 스코프. 웹 브라우저별로 변수를 관리하고자 할 경우 사용한다.
  • application : 서블릿 컨텍스트와 동일한 생명주기를 가지는 스코프
  • websocket : 웹 소켓과 동일한 생명주기를 가지는 스코프

Request 스코프 예시

Request 스코프의 경우 매 요청마다 하나의 빈이 관리된다. 따라서 다음과 같이 로그를 남기는 빈에서, 요청을 구분하기 위해 사용될 수 있다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+
@Component
+@Scope(value = "request")
+public class MyLogger {
+	private String uuid;
+	private String requestURL;
+  
+	public void setRequestURL(String requestURL) {
+		this.requestURL = requestURL;
+	}
+	public void log(String message) {
+		System.out.println("[" + uuid + "]" + "[" + requestURL + "] " + message); 
+	}
+  
+ 	@PostConstruct
+	public void init() {
+		uuid = UUID.randomUUID().toString();
+		System.out.println("[" + uuid + "] request scope bean create:" + this);
+	}
+  
+	@PreDestroy
+	public void close() {
+		System.out.println("[" + uuid + "] request scope bean close:" + this);
+	}
+}
+
  • 이 빈이 생성되는 시점에 자동으로 @PostConstruct 초기화 메서드를 사용해서 uuid를 생성해서 저장해둔다. 이 빈은 HTTP 요청 당 하나씩 생성되므로, uuid를 저장해두면 다른 HTTP 요청과 구분할 수 있다.
  • 빈이 소멸되는 시점에 @PreDestroy 를 사용해서 종료 메시지를 남긴다. 따라서 요청이 끝난 시점을 기록할 수 있다.

주의점

만약 위와 같은 웹 스코프 빈을 주입받는 빈을 생성하고 애플리케이션을 실행시키면 다음과 같은 오류가 발생한다.

1
+2
+3
+
Error creating bean with name 'myLogger': Scope 'request' is not active for the
+  current thread; consider defining a scoped proxy for this bean if you intend to
+  refer to it from a singleton;
+

request 스코프 빈은 요청이와야 생성되는 빈이므로, 아직 생성도 안됐는데 다른 빈에서 주입받을 수 없다는 뜻이다.

이 문제를 해결해기 위해선 다음과 같은 방법이 있다.

  • 웹 스코프 빈을 사용할 때마다 스프링빈에서 직접 조회 or Provider 사용

    • ObjectProvider로 필요할 때 꺼내어 사용하면 된다.
    • 웹 환경이 아니어서 빈이 존재하지 않는 경우가 있을 수 있는데, 그럴 땐 getIfAvailable() 메서드를 사용하면 된다. 빈을 사용할 수 없는 상황에선 null을 반환하는 메서드이다.
  • 프록시 사용

    1
    +2
    +3
    +
    @Component
    +@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
    +public class MyLogger
    +
    • @Scope 애노테이션에 속성으로 proxyMode를 주어 프록시를 사용할 수 있다.
      • 적용대상이 클래스면 TARGET_CLASS, 인터페이스면 INTERFACECS 선택
    • 가짜 프록시 인스턴스를 만들고, HTTP Request와 상관없이 가짜 프록시 인스턴스를 다른 빈에 미리 주입하는 방식이다.

프록시 동작원리

  • 스프링 컨테이너는 CGLIB라는 바이트코드를 조작하는 라이브러리를 사용해서, MyLogger를 상속받은 가짜 프록시 객체를 생성한다.
  • 그리고 스프링 컨테이너에 “myLogger”라는 이름으로 진짜 대신에 이 가짜 프록시 객체를 등록한다.
  • 가짜 프록시 객체는 요청이 오면 그때 내부에서 진짜 빈을 요청하는 위임 로직이 들어있다.

image-20221113224321240

  • 가짜 프록시 객체는 내부에 진짜 myLogger를 찾는 방법을 알고 있다. 따라서 클라이언트가 myLogger.logic()을 호출하면 사실은 가짜 프록시 객체의 메서드를 호출한 것이다.
  • 가짜 프록시 객체는 원본 클래스를 상속받아서 만들어졌기 때문에 이 객체를 사용하는 클라이언트 입장에서는 사실 원본인지 아닌지도 모르게, 동일하게 사용할 수 있다(다형성)


출처

This post is licensed under CC BY 4.0 by the author.

ResponseStatus vs ResponseEntity

SpEL - Spring Expression Language

Comments powered by Disqus.

diff --git "a/posts/spring-\354\204\234\353\270\224\353\246\277-\354\273\250\355\205\214\354\235\264\353\204\210/index.html" "b/posts/spring-\354\204\234\353\270\224\353\246\277-\354\273\250\355\205\214\354\235\264\353\204\210/index.html" new file mode 100644 index 000000000..8ffc6eb57 --- /dev/null +++ "b/posts/spring-\354\204\234\353\270\224\353\246\277-\354\273\250\355\205\214\354\235\264\353\204\210/index.html" @@ -0,0 +1,21 @@ + 서블릿 컨테이너 | 디피의 개발일지
Posts 서블릿 컨테이너
Post
Cancel

서블릿 컨테이너

서블릿 컨테이너는 서블릿들의 생성, 실행, 파괴를 담당하며 서블릿을 관리해준다. 서블릿 컨테이너는 클라이언트의 요청을 받고 응답할 수 있게, 웹 서버와 소켓을 만들어 통신한다.

servlet container

서블릿 컨테이너의 대표적인 무료 서비스로 톰캣이 있다. 톰캣은 웹 서버와 소켓을 만들어 통신하며 JSP와 서블릿이 작동할 수 있는 환경을 제공한다.


서블릿이란?

웹 프로그래밍에서 클라이언트의 HTTP 요청을 처리하고 응답을 보내는 자바 프로그래밍 기술. Servlet 클래스의 구현 규칙을 따른다.

Servlet 은 javax.servlet 패키지에 정의된 인터페이스로, 서블릿의 라이프 사이클을 위한 세가지 필수적인 메서드를 정의한다.

  • init() : 서블릿 초기화 단계에 호출됨
  • service() : 초기화 이후 각각의 요청이 들어오면 호출됨
  • destroy() : 서블릿 객체가 파괴되어야할 때 호출됨

서블릿의 특징

  • 클라이언트 요청에 동적으로 작동
  • java thread를 이용해 동작
  • html 변경시 재컴파일 필요
  • java 코드에 html이 들어가있음
  • html을 사용해서 요청에 응답


서블릿 컨테이너의 역할은?

웹 서버와의 통신 지원

서블릿 컨테이너는 서블릿와 웹서버가 손쉽게 통신할 수 있게 해주어, 소켓을 만들고 listen, accept 등을 API로 제공하여 복잡한 과정을 생략할 수 있게 해준다

서블릿 생명주기 관리

  1. 서블릿 클래스를 로딩하여 인스턴스화
  2. 초기화 메서드를 호출
  3. 요청이 들어오면 적절한 서블릿 메서드를 호출한다
  4. 서블릿 소멸시 GC를 진행

멀티 쓰레드 지원 및 관리

  1. 서블릿 컨테이너는 요청이 올 때마다 새로운 자바 쓰레드를 하나 생성
  2. HTTP 서비스 메서드를 실행하고 나면 쓰레드는 자동으로 소멸
  3. 원래는 쓰레드를 관리해야하지만 서버가 다중 쓰레드를 생성 및 운영해주니 서블릿 컨테이너에서는 쓰레드의 안정성에 대해서는 걱정하지 않아도 된다

선언적인 보안 관리

  • 서블릿 컨테이너를 사용하면 개발자는 보안에 관련된 내용을 서블릿 또는 자바 클래스에 구현해 놓지 않아도 된다
  • 일반적으로 보안관리는 XML 배포 서술자에 기록하므로, 보안에 대해 수정할 일이 생겨도 자바 소스 코드를 수정하여 다시 컴파일 하지 않아도 보안관리가 가능합니다.


서블릿 컨테이너 동작방식

images%2Fhan_been%2Fpost%2Fb6a06db4-33a2-4bfc-a822-81a8dafe90d1%2Fimage

  1. 웹 서버가 HTTP 요청을 받는다
  2. 웹서버는 요청을 서블릿 컨테이너로 전달한다
  3. 서블릿이 컨테이너에 없다면, 서블릿을 동적으로 검색하여 컨테이너의 주소 공간에 로드한다
  4. 컨테이너가 서블릿의 init() 메소드를 호출하면, 서블릿이 초기화된다
  5. 컨테이너가 서블릿의 service() 메소드를 호출하여 HTTP 요청을 처리한다.
  6. 웹서버는 동적으로 생성된 결과를 올바른 위치에 반환한다.


JVM의 역할

이 과정에서 JVM은 클라이언트의 요청들을 각각 분리된 스레드 내부에서 처리한다. 즉, 서블릿을 사용하는 것은 JVM이 각 요청을 분리된 자바 스레드 내부에서 처리하도록 하는 것이다. 각 서블릿은 HTTP 요청에 응답하는 특정한 요소들이 있는 자바 클래스이다.

대부분의 사례에 서블릿 컨테이너는 하나의 JVM에서 동작하지만 컨테이너가 여러 개의 JVM들을 필요로하는 문제가 존재하기도 한다.

서블릿 컨테이너의 가장 중요한 기능은 요청을 올바른 서블릿에 전달해서 처리되도록 하고, JVM이 해당 요청을 처리한 후에는 생성된 결과를 올바른 장소에 동적으로 반환해주는 것이다.


서블릿 사용 예시

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+
@WebServlet(name = "requestHeaderServlet", urlPatterns = "/request-header")
+public class RequestHeaderServlet extends HttpServlet {
+	@Override
+	protected void service(HttpServletRequest request, HttpServletResponse response)
+		throws ServletException, IOException {
+	  System.out.println("request.getMethod() = " + request.getMethod());
+		...
+		response.getWriter().write("ok");
+	}
+}
+
  • @WebServlet : 클라이언트의 요청 URL을 보고 서블릿 컨테이너에서 서블릿을 매핑할 때 web.xml을 통해 찾는 방법과 애노테이션으로 찾는 방법이 있다. 여기서는 @WebServlet을 사용하여 애노테이션으로 접근하였다
  • HttpServlet : Servlet인터페이스를 구현한 클래스로, HTTP 요청에 대응하기 좋은 형태로 구현되어있다. WAS가 생성한 HttpServletRequest와 HttpServletResponse을 받아 servicedoGet 등의 메서드를 수행한다.
  • HttpServletRequest, HttpServletResponse : 클라이언트의 요청과, 응답을 위한 데이터는 개발자가 접근하기 어려운 단순 텍스트로 되어있다. 이와 같은 요청/응답에 쉽게 접근할 수 있도록 하는 객체가 이 두가지이다.



출처

https://velog.io/@han_been/%EC%84%9C%EB%B8%94%EB%A6%BF-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88Servlet-Container-%EB%9E%80

https://velog.io/@han_been/Servlet

김영한의 스프링 MVC 1

This post is licensed under CC BY 4.0 by the author.

ElasticSearch

SockJS

Comments powered by Disqus.

diff --git "a/posts/spring-\354\204\234\353\270\224\353\246\277-\355\225\204\355\204\260/index.html" "b/posts/spring-\354\204\234\353\270\224\353\246\277-\355\225\204\355\204\260/index.html" new file mode 100644 index 000000000..51d86d77d --- /dev/null +++ "b/posts/spring-\354\204\234\353\270\224\353\246\277-\355\225\204\355\204\260/index.html" @@ -0,0 +1,23 @@ + 서블릿 필터 | 디피의 개발일지
Posts 서블릿 필터
Post
Cancel

서블릿 필터

서블릿 필터

스프링에서 공통 관심사를 처리하기 위한 방법으로, 스프링 인터셉터와 함께 쓰이는 방법이다. 스프링 AOP를 사용하여 공통관심사를 처리할 수 있지만 웹과 관련된 공통 관심사는 서블릿 필터나 스프링 인터셉터를 사용하는 것이 좋다. 서플릿 필터나 스프링 인터셉터는 HttpServletRequest를 제공해주기 때문이다.

소개

필터 흐름

HTTP 요청 -> WAS -> 필터 -> 디스패처 서블릿 -> 컨트롤러

필터는 디스패처 서블릿이 호출되기 전에 호출된다. 필터를 통해 다음과 같은 것들을 할 수 있다.

  • 필터 제한 : 적절하지 않은 요청을 차단할 수 있다.

  • 필터 체인 : 다음과 같이 필터를 연속으로 적용하여 체인처럼 사용할 수 있다.

    HTTP 요청 -> WAS -> 필터1 -> 필터2 -> 필터3 -> 디스패처 서블릿

필터 인터페이스

필터인터페이스는 다음과 같은 메서드가 등록되어있다. 이 필터 인터페이스를 구현하고 등록하면 서블릿 컨테이너가 필터를 싱글톤 객체로 생성하고 관리한다.

  • Init() : 필터 초기화 메서드. 서블릿 컨테이너가 생성될 떄 호출된다.
  • doFilter() : 고객의 요청이 올 때마다 해당 메서드가 호출된다. 필터의 로직을 구현하면 된다.
    • return; 하면 디스패처 서블릿으로 진행하지 않음.
  • destroy() : 필터 종료 메서드. 서블릿 컨테이너가 종료될 때 호출된다.

필터 등록

스프링 부트에서는 다음과 같이 FilterRegistrationBean를 사용하여 등록하면 된다. 이때 등록하는 메서드는 @Configuration 로 등록한 클래스 아래 위치해야한다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
@Configuration
+public class WebConfig {
+  @Bean
+  public FilterRegistrationBean logFilter() {
+  	 FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
+    filterRegistrationBean.setFilter(new LogFilter());
+    filterRegistrationBean.setOrder(1);
+    filterRegistrationBean.addUrlPatterns("/*");
+    return filterRegistrationBean;
+  }
+}
+
  • setFilter : 등록할 필터를 지정한다.
  • setOrder : 필터가 적용되는 순서를 정함. 낮은 숫자가 먼저 실행된다.
  • addUrlPatterns() : 필터를 적용할 URL 패턴을 지정한다. 한번에 여러 패턴을 지정할 수 있다.
    • 이때 적용되는 패턴은 서블릿 URL 패턴이라고 검색하면 된다.

이외 @ServletComponentScan, @WebFilter(filterName="logFilter", urlPatterns="/*")으로 필터 등록이 가능하지만, 필터 순서 조절이 안된다. 따라서 FilterRegistrationBean을 사용하는 것이 좋다.

요약

서블릿 필터는 스프링 인터셉터와 함께 웹 요청이 왔을 때 공통 관심사를 처리해주는 방법이다. 서블릿 필터는 스프링 인터셉터와 달리 디스패처 서블릿 이전에 호출된다. 필터 인터페이스를 구현하고, 빈에 등록하여 서블릿 필터를 등록할 수 있다

출처

This post is licensed under CC BY 4.0 by the author.

쿠키 인증 vs 세션 vs JWT

스프링 MVC 구조

Comments powered by Disqus.

diff --git "a/posts/spring-\354\212\244\355\224\204\353\247\201-MVC-\352\265\254\354\241\260/index.html" "b/posts/spring-\354\212\244\355\224\204\353\247\201-MVC-\352\265\254\354\241\260/index.html" new file mode 100644 index 000000000..6be256166 --- /dev/null +++ "b/posts/spring-\354\212\244\355\224\204\353\247\201-MVC-\352\265\254\354\241\260/index.html" @@ -0,0 +1 @@ + 스프링 MVC 구조 | 디피의 개발일지
Posts 스프링 MVC 구조
Post
Cancel

스프링 MVC 구조

스프링 구조

과정

  1. 클라이언트가 서버로 요청하면 먼저 Dispatcher Servlet 받음
  2. Dispatcher Servlet은 http 요청의 URL과 메소드를 보고 이것을 처리할 수 있는 핸들러(컨트롤러)를 찾기 위해 핸들러 매핑 과정을 진행한다.
  3. 핸들러를 찾으면 이 핸들러를 처리할 수 있는 핸들러 어댑터를 조회한다.
  4. 핸들러 어댑터를 실행시킨다. 핸들러 어댑터는 요청을 적절히 처리한 다음 핸들러를 호출한다.
  5. 핸들러는 작업을 완료한 후 핸들러 어댑터에게 작업 결과를 반환한다.
  6. 핸들러 어댑터는 이 결과를 가공하여 ModelAndView로 만들어 Dispatcher Servlet에게 반환해준다.
  7. Dispatcher Servlet은 ModelAndView의 view name을 가지고 viewResolver 를 호출한다.
  8. viewResolver는 View를 반환해준다.
  9. Dispatcher Servlet에서는 View의 render 메소드를 model을 넣어주며 호출해준다.
  10. 이후 HTML이 응답으로 클라이언트에게 간다.


스프링 MVC는 위와 같이 매우 많은 과정을 거쳐 동작하지만, 개발자는 핸들러를 구현하고 이를 매핑 시켜주는 것만해주면 스프링 프레임워크에서 나머지를 알아서 해준다.


Dispatcher Servlet 구조

  • HttpServlet을 상속 받아서 사용하고, 서블릿으로 동작한다.
  • 스프링 부트는 Dispatcher Servlet 을 서블릿으로 자동으로 동록하며 모든 경로(urlPatterns="/")에 대해 매핑한다.
  • 클라이언트의 요청을 받고 서블릿이 호출되면, HttpServlet이 제공하는 service()가 호출된다. Dispatcher Servlet의 부모인 FrameworkServletservice()를 오버라이드 해두었다. FrameworkServlet.service()를 시작으로 여러 메서드가 호출되면서 DispatcherServlet.doDispatch()가 호출되고 위 과정을 시작한다.


스프링 부트가 자동 등록하는 핸들러 매핑과 핸들러 어댑터

HandlerMapping

  1. RequestMappingHandlerMapping : 애노테이션 기반의 컨트롤러인 @RequestMapping에서 사용
  2. BeanNameUrlHandlerMapping : 스프링 빈의 이름으로 핸들러를 찾는다.

HandlerAdapter

  1. RequestMappingHandlerAdapter : 애노테이션 기반의 컨트롤러인 @RequestMapping에서 사용
  2. HttpRequestHandlerAdapter : HttpRequestHandler 처리
  3. SimpleControllerHandlerAdapter : controller 인터페이스 처리

핸들러 매핑과 핸들러 어댑터는 이 순서대로 찾고, 없으면 다음 순서로 넘어간다.


스프링 부트가 자동 등록하는 뷰 리졸버

(이외에도 존재함)

  1. BeanNameViewResolver : 빈 이름으로 뷰를 찾아서 반환한다.
  2. InternalResouceViewResolver : JSP를 처리할 수 있는 뷰를 반환함.


출처 : 김영한의 스프링 강의

This post is licensed under CC BY 4.0 by the author.

서블릿 필터

예외 처리와 오류페이지

Comments powered by Disqus.

diff --git "a/posts/spring-\354\212\244\355\224\204\353\247\201-\354\272\220\354\213\234/index.html" "b/posts/spring-\354\212\244\355\224\204\353\247\201-\354\272\220\354\213\234/index.html" new file mode 100644 index 000000000..147295871 --- /dev/null +++ "b/posts/spring-\354\212\244\355\224\204\353\247\201-\354\272\220\354\213\234/index.html" @@ -0,0 +1,90 @@ + 스프링 캐시 | 디피의 개발일지
Posts 스프링 캐시
Post
Cancel

스프링 캐시

[spring] 스프링 캐시

캐시란 반복적으로 데이터를 불러올 때 지속적으로 DBMS 혹은 서버에 요청하는 것이 아닌 메모리에 데이터를 저장하였다가 데이터를 불러다가 쓰는 것을 말한다. 서버나 DBMS의 부담을 줄여주고, 메모리에 저장되어있기 때문에 많은 시스템에서 사용할수 있다.

캐시는 Long Tail 법칙에 따라 시스템 리소스 사용의 대부분을 차지하는 20%의 요청을 캐싱하면 시스템 전체가 매우 빨라진다고 할 수 있다.

17110B4350CC5EC51D


CacheManager

스프링 부트에서 spring-boot-starter-cache을 추가하여 구성할 수 있는 캐시 매니저이다. 기본적으로 별도의 추가적인 서드파티 모듈이 없는 경우에는 Local Memory에 저장이 가능한 ConcurrentMap 기반인 ConcurrentMapCacheManager가 빈으로 자동 생성된다.

이 외에도 EHCache, Redis 등의 서드 파티 모듈을 추가하면 EHCacheCacheManager, RedisCacheManager를 빈으로 등록하여 사용할 수 있다. 이렇게 하면 별도로 다른 설정 없이도 단순 Memory cache가 아닌 cache server를 대상으로 캐시를 저장할 수 있도록 지원하고 있다.

스프링 3.2 이상에서는 7개의 CacheManager 구현체를 지원한다

  • SimpleCacheManager
  • NoOpCacheManager
  • ConcurrentMapCacheManager
  • CompositeCacheManager : 한 개 이상의 캐시매니저를 선택해야할 떄 사용
  • EhCacheCacheManager
  • RedisCacheManager (스프링 Redis)
  • GemfireCacheManager (스프링 GemFire)

SimpleCacheManager 사용처

SimpleCacheManager는 단순히 캐시를 등록하여 사용하는데 사용할 수 있지만, 다른 캐시매니저와는 달리 다양한 캐시를 조합하여 등록할 수 있다는 점에서 편리할 수 있다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+
// ConcurrentMapCacheManager
+// 캐시의 이름만 등록할 수 있고, 모두 ConcurrnetMapCache로 등록된다.
+new ConcurrentMapCacheManager("comment", "category", "user");
+
+// SimpleCacheManager
+// 캐시를 직접 등록하여, 다양한 조합의 캐시로 사용할 수 있다.
+SimpleCacheManager cm = new SimpleCacheManager();
+cm.setCaches(Arrays.asList(
+      defaultCache(),
+      exceptionCache()));
+


스프링 캐시 애노테이션

@EnableCache

Annotation 기반 캐싱 설정을 사용함을 명시하는 애노테이션이다. (내부적으로는 Sprign AOP 사용)

속성

  • proxyTargetClass : 클래스 기반의 Proxy 생성 여부. 디폴트는 false
    • false : JDK Dynamic Proxy 사용. (interface 기반)
    • true : CGLIB Proxy 사용 (class 기반)
  • Mode : 위빙 모드에 대한 설정. 디폴트는 PROXY
    • PROXY : 기본의 spring AOP 방식을 이용한 RTW 방식 사용
    • ASPECTJ : aspectj 라이브러리를 이용한 CTW, LTW 방식 지원
  • order : AOP order 설정. 디폴트는 Integer.MAX_VALUE


@Cacheable

1
+2
+3
+4
+5
+
@Target(value={TYPE,METHOD})
+@Retention(value=RUNTIME)
+@Inherited
+@Documented
+public @interface Cacheable
+

메서드에 붙어, 해당 메서드가 호출된 결과가 캐시 될 수 있다고 알려주는 애노테이션이다.

동작

지시된 메서드가 호출될 때마다, 주어진 파라미터로 이미 호출된 적이 있는지 확인하는 과정이 추가된다. 기본적으론 메서드의 파라미터를 캐시 키로 사용하지만, SpEL expressionkey()로 적용될 수 있다. 또는 커스텀 KeyGenerator 구현이 적용될 수도 있다.

만약 주어진 캐시 키로 값을 찾을 수 없다면, 메서드는 정상적으로 호출되고 반환값을 만든다. 그리고 그 반환값은 캐시 저장소에 저장된다.

주의 : Optional로 감싸진 값은 자동으로 unwrap 된다. Optional 값이 존재하면 그 값이 저장되고, 없으면 null이 저장된다.

속성

  • cacheManager : 사용할 CacheManager를 지정
  • cacheNames : 메서드의 반환값이 저장될 떄 사용될 캐시의 이름들
  • cacheResolver : 커스텀 CacheResolver의 빈 이름(CacheResolver : Cache키에 대한 결과값을 돌려주는 리졸버)
  • condition : 메서드 결과값이 캐싱되도록 하는 조건. SpEL로 표현
  • unless : 메서드 결과값이 캐싱되지 않도록 하는 조건. SpEL로 표현
    • ex) unless = “#id == null” –> id가 null일 땐 캐싱이 일어나지 않음.
  • key : 동적으로 키를 계산할 때 사용되는 SpEL 표현. 같은 캐시명을 사용할 때 구분되는 구분 값이다.
  • keyGenerator : 커스텀 KeyGenerator의 빈 이름. 특정 로직에 의해 cache key를 만들고자할 때 사용한다. key와 함께 사용할 수 없음.
  • sync : 복수의 스레드가 같은 키로 값을 가져오려고 할 때 동기화할 것인지 표시
  • value : cacheNames()의 alias

@CacheEvict

메서드 실행 시, 해당 캐시를 삭제

속성

  • Value, cacheName : 삭제할 캐시 명
  • key : 같은 캐시명을 사용할 때 구분되는 구분값.
  • keyGenerator
  • cacheManager
  • cacheResolver
  • condition : SpEL 표현식을 통해 특정 조건에 부합하는 경우에만 캐시 사용. 연산 조건이 true일 때문 캐시 삭제
  • allEntries : Cache key에 대한 전체 데이터 삭제 여부
  • beforeInvocation : true면 메서드 실행 이전에 캐시 삭제. false면 메서드 실행 이후 삭제

@CachePut

  • 캐시가 존재하는지 검사하지않고 항상 메서드 실행하고 결과값을 캐시에 저장. 캐시를 갱신할 때 사용한다.
  • 보통 @Cacheable과 @CachePut 을 동시에 사용하지 않는다. (실행 순서에 따라 다른 결과가 나올 수 있기에)
  • @CachePut은 캐시 생성용으로만 사용한다.

속성

  • Value, cacheName : 삭제할 캐시 명
  • key : 같은 캐시명을 사용할 때 구분되는 구분값.
  • keyGenerator
  • cacheManager
  • cacheResolver
  • condition : SpEL 표현식을 통해 특정 조건에 부합하는 경우에만 캐시 사용. 연산 조건이 true일 때문 캐시 삭제
  • unless : 메서드 결과값이 캐싱되지 않도록 하는 조건. SpEL로 표현

@Caching

  • @Cacheable, @CacheEvict, @CachePut을 여러개 지정해야하는 경우 사용.
  • 조건식이나 표현식이 다른 경우에 사용됨.
  • 여러가지의 key에 대한 캐시를 중첩적으로 삭제해야할 때 사용.

속성

  • cacheable[] : 등록할 @Cacheable 등록
  • put[] : 등록할 @CacheEvict 등록
  • evict[] : 등록할 @CachePut 등록

@CacheConfig

  • 클래스 단위로 캐시 설정을 동일하게 하는데 사용
  • 보통 CacheManager가 여러 개인 경우에 사용

속성

  • cacheNames : 캐시명
  • keyGenerator
  • cacheManater
  • cacheResolver


사용

org.springframework.cache.annotation 아래 있는 애노테이션으로, spring-boot-starter-cache 의존성을 추가해줘야한다.

compile('org.springframework.boot:spring-boot-starter-cache')
+

config 추가

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
@Configuration
+@EnableCaching
+public class CachingConfig {
+    @Bean
+    public CacheManager cacheManager() {
+        SimpleCacheManager cacheManager = new SimpleCacheManager();
+        cacheManager.setCaches(Arrays.asList(
+                new ConcurrentMapCache("article")));
+        return cacheManager;
+    }
+}
+

캐시 적용

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+
@Service
+public class ArticleService {
+    @Cacheable(value = "article")
+    public String getArticles(long articleId){
+			String article = "";
+
+			System.out.println( code +  " article을 조회합니다 ...");
+			if(articleId == 1){
+				article = "첫번째 아티클"
+			}
+			else if(articleId == 2){
+				article = "두번째 아티클";
+			}
+			else if(articleId == 3){
+				article = "세번째 아티클";
+			}
+    }
+}
+


출처

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/cache/annotation/Cacheable.html

https://bob-full.tistory.com/23

https://jaehun2841.github.io/2018/11/07/2018-10-03-spring-ehcache/#Cache%EB%9E%80

https://shinsunyoung.tistory.com/50

This post is licensed under CC BY 4.0 by the author.

PathPattern과 servletPath

Content-Disposition

Comments powered by Disqus.

diff --git "a/posts/spring-\354\230\210\354\231\270\354\262\230\353\246\254\354\231\200-\354\230\244\353\245\230\355\216\230\354\235\264\354\247\200/index.html" "b/posts/spring-\354\230\210\354\231\270\354\262\230\353\246\254\354\231\200-\354\230\244\353\245\230\355\216\230\354\235\264\354\247\200/index.html" new file mode 100644 index 000000000..384461ac0 --- /dev/null +++ "b/posts/spring-\354\230\210\354\231\270\354\262\230\353\246\254\354\231\200-\354\230\244\353\245\230\355\216\230\354\235\264\354\247\200/index.html" @@ -0,0 +1,55 @@ + 예외 처리와 오류페이지 | 디피의 개발일지
Posts 예외 처리와 오류페이지
Post
Cancel

예외 처리와 오류페이지

서블릿 예외처리

서블릿의 예외처리 방식

Exception

WAS <- 서블릿 컨테이너 <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러(예외발생)

  • 웹 애플리케이션에서 발생한 예외를 잡지 못하면, 예외는 서블릿 컨테이너에까지 전달된다.
  • 서블릿 컨테이너는 넘어온 예외를 기준으로 등록된 오류페이지를 조회한다. 등록된 것이 없다면 기본으로 등록되어있는 500 Internal Server Error 페이지를 클라이언트로 반환한다.

response.sendError()

WAS <- 서블릿 컨테이너(sendError 호출 기록 확인) <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러(예외발생)

  • response.sendError()를 호출하면 response 내부에는 오류가 발생했다는 상태를 저장하고, 서블릿 컨테이너는 고객에게 응답하기 전에 response에 sendError()가 호출되었는지 확인한다. 호출되었다면 설정한 HTTP 오류코드에 맞추어 오류 페이지를 보여준다.


서블릿 오류화면 등록

스프링 부트에서 서블릿 컨테이너는 스브링 부트를 통해 실행되므로 스프링 부트가 제공하는 기능을 사용하여 서블릿 오류 페이지를 등록할 수 있다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+
@Component
+public class WebServerCustomizer implements WebServerFactoryCustomizer<ConfigurableWebServerFactory> {
+  @Override
+  public void customize(ConfigurableWebServerFactory factory) {
+    ErrorPage error404 = new ErrorPage(HttpStatus.NOT_FOUND, "/error-404");
+    ErrorPage errorExtra = new ErrorPage(Exception.class,  "/error-extra");
+    factory.addErrorPages(page404, pageExtra);
+  }
+}
+
  • ErrorPage()는 HTTP 상태코드 또는 Exception를 통해 생성할 수 있다.
  • Exception을 등록할 때는, 자식 타입의 오류도 함께 처리한다. Exception은 모든 예외의 조상이므로 위 코드에서는 모든 예외가 잡힌다.

등록된 에러 페이지는 함께 등록된 주소를 호출하는 방식으로 동작하기에, 해당 주소를 처리해줄 컨트롤러가 필요하다.

1
+2
+
@RequestMapping("/error-404")
+public String error404(HttpServletRequest request, HttpServletResponse response)
+


서블릿 컨테이너에서 담아주는 정보

서블릿 컨테이너는 오류페이지를 호출하기 전에 request attribute에 오류에 관한 정보를 담아준다. 그 정보는 다음과 같다.

  • javax.servlet.error.exception : 예외
  • javax.servlet.error.exception_type : 예외 타입
  • javax.servlet.error.message : 오류 메시지
  • javax.servlet.error.request_uri : 클라이언트 요청 URI
  • javax.servlet.error.servlet_name : 오류가 발생한 서블릿 이름
  • javax.servlet.error.status_code : HTTP 상태 코드


DispatcherType : 오류시 필터 넘어가기

서블릿 컨테이너로 넘어온 에러는 다음과 같은 과정을 거친다

  1. 서블릿 컨테이너 <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러(예외발생)

  2. 서블릿 컨테이너 -> 필터 -> 서블릿 -> 인터셉터 -> 컨트롤러(error-404) -> view

하지만 필터를 오류화면을 가져오는 과정에서까지 반복할 필요는 없을 것이다. 서블릿은 이런 문제를 해결하기 위해 DispatcherType을 제공한다.

DispatcherType은 request에 들어있는 정보로 request.getDispatcherType()으로 조회가능하다. DispatcherType에는 다음과 같은 것들이 있다.

  • FORWARD : 서블릿에서 다른 서블릿이나 JSP를 호출할 때

    RequestDispatcher.forward(request, response)

  • INCLUDE : 서블릿에서 다른 서블릿이나 JSP의 결과를 포함할 때

    RequestDispatcher.include(request, response)

  • REQUEST : 클라이언트 요청

  • ASYNC : 서블릿 비동기 호출

  • ERROR : 오류 요청

이 DispatcherType을 활용하여 필터를 등록할 때 DispatcherType 설정을 추가해주면 원하는 DispatcherType일 때만 필터를 호출하도록 설정가능하다

1
+2
+3
+4
+5
+6
+7
+8
+9
+
@Configuration
+public class WebConfig implements WebMvcConfigurer {
+	@Bean
+  public FilterRegistrationBean logFilter() {
+    ...
+    filterRegistrationBean.setDispatcherTypes(DispatcherType.REQUEST,DispatcherType.ERROR);
+    ...
+  }
+}
+

오류시 인터셉터 넘어가기

인터셉터는 스프링에서 제공하는 기능이므로 DispatcherType과 무관하게 항상 호출된다. 하지만 다음과 같이 인터셉터를 등록할 때 오류페이지로 가는 호출을 excludePathPatterns에 등록하면 같은 효과를 얻을 수 있다.

1
+2
+3
+4
+5
+6
+7
+
@Override
+public void addInterceptors(InterceptorRegistry registry) {
+		registry.addInterceptor(new LogInterceptor())
+      .order(1)
+      .addPathPatterns("/**")
+      .excludePathPatterns("/error-page/**");
+}
+


스프링부트 예외처리

서블릿에 오류페이지를 등록할 때는 다음과 같은 과정을 거쳤다.

  • WebServerCustomizer를 만듦
  • 예외 종류에 따라 ErrorPage를 추가함
  • 예외 처리용 컨트롤러를 만듦

하지만 스프링 부트에서는 위와 같은 과정이 필요없다.

오류페이지 등록

스프링부트에서는 아래를 기본으로 제공한다.

  • ErrorPage를 자동으로 등록한다. /error라는 경로로 기본 오류페이지를 설정하고, 상태코드나 예외를 설정하지 않으면 기본 오류페이지로 사용한다.
    • 서블릿 밖으로 에외가 발생하거나, response.sendError()가 호출되면 모든 오류는 /error를 호출한다.
    • ErrorMvcAutoConfiguration 라는 클래스가 오류 페이지를 자동으로 등록하는 역할을 한다.
  • BasicErrorController라는 스프링 컨트롤러를 자동으로 등록한다.
    • ErrorPage에서 등록한 /error를 매핑해서 처리하는 컨트롤러다

따라서 스프링부트에서는 /error 경로에 오류 페이지 파일을 만들어서 넣어두기만 하면 알아서 오류페이지가 클라이언트로 전달된다.이때 뷰 선택은 BasicErrorController에서 일어나고, 아래와 같은 우선순위로 일어난다.

  1. 뷰 템플릿
    • resources/templates/error/500.html
    • resources/templates/error/5xx.html. : 500번대 오류 처리
  2. 정적리소스(static,public)
    • resources/static/error/400.html
    • resources/static/error/404.html
    • resources/static/error/4xx.html : 400번대 오류처리
  3. 적용 대상이 없을 때 뷰 이름(error)
  • resources/templates/error.html


BasicErrorController가 제공하는 정보들

BasicErrorController는 다음 정보를 model에 담아서 뷰에 전달한다.

  • timestamp : 발생 시각
  • path : 클라이언트 요청 경로
  • status : 에러 상태
  • message : 에러 메세지
  • error : 에러 코드
  • exception : 발생한 예외
  • errors : 발생한 에러
  • trace : 예외 trace

하지만 위와 같은 정보를 클라이언트에 노출하는 건 좋지 않다.

application.properties에서 다음과 같이 model에 정보를 담을지 말지 선택할 수 있다.

  • server.error.include-exception
  • server.error.include-message
  • server.error.include-stacktrace
  • server.error.include-binding-errors


스프링 부트 오류 관련 옵션

  • server.error.whitelabel.enabled : 오류 처리화면을 못 찾을 시, 스프링 whitelabel 오류 페이지 적용
  • server.error.path : 오류 페이지 경로. 스프링이 자동 등록하는 서블릿 글로벌 오류 페이지 경로와 BasicErrorController 오류 컨트롤러 경로에 함께 사용된다.


BasicErrorController 확장 방법

스프링 부트에서 자동등록하는 에러 공통처리 컨트롤러의 기능을 변경하고 싶으면 다음과 같이 하면 된다.

  • ErrorController 인터페이스를 상속받아서 구현
  • BasicErrorController 상속받아 기능 추가

출처

This post is licensed under CC BY 4.0 by the author.

스프링 MVC 구조

컨트롤러 매개변수/반환 타입 유형

Comments powered by Disqus.

diff --git "a/posts/spring-\354\273\250\355\212\270\353\241\244\353\237\254-\353\247\244\352\260\234\353\263\200\354\210\230-\353\260\230\355\231\230-\355\203\200\354\236\205-\354\234\240\355\230\225/index.html" "b/posts/spring-\354\273\250\355\212\270\353\241\244\353\237\254-\353\247\244\352\260\234\353\263\200\354\210\230-\353\260\230\355\231\230-\355\203\200\354\236\205-\354\234\240\355\230\225/index.html" new file mode 100644 index 000000000..67d52f18b --- /dev/null +++ "b/posts/spring-\354\273\250\355\212\270\353\241\244\353\237\254-\353\247\244\352\260\234\353\263\200\354\210\230-\353\260\230\355\231\230-\355\203\200\354\236\205-\354\234\240\355\230\225/index.html" @@ -0,0 +1,177 @@ + 컨트롤러 매개변수/반환 타입 유형 | 디피의 개발일지
Posts 컨트롤러 매개변수/반환 타입 유형
Post
Cancel

컨트롤러 매개변수/반환 타입 유형

요청

파라미터(쿼리, form 데이터)

HttpServletRequest

1
+2
+
@RequestMapping("/request-param-v1")
+public void requestParamV1(HttpServletRequest request, HttpServletResponse response)
+

HttpServletRequest에서 조회해서 꺼내올 수 있다.

@RequestParam

1
+2
+3
+4
+
 @RequestMapping("/request-param-v2")
+ public String requestParamV2(
+            @RequestParam("username") String memberName,
+            @RequestParam("age") int memberAge) {
+

이때 파라미터 이름과, 매개변수에 쓰인 변수명이 같다면 다음과 같이 생략할 수있다.

1
+2
+3
+4
+
@RequestMapping("/request-param-v3")
+public String requestParamV3(
+            @RequestParam String username,
+            @RequestParam int age) {
+

또한 만약 String, int, Integer와 같이 단순 타입이면 @RequestParam을 생략할 수도 있다.

1
+2
+
@RequestMapping("/request-param-v4")
+public String requestParamV4(String username, int age)
+

but 너무 없애면 과할 수도 있다는걸 생각하자.

번외로 다음과 같이 required 옵션을 주면 해당 파라미터가 없을 때 400 예외를 내도록 할 수 있다. defaultValue도 줄 수 있다.

1
+2
+3
+4
+
@RequestMapping("/request-param-required")
+public String requestParamRequired(
+          @RequestParam(required = true, defaultValue = "guest") String username,
+          @RequestParam(required = false, defaultValue = "-1") int age)
+

주의

  • 파라미터 이름만 사용 : /request-param?username= –> 이 경우는 빈문자로 통과한다.
  • primitive 타입에 null 입력 : /request-param –> 이 경우에는 required=false 로 지정된 매개변수엔 null이 들어가는데, primitive 타입엔 null 할당이 불가능하므로 500 에러가 난다.
  • defaultValue + 빈문자 : /request-param?username= –> 빈문자도 defaultValue가 적용된다.

다음과 같이 맵으로 조회하는 방법도 있다.

1
+2
+3
+4
+5
+6
+
@RequestMapping("/request-param-map")
+public String requestParamMap(@RequestParam Map<String, Object> paramMap)
+
+// "?value=첫번째&value=두번째" 처럼 여러개가 들어올 땐 MultiValueMap 사용
+@RequestMapping("/request-param-map")
+public String requestParamMap(@RequestParam MultiValueMap<String, Object> paramMap)
+

@ModelAttribute

파라미터를 객체의 형태로 받을 수 있도록 하는 애노테이션이다.

1
+2
+
@RequestMapping("/model-attribute-v1")
+public String modelAttributeV1(@ModelAttribute HelloData helloData)
+

@ModelAttribute 동작원리

  1. HelloData 객체를 생성한다.
  2. 요청 파라미터의 이름으로 HelloData 객체의 프로퍼티를 찾는다. 그리고 해당 프로퍼티의 setter를 호출해서 값을 바인딩한다.

바인딩 오류

  • 타입이 다르거나, primitive 타입에 null을 대입하는 등 바인딩 오류가 발생할 시 스프링 MVC는 BindException을 발생시킨다.

@ModelAttribute 는 다음과 같이 생략할 수 있다. 하지만, 너무 생략하면 이해하기 어려운 코드가 될 수 있다.

1
+2
+
@RequestMapping("/model-attribute-v2")
+public String modelAttributeV2(HelloData helloData)
+


HTTP 요청 메시지 - 단순 텍스트

HttpServletRequest

1
+2
+3
+4
+5
+
@PostMapping("/request-body-string-v1")
+public void requestBodyString(HttpServletRequest request) {
+  ServletInputStream inputStream = request.getInputStream();
+  String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
+}
+

HttpServletRequest를 통해 http request 메시지 전체를 받고, InputStream을 꺼내어 body를 만드는 방법이 있다. 많이 번거로운 방법.

InputStream

1
+2
+3
+4
+
@PostMapping("/request-body-string-v2")
+public void requestBodyStringV2(InputStream inputStream) {
+    String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
+}
+

바로 InputStream을 꺼내는 방법도 가능하다.

HttpEntity

1
+2
+3
+4
+
  @PostMapping("/request-body-string-v3")
+  public HttpEntity<String> requestBodyStringV3(HttpEntity<String> httpEntity) {
+      String messageBody = httpEntity.getBody();
+  }
+

HttpEntity로 body를 직접 꺼내올 수 있다. (header도 조회가능)

RequestEntity는 HttpEntity를 상소받아 만든 객체이다. 따라서 마찬가지로 body를 바로 가져올 수 있다.

@RequestBody

1
+2
+
@PostMapping("/request-body-string-v4")
+public String requestBodyStringV4(@RequestBody String messageBody)
+

애노테이션 기반으로 바로 가져올 수도 있다.


HTTP 요청 메시지 - JSON

HttpServletRequest

1
+2
+3
+4
+5
+6
+7
+8
+
public void requestBodyJsonV1(HttpServletRequest request,
+				HttpServletResponse response) throws IOException {
+  ServletInputStream inputStream = request.getInputStream();
+  String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
+
+  ObjectMapper objectMapper = new ObjectMapper();
+  HelloData data = objectMapper.readValue(messageBody, HelloData.class);
+}
+

InputStream을 가져와 ObjectMapper로 변환시켜주면 된다. 매우 번거로움

@RequestBody

1
+2
+
@PostMapping("/request-body-json-v3")
+public String requestBodyJsonV3(@RequestBody HelloData data)
+

애노테이션 기반으로 객체 형식으로 바로 가져올 수 있다.

이때 @RequestBody 애노테이션은 생략 불가능하다. 그 이유는 스프링에서는 @ModelAttribute, @RequestParam과 같은 애노테이션을 생략할 시 다음과 같은 규칙을 적용하기 때문이다.

  1. String, int, Integer 같은 단순 타임 => @RequestParam 적용
  2. 나머지 => @ModelAttribute 적용

따라서 @RequestBody를 생략하면 HelloData는 단순타입이 아니므로 @ModelAttribute가 적용되기에 @RequestBody는 생략해서는 안된다.

HttpEntity

1
+2
+3
+4
+
@PostMapping("/request-body-json-v4")
+public String requestBodyJsonV4(HttpEntity<HelloData> httpEntity) {
+	HelloData data = httpEntity.getBody();
+}
+


응답

정적 리소스, 뷰 템플릿

ModelAndView

1
+2
+3
+4
+5
+
@RequestMapping("/response-view-v1")
+public ModelAndView responseViewV1() {
+	ModelAndView mav = new ModelAndView("response/hello").addObject("data", "hello!");
+	return mav;
+}
+

String 반환

1
+2
+3
+4
+5
+
@RequestMapping("/response-view-v2")
+public String responseViewV2(Model model) {
+	model.addAttribute("data", "hello!!");
+	return "response/hello";
+}
+

void 반환

1
+2
+3
+4
+
@RequestMapping("/response/hello")
+public void responseViewV3(Model model) {
+	model.addAttribute("data", "hello!!");
+}
+

요청 URL을 참고하여 논리 뷰 이름으로 사용한다.

요청 URL : /response/hello

실행 : templates/response/hello.html

하지만 이 방법은 가시성이 떨어지기에 웬만하면 다른 방법을 쓰자.

Body 응답

HttpServletResponse

1
+2
+3
+
public void responseBodyV1(HttpServletResponse response) throws IOException {
+	response.getWriter().write("ok");
+}
+

ResponseEntity

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+
public ResponseEntity<String> responseBodyV2() {
+	return new ResponseEntity<>("ok", HttpStatus.OK);
+}
+
+public ResponseEntity<HelloData> responseBodyJsonV1() {
+	HelloData helloData = new HelloData();
+	helloData.setUsername("userA");
+	helloData.setAge(20);
+	return new ResponseEntity<>(helloData, HttpStatus.OK);
+}
+

@ResponseBody

1
+2
+3
+4
+5
+6
+
@ResponseStatus(HttpStatus.OK)
+@ResponseBody
+@GetMapping("/response-body-string-v3")
+public String responseBodyV3() {
+	return "ok";
+}
+

@ResponseBody 로 응답을 보낼 때는 Status 설정을 애노테이션으로 해줘야한다. 또한 컨트롤러에 @RestController를 등록하면, 해당 컨트롤러에 모든 메서드에 @ResponseBody가 적용된다.

주의 : @ResponseBody를 빼고, String을 응답하면 해당 경로에 있는 정적리소스를 반환한다.

This post is licensed under CC BY 4.0 by the author.

예외 처리와 오류페이지

ModelAttribute와 NoArgsConstructor 추가 시 아무것도 안들어가는 이슈

Comments powered by Disqus.

diff --git "a/posts/spring-\354\273\250\355\212\270\353\241\244\353\237\254-\355\214\214\353\235\274\353\257\270\355\204\260-\355\201\264\353\236\230\354\212\244\354\231\200-@NoArgsConstructor-\354\235\264\354\212\210/index.html" "b/posts/spring-\354\273\250\355\212\270\353\241\244\353\237\254-\355\214\214\353\235\274\353\257\270\355\204\260-\355\201\264\353\236\230\354\212\244\354\231\200-@NoArgsConstructor-\354\235\264\354\212\210/index.html" new file mode 100644 index 000000000..e11427fa2 --- /dev/null +++ "b/posts/spring-\354\273\250\355\212\270\353\241\244\353\237\254-\355\214\214\353\235\274\353\257\270\355\204\260-\355\201\264\353\236\230\354\212\244\354\231\200-@NoArgsConstructor-\354\235\264\354\212\210/index.html" @@ -0,0 +1,49 @@ + ModelAttribute와 NoArgsConstructor 추가 시 아무것도 안들어가는 이슈 | 디피의 개발일지
Posts ModelAttribute와 NoArgsConstructor 추가 시 아무것도 안들어가는 이슈
Post
Cancel

ModelAttribute와 NoArgsConstructor 추가 시 아무것도 안들어가는 이슈

제목 그대로 다음과 같이 @NoArgsConstructor 애노테이션을 @ModelAttribute로 적용되는 컨트롤러 파라미터 클래스에 추가하니 제대로 쿼리를 날려도 아무것도 안들어가는 이슈가 발생했다.

1
+2
+
@GetMapping("/article")
+public ResponseEntity getArticles(@ModelAttribute SelectArticlesQuery query)
+
1
+2
+3
+4
+5
+6
+7
+8
+9
+
@Getter
+@AllArgsConstructor
+@NoArgsConstructor
+@ToString
+public class SelectArticlesQuery {
+	private Integer offset;
+	private Integer limit;
+	private Integer category;
+}
+
요청 : /article?limit=20&offset=10
+toString() => SelectArticlesQuery(offset=null, limit=null, category=null)
+


이 이슈에 대한 해답은 이 포스트에서 찾을 수 있었다.

Spring은 URL 파라미터 또는 POST Form Data 형태의 파라미터를 위와 같은 특정 클래스로 자동으로 바인딩해주는데 이때 반환되는 객체를 커맨드 객체라고 부른다. 그리고 커맨드 객체의 매핑규칙은 다음과 같다.

  1. NoArgsConstructor와 AllArgsConstructor 둘 다 있는 경우
    • NoArgsConstructor를 호출하고, setter 호출하여 param을 필드에 각각 초기화 한다.
  2. AllArgsConstructor만 있는 경우
    • AllArgsConstructor을 호출하여 param을 필드에 각각 초기화 한 뒤, setter 호출하여 param을 필드에 각각 다시 초기화하여 덮어씌운다.

따라서 만약 @NoArgsConstuctor가 있다면 @Setter를 통해 setter 메서드를 선언해주어야 필드를 초기화 할 수 있게 된다. 따라서 위 예시에서 SelectArticlesQuery을 다음과 같이 변경하면 문제는 해결된다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+
@Getter
+@Setter // 추가
+@AllArgsConstructor
+@NoArgsConstructor
+@ToString
+public class SelectArticlesQuery {
+	private Integer offset;
+	private Integer limit;
+	private Integer category;
+}
+
1
+2
+
요청 : /article?limit=20&offset=10
+toString() => SelectArticlesQuery(offset=10, limit=20, category=null)
+

참고

  • https://steady-coding.tistory.com/489
This post is licensed under CC BY 4.0 by the author.

컨트롤러 매개변수/반환 타입 유형

AOP

Comments powered by Disqus.

diff --git "a/posts/spring-\355\214\214\354\235\274-\354\227\205\353\213\244\354\232\264\353\241\234\353\223\234/index.html" "b/posts/spring-\355\214\214\354\235\274-\354\227\205\353\213\244\354\232\264\353\241\234\353\223\234/index.html" new file mode 100644 index 000000000..d097a301e --- /dev/null +++ "b/posts/spring-\355\214\214\354\235\274-\354\227\205\353\213\244\354\232\264\353\241\234\353\223\234/index.html" @@ -0,0 +1,161 @@ + 스프링 파일업다운로드 | 디피의 개발일지
Posts 스프링 파일업다운로드
Post
Cancel

스프링 파일업다운로드

[spring] 파일 업/다운로드

HTML 폼 전송 방식

HTML에서 폼을 전송하는 방식(Content-Type)에는 다음 두가지가 있다.

  • application/x-www-form-urlencoded : 문자와 같은 데이터를 키와 함께 전송하는 방식

    • ex) username=Kim&age=20
  • multipart/form-data : 여러 개의 데이터를 파트별로 나누어 전송하는 방식. 파일과 같은 바이너리 데이터를 전송할 때 쓰임.

    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +14
    +15
    +16
    +
    Content-Type: multipart/form-data; boundary=------XXX
    +
    +------XXX
    +Content-Disposition: form-data; name="username"
    +
    +kim
    +------XXX
    +Content-Disposition: form-data; name="age"
    +
    +20
    +------XXX
    +Content-Disposition: form-data; name="file"; filename="intro.png"
    +Content-Type: Image/png
    +
    +[이미지 바이너리 데이터]
    +------XXX--
    +


파일 업로드

서블릿 파일 업로드

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+
@PostMapping("/upload")
+public String saveFileV1(HttpServletRequest request) throws ServletException, IOException {
+	Collection<Part> parts = request.getParts();		// 멀티파트 데이터 가져오기
+
+	for (Part part : parts) {
+      //데이터 읽기
+      InputStream inputStream = part.getInputStream();
+    	String body = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
+
+      //파일에 저장하기
+      if (StringUtils.hasText(part.getSubmittedFileName())) {
+      	String fullPath = fileDir + part.getSubmittedFileName(); 		// 클라이언트가 전달한 파일명
+				part.write(fullPath);		// 주어진 위치에 저장
+			}
+  }
+	return "upload-form";
+}
+

request.getPart()를 통해 멀티파트 데이터를 가져오고, 각 파트별로 part.write() 메서드를 사용하면 인자로 넣은 주소에 저장된다.

하지만 위와 같은 방법은 ` HttpServletRequest`를 인자로 받아와야하며, 파일을 각자 구분하기 위해 여러 코드를 넣어야한다.


스프링 파일 업로드

1
+2
+3
+4
+5
+6
+7
+8
+9
+
@PostMapping("/upload")
+public String saveFile(@RequestParam String itemName, @RequestParam MultipartFile file) throws IOException {
+
+	if (!file.isEmpty()) {
+		String fullPath = fileDir + file.getOriginalFilename(); // 파일 원제목 가져오기
+		file.transferTo(new File(fullPath)); // 주어진 위치로 파일 저장
+	}
+	return "upload-form";
+}
+

스프링에서는 멀티파트 파일을 저장하기 위핸 MultipartFile를 제공한다. 따라서 매개변수의 이름을 들어올 파일의 이름과 맞춰준다면 자동으로 해당 매개변수에 파일을 넣어준다.

다음과 같이 @ModelAttribute를 사용하여 받을 수도 있다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+
// MultipartFile
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+public class MultipartTest {
+	private String itemName;
+	private MultipartFile file;
+}
+
1
+2
+
@PostMapping("/upload")
+public String saveFile(@ModelAttribute MultipartTest item) // work
+


파일 다운로드

이미지 다운로드

<img> 태그로 조회 시 내려주는 방법. UrlResource로 이미지 파일을 읽어서 body에 넣어 반환한다.

1
+2
+3
+4
+5
+
@ResponseBody
+@GetMapping("/images/{filename}")
+public Resource downloadImage(@PathVariable String filename) throws MalformedURLException {
+	return new UrlResource("file:" + fileStore.getFullPath(filename));
+}
+

파일 다운로드

Content-Disposition 헤더에 attachment; filename="파일 이름" 값을 넣고, UrlResource를 body에 담아 내려준다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+
@GetMapping("/attach/{itemId}")
+public ResponseEntity<Resource> downloadAttach(@PathVariable Long itemId) throws MalformedURLException {
+  	// db에 저장된 파일 데이터 가져오기.
+		Item item = itemRepository.findById(itemId);
+		String storeFileName = item.getAttachFile().getStoreFileName();
+		String uploadFileName = item.getAttachFile().getUploadFileName();
+
+		UrlResource resource = new UrlResource("file:" + fileStore.getFullPath(storeFileName));
+
+		String encodedUploadFileName = UriUtils.encode(uploadFileName, StandardCharsets.UTF_8);
+
+		return ResponseEntity.ok()
+			.header(HttpHeaders.CONTENT_DISPOSITION,
+              "attachment; filename=\"" + encodedUploadFileName + "\"")
+			.body(resource);
+	}
+

이미지 다운로드 때와 다른 점은 Content-Disposition 헤더가 설정되었다는 점이다.

Content-Disposition은 HTTP response body에 오는 컨텐츠의 기실/성향을 알려주는 속성으로, default 값은 web에 전달되는 data라고 생각하면된다.

특수한 경우는 Content-Disposition에 attachment를 주는 경우로, 이때 filename과 함께 주면 body에 오는 값을 다운로드 받으라는 뜻이 된다.

https://lannstark.tistory.com/8


스프링 멀티파트 설정

다음과 같이 application.properties에서 멀티파트 데이터에 관한 설정을 할 수 있다.

1
+2
+3
+4
+5
+6
+
# 한 파일의 최대 용량
+spring.servlet.multipart.max-file-size=5MB
+# 한 multipart 요청의 최대 크기
+spring.servlet.multipart.max-request-size=10MB
+# multipart 처리를 할 것인가. (false 시 멀티파트 데이터를 처리하지 않음)
+spring.servlet.multipart.enabled=true
+

출처

김영한의 스프링 MVC 강의 2편

This post is licensed under CC BY 4.0 by the author.

Spring 파라미터 Validation

blue-green 배포

Comments powered by Disqus.

diff --git a/posts/st-algorithm/index.html b/posts/st-algorithm/index.html new file mode 100644 index 000000000..7fea9793d --- /dev/null +++ b/posts/st-algorithm/index.html @@ -0,0 +1 @@ + LocateC | 디피의 개발일지
Posts LocateC
Post
Cancel

LocateC

개발 계기

  • 코로나 전에 커피를 마시면서 자주 길거리를 걸어다녔는데, 항상 다 마시고 남은 빈 통만 들고 한참을 쓰레기통을 찾아다녔던 기억이 있었다.
  • 그런데 학과 내에서 학교 생활에 도움을 줄 서비스를 개발하는 공모전을 하였고, 위 기억을 되살려서 학교 내 흡연장소, 쓰레기통의 위치를 알려주는 서비스를 개발해보기로 하였다.

서비스 소개

  • 교내에 위치한 흡연장소, 쓰레기통의 위치를 사진과 함께 확인 가능.
  • 가장 가까운 곳에 위치한 흡연장소/쓰레기통을 안내
  • 사용자는 관리자에게 서버에 등록되지않은 새로운 위치를 추가하도록 요청을 보낼 수 있음.
  • 관리자는 별도의 웹페이지를 통해 사용자의 요청을 처리하고, 이미 등록된 리스트도 관리할 수 있음.
  • web, android, ios 크로스 플랫폼 지원

서비스 링크

custom hook

  • 웹에서의 반응형/ 웹, 앱에서의 다크모드 구현을 위해 처음으로 custom hook 을 구현하여 사용하였다.
  • 일전엔 일일히 하드코딩을 통해 구현하였던 반응형을 custom hook으로 구현하니 훨씬 코드가 깔끔해졌다.

다크모드

  • 처음으로 앱/웹에서 다크모드를 지원하게 되었다.
  • 다크모드의 영향을 받는 컴포넌트들을 별도의 파일로 분류하여 관리하였다.

patch-package

  • 웹에서 사용한 지도라이브러리에 문제가 있어, 처음으로 모듈을 수정하여 사용할 수 있도록 해주는 patch-package 를 이용하여, 모듈을 직접 수정하였다.

어려웠던 부분

typescript

  • typescript를 배운지 그리 오래되지 않아, 타입오류를 해결하는 것에 어려움이 있었다.

크로스 플랫폼에서의 지도 라이브러리 사용

  • 지도 라이브러리로 앱에서는 react-native-maps, 웹에서는 react-native-web-maps 라이브러리를 사용하였는다.

  • 앱에서 사용한 react-native-maps는 높은 완성도를 가지고 있어 활용에 크게 문제는 없었다.

  • 하지만, 웹에서 사용한 react-native-web-maps 은 관리가 되어있지 않은 오래된 라이브러리였기에 몇몇 컴포넌트가 지원이 되지 않는 문제들이 있었다.

  • 이를 해결하기 위해 patch-package를 사용하여 모듈을 수정하여 사용하였다.

결과

  • 이 서비스로 학과 내 공모전에 참가하여 2위를 수상하였다.

UI UX

img_1

img_2

img_3

img_4

This post is licensed under CC BY 4.0 by the author.

Yourlist

flutter (rn 개발자를 위한 정리 1)

Comments powered by Disqus.

diff --git a/posts/subnetting/index.html b/posts/subnetting/index.html new file mode 100644 index 000000000..2a36f3b73 --- /dev/null +++ b/posts/subnetting/index.html @@ -0,0 +1 @@ + 서브네팅 | 디피의 개발일지
Posts 서브네팅
Post
Cancel

서브네팅

서브네팅

사용가능한 IP 주소 범위를 분할하여 사용하는 것

특징

  • 2의 배수로, 같은 크기로 분할


주소할당

서브넷 마스크는 다음과 같음

  • 네트워크 주소사용부분 모두 1 + 호스트 주소 사용부분 모두 0

각 서브넷에서는 다음과 같이 2개의 주소는 예약되어있음

  • 네트워크 주소 : (기존 네트워크 부 + 분할부분 매핑 주소) + (남은 호스트부분 모두 0)
  • 브로드캐스트 주소 : (기존 네트워크 부 + 분할 매핑 주소) + (남은 호스트 부분 모두 1)

따라서 서브넷에서 사용가능한 주소 범위는, 네트워크 주소와 브로드캐스트 주소를 제외한 나머지 부분이다. 그 나머지 부분은 다음과 같이 구성

  • 라우터 주소 : 1개 예약
  • 컴퓨터 주소 할당 : 나머지

즉, 한 서브넷에서 컴퓨터 주소 할당에 사용가능한 주소는 네트워크주소, 브로드캐스트 주소, 라우터 주소를 제외한 나머지 부분이다.

This post is licensed under CC BY 4.0 by the author.
diff --git a/posts/summary/index.html b/posts/summary/index.html new file mode 100644 index 000000000..292c3e8c4 --- /dev/null +++ b/posts/summary/index.html @@ -0,0 +1 @@ + 학습 내용 요약 | 디피의 개발일지
Posts 학습 내용 요약
Post
Cancel

학습 내용 요약

프론트엔드

Virtual DOM

리액트의 작동방식은 상태변화 시, 그 컴포넌트와 하위 컴포넌트를 모두 교체하기에 많은 DOM 변화가 일어난다.

이때 리액트에서는 DOM을 추상화한 객체인 Virtual DOM을 변경하고, 변경이 완료되면 실제 DOM과의 차이점을 확인한다음 한번의 리렌더로 해결한다.

Virtual DOM은 메모리에 존재하여 메모리를 차지한다는 단점이 있다.

리액트도 DOM API를 사용하여 뷰를 변화시키기에, DOM API보다 더 빠르진 않다. Virtual DOM은 리액트를 빠르게 하기 위한 수단.

링크


React key

리액트에서 리스트를 만들 시, key를 사용하라는 안내문이 뜬다.

그 이유는 리스트에 변화가 생겨 리렌더링이 일어날 시, 변화 전후에 key가 같은 요소는 서로 같은 요소로 받아들여 리렌더링에서 제외시키기 때문에 성능적으로 큰 이점이 있다.

따라서 리스트가 고정되어 변경이 일어나지 않는 경우가 아니라면, key를 인덱스로 사용하자 말자

링크


클래스형 컴포넌트 vs 함수형 컴포넌트

기존에는 함수형 컴포넌트에서 state생명주기함수 사용이 불가능했다.

하지만 hook의 등장으로 두가지 모두 함수형 컴포넌트에서도 사용이 가능해졌다.

생명주기 hook으로는 useEffectuseLayoutEffect가 있다. useEffect는 컴포넌트가 실제 돔에 반영 된 이후 호출되고, useLayoutEffect는 컴포넌트가 실제 돔에 반영되기 직전에 동기적으로 호출된다.

  • useEffect : componentDidMount, componentDidUpdate, componentWillUnmount(return문)
  • useLayoutEffect : shouldComponentUpdate

함수형 컴포넌트는 코드가 간결하고, 클래스형에 비해 메모리를 덜 사용한다는 장점이 있다.

링크


CSS 최적화

CSS는 렌더링을 막고, HTML 파싱도 막을 수 있다. 따라서 최적화가 필요하다.

CSS 사이즈를 줄이고, 꼭 필요한 Critical CSS를 인라인으로 선언하고, non-Critical CSS는 비동기로 불러올 수 있다.

애니메이션을 최적화하기 위해선, 레이아웃을 변경하는 대신 transform을 사용하는 것이 더 빠르다.

  • 그 원리는 CSS는 styles -> layout -> Paint -> composite 순으로 그려지는데, 앞 순서 속성을 변경하면 뒷순서를 다시 그리기 때문이다. 따라서 composite에 해당하는 transform, opacity를 변경하는 것이 가장 빠르다.
  • will-change를 통해 애니메이션을 적용할 속성을 미리 브라우저에게 알려주어 GPU 가속을 사용할 수 있다.
    • 너무 많이 사용하면 기기자원소모가 심함.

contain 속성으로 어떤 요소와 그 하위요소가 전체 문서트리와 무관하다는 것을 알려주는 방법도 있다.

variable font로 폰트파일 크기를 줄일 수도 있다.

링크



Javascript

ES6

  • let, const 키워드
  • 템플릿 리터럴 : ``
  • 객체 리터럴
  • 화살표함수
  • 구조분해 할당
  • Promise
  • Class
  • 모듈시스템


Var vs Let

  • 재선언 : 가능 vs 불가능
  • hoisting : function-scoped vs block-scoped
    • 하지만 let에서는 선언된 위치 이전에는 사용할 수 없음.


화살표함수

  • 조금 더 간편하게 사용할 수 있음.
  • 일반 함수내부의 this는 전역 객체로 정의됨. 화살표함수에서는 상위 스코프의 this로 정의됨.
    • 단 일반 함수도 특정 객체의 메소드로 호출될 경우, 그 호출한 객체의 this가 바인딩 된다.
  • 이벤트리스너의 콜백함수로는 사용하면 안된다. this가 전역 this로 바인딩 되기 때문.

링크


실행컨텍스트

코드의 실제 진행상황을 추적하는데 필요한 정보들을 모아둔 구조. 함수가 호출되면 생성되고 종료되면 사라짐.

실행컨텍스트에는 지역메모리실행문이 있음.

실행컨텍스트는 콜스택에 차례대로 쌓임. 자바스크립트 엔진이 현재 실행중인 함수의 실행컨텍스트를 추적하기 위해 쌓는다.

**스코프체인 **: 현재 실행 컨텍스트에서 변수를 찾을 수 없으면, 상위 스코프의 실행컨텍스트로 탐색범위를 옮기는 것

**클로저 ** : 함수가 함수를 반환할때, 반환되는 함수가 선언된 환경의 스코프를 기억한 상태로 반환되는 것. 상위 스코프를 탐색하기 전 클로저를 먼저 확인한다.

  • 렉시컬스코프 : 함수가 선언된 시점의 유효범위

링크


바벨

자바스크립트 트랜스파일러로, 주로 모던 자바스크립트를 호환성을 위해 예전 문법으로 변환할 때 사용한다.

플러그인과 프리셋으로 설정을 할 수 있다.

하지만 바벨을 사용해도 변환할 수 없는 모던자바스크립트 코드들이 있다.(Promise 등) 이를 변환하기 위해서는 Polyfill을 사용하여야한다. Polyfill은 바벨에서 설정해도 되고, 웹팩에서 설정해도 된다.

링크



CS

선언형 프로그래밍

How를 감추고, What을 노출하는 방식의 추상화. 명령형 코드에서 리팩토링하여 생성할 수 있다.

명령형 코드를

  • what을 적절히 인터페이스에 노출시키고
  • How를 내부에 숨기고
  • 언제 어디서 불러와도 동일한 결과가 나와서 재사용하기 편하게 추상화한다면

선언적 코드이다.

명령형 코드를 해석할 땐 시간 흐름에 따라 해석해야한다. 하지만 선언적코드는 흐름에 따라 읽지 않아도 되기에, 디버깅에 용이하다. 코드 자체에 어떤 일이 벌어지는지 설명하기 떄문에 추론하기 좋다

또한 언제 불어와도 동일한 결과를 내니, 재사용하기도 좋다.

링크


SSR

  • CSR은 리액트가 포함된 자바스크립트를 다운 받고, 리액트를 실행시켜 화면을 렌더링한다. 이때 리액트는 앱 전체를 실행시키기에, 리액트 코드가 방대하다면 그만큼 사용자는 빈 화면에 방치된다.
  • SSR은 서버에서 미리 첫번째 화면을 생성하고 그 생성된 첫번째 화면의 html, css를 사용자에게 보내는 방법. 사용자 브라우저는 이후 js를 다운 받고 리액트를 실행시켜 인터랙션이 가능하게 한다.
  • 따라서 CSR을 사용하면 사용자가 첫 화면을 보는 시점이 SSR에 비해 늦어지게 된다. 또한 SEO에도 문제가 있다.

  • SSR에도 단점이 있다. 첫페이지를 구성한 이후 다른 페이지로 넘어갈 때, CSR에서는 그 다른 페이지를 렌더링하기 위한 코드가 이미 있고 필요한 부분만 다시 렌더링하면 되기에 다른 페이지로 넘어갈 때는 CSR이 더 빠르다. 또 SSR의 경우 매번 서버로 페이지를 요청하기에 서버의 부담이 크다. 이를 해결하기 위해 CSR와 SSR를 결합하는 방식을 채택하여 해결하는 걸로 알고 있다.

  • 또 기존 리액트에서 SSR에서는 html 코드를 받아오고 리액트 코드를 입히는 hydration 동작을 앱 전체 단위로 진행시켜서 사용자가 최초로 인터랙션을 하는데까지 시간이 오래 걸렸는데, 이번에 react 18 업데이트에 포함된 Suspense API를 통해 컴포넌트 단위로 hydration을 진행시켜 우선도가 높은 컴포넌트부터 먼저 인터랙션이 가능하도록 하는 발전이 있었다

Suspense API에 대해


MVC 패턴

Controller

  • 클라이언트의 요청을 받으면, 데이터를 가공하여, 그 요청에 대한 실제 업무를 수행하는 모델 컴포넌트를 호출함.
  • 모델이 업무를 마치면 그 결과를 뷰에게 전달한다.
  • 다른 컴포넌트와 달리 모델이나 뷰에 대해 알고 있어야 하며, 둘의 변경을 모니터링해야한다.

Model

  • 컨트롤러가 호출했을 때, 요청에 맞는 비즈니스 로직을 수행함. 응용프로그램에서 데이터를 처리하는 부분.
  • DB와 통신하며, 상태의 변화가 있을 때 컨트롤러에 통보해 후속조치 명령을 받는다.

View

  • 컨트롤러로부터 받은 모델의 결과값을 가지고 사용자에게 출력할 화면을 만든다.
  • 만들어진 화면은 컨트롤러로 보내고, 컨트롤러는 이를 클라이언트로 다시 전송한다.

링크


테스트 관련

유닛테스트가 무엇인지

소프트웨어의 가장 작은 단위에서, 그 부분이 예상대로 동작하는지 테스트하는 기법이다.

요구사항에 맞는 테스트 코드를 한번 작성해 놓으면, 나중에 리팩토링할 때 테스트를 실행하는 것만으로 정상적으로 동작함을 보장할 수 있기에 두려움 없이 리팩토링을 할 수 있다.


TDD가 무엇인지

테스트를 미리 작성하고 개발을 하는 방법. 실패하는 테스트 코드를 작성하고, 테스트 코드를 성공시키기 위한 실제코드를 작성한 후 리팩토링을 거치는 작업을 반복하는 개발법.

요구사항에 대한 이해도를 높이고, 테스트 코드를 통과하는 코드를 작성함으로써 코드의 정확성을 보장할 수 있다. 또한 리팩토링이 포함되어있기에 코드 품질도 높일 수 있다.

실제 코드의 작성을 마치고 테스트를 하는 것보다 개발 비용을 절감할 수 있고, 개발자의 특성 상 작업이 끝난 코드에 테스트를 붙이는 것을 싫어하기에 미리 테스트를 작성하는 것이 효율적이다.


UI 테스트를 위한 기법에는 무엇이 있는지

실제 DOM에 보여지는 걸 기준으로 테스트하는 기법(react-testing-library)과, 컴포넌트 내부 동작을 테스트(enzyme)하는 기법 두 가지가 있다.

실제 DOM에 보여지는 걸 테스트할 때는, 보여져야 할 텍스트가 제대로 보여지는지 어떤 버튼을 클릭했을 때 적절한 요소가 등장하는지를 본다.

컴포넌트 내부 동작을 확인할 때는, 매개변수와 상태가 적절한지 확인하고, 컴포넌트 내장 메소드를 직접 호출하기도 한다.


DB

DB 페이징

Offset Paging : DB의 Limit과 offset을 활용한 방법

장점

  • 구현이 간단하다.

단점

  • 비효율적이다.
  • 데이터가 누락되거나, 중복될 가능성이 존재한다.

Cursor Paging : 유니크하고 순차적인 컬럼을 기준으로 정렬하여 구현하는 방법

장점

  • 효율적이다.
  • 데이터가 누락되거나 중복될 가능성이 없다.

단점

  • 구현이 까다롭다.
  • 무한 스크롤이 아닌 경우 특수한 처리를 해줘야한다.

링크


관계 데이터모델

무결성 제약조건

  • 개체 무결성 제약조건 : 기본키를 구성하는 모든 속성은 널 값을 가질 수 없다.
  • 참조 무결성 제약조건 : 외래키는 참조할 수 없는 값을 가질 수 없다.

링크


인덱스

DBMS에서 데이터베이스 테이블의 모든 데이터를 검색하여 데이터를 찾는 것은 비효율적이기에, 데이터의 칼럼 값과 그 데이터가 저장된 레코드의 주소를 키와 값의 쌍으로 만든 것

DBMS의 인덱스는 항상 정렬된 상태를 유지하기에 원하는 값을 탐색하는 빠르지만, 추가/삭제/수정에는 느리다.

즉, 데이터베이스의 탐색성능은 높이되 저장성능은 희생시키는 방법. 따라서 모든 컬럼을 대상으로 인덱스를 생성하면 데이터 저장 성능이 크게 저하되어 역효과가 난다.

링크


트랜잭션

하나의 논리적인 작업을 수행하는 쿼리문들을 묶어놓은 것. 트랜잭션에 속한 쿼리문들은 모두 완전히 실행되거나 그렇지 않으면 원상복구해야함.

링크


This post is licensed under CC BY 4.0 by the author.

virtual DOM와 리렌더링

JS 코테 대비 메소드 정리

Comments powered by Disqus.

diff --git a/posts/this/index.html b/posts/this/index.html new file mode 100644 index 000000000..03f04fd61 --- /dev/null +++ b/posts/this/index.html @@ -0,0 +1,157 @@ + this에 대해서 | 디피의 개발일지
Posts this에 대해서
Post
Cancel

this에 대해서

this에 대해서

자바스크립트에서 모든 함수는 실행될때마다 함수 내부에 this라는 객체가 추가된다. arguments라는 유사배열 객체와 함께 함수 내부로 암묵적으로 전달되는 것이다. 그렇기 때문에 자바스크립트에서의 this는 함수가 호출된 상황에 따라 그 모습을 달리한다.


상황 1. 객체의 메서드를 호출할때

객체의 프로퍼티가 함수일 경우 메서드라고 부른다. this는 함수를 실행할 때 함수를 소유하고 있는 객체를 참조한다. 즉, 해당 메서드를 호출한 객체로 바인딩 된다. A.B일때, B함수 내부에서의 this는 A를 가리키는 것이다.

1
+2
+3
+4
+5
+6
+7
+8
+
var myObject = {
+  name: "foo",
+  sayName: function () {
+    console.log(this);
+  },
+};
+myObject.sayName();
+// console> Object {name: "foo", sayName: sayName()}
+


상황 2. 함수를 호출할때

특정 객체의 메서드가 아니라 함수를 호출하면, 해당 함수 내부코드에서 사용된 this는 전역 객체에 바인딩 된다. A.B라고 생각해보면 A가 전역객체가 되므로, B 함수 내부에서의 this는 전역 객체에 바인딩된다. 즉, A에 해당하는 특정 객체가 없다면 B에서의 this는 전역객체에 바인딩된다는 뜻이다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+
var value = 100;
+var myObj = {
+  value: 1,
+  func1: function () {
+    console.log(`func1's this.value: ${this.value}`);
+
+    var func2 = function () {
+      console.log(`func2's this.value ${this.value}`);
+    };
+    func2();
+  },
+};
+myObj.func1();
+// console> func1's this.value: 1
+// console> func2's this.value: 100
+


상황 3. 생성자 함수를 통해 객체를 생성할 때

new 키워드를 통해 생성자함수를 호출하여 객체를 만들었을때는, new 키워드를 통해 호출된 함수 내부에서의 this는 객체 자신이 된다. 이는 생성자 함수가 동작하는 방식을 통해 이해할 수 있다.

new 연산자를 통해 함수를 생성자로 호출하게 되면, 일단 빈 객체가 생성되고 this가 바인딩된다. 이 객체는 함수를 통해 생성된 객체이며, 자신의 부모인 프로토타입 객체와 연결되어있다. 그리고 return 문이 명시되어있지 않은 경우에는 this로 바인딩 된 새로 생성한 객체가 리턴된다.

1
+2
+3
+4
+5
+6
+7
+
var Person = function (name) {
+  console.log(this);
+  this.name = name;
+};
+
+var foo = new Person("foo"); // Person
+console.log(foo.name); // foo
+


상황 4. apply, call, bind를 통한 호출

상황 2에서, func2를 호출할때, func1에서의 this를 주입하기 위해서는 위 세가지 메소드를 사용할 수 있다.

  • bind : 함수를 생성할때 연결함. 매개변수는 하나씩 콤마로 구분하여 넣어줌
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+
var value = 100;
+var myObj = {
+  value: 1,
+  func1: function () {
+    console.log(`func1's this.value: ${this.value}`);
+
+    var func2 = function (val1, val2) {
+      console.log(`func2's this.value ${this.value} and ${val1} and ${val2}`);
+    }.bind(this, `param1`, `param2`);
+    func2();
+  },
+};
+
+myObj.func1();
+// console> func1's this.value: 1
+// console> func2's this.value: 1 and param1 and param2
+
  • call : 호출할때 연결함. 매개변수는 하나씩 콤마로 구분하여 넣어줌
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+
var value = 100;
+var myObj = {
+  value: 1,
+  func1: function () {
+    console.log(`func1's this.value: ${this.value}`);
+
+    var func2 = function (val1, val2) {
+      console.log(`func2's this.value ${this.value} and ${val1} and ${val2}`);
+    };
+    func2.call(this, `param1`, `param2`);
+  },
+};
+
+myObj.func1();
+// console> func1's this.value: 1
+// console> func2's this.value: 1 and param1 and param2
+
  • apply : 호출할때 연결함. 매개변수는 배열로 넣어줌
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+
var value = 100;
+var myObj = {
+  value: 1,
+  func1: function () {
+    console.log(`func1's this.value: ${this.value}`);
+
+    var func2 = function (val1, val2) {
+      console.log(`func2's this.value ${this.value} and ${val1} and ${val2}`);
+    };
+    func2.apply(this, [`param1`, `param2`]);
+  },
+};
+
+myObj.func1();
+// console> func1's this.value: 1
+// console> func2's this.value: 1 and param1 and param2
+

출처

https://github.com/JaeYeopHan/Interview_Question_for_Beginner/tree/master/JavaScript#this-%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C

This post is licensed under CC BY 4.0 by the author.

closure

Promise, async/await

Comments powered by Disqus.

diff --git a/posts/typescript-extends/index.html b/posts/typescript-extends/index.html new file mode 100644 index 000000000..504ee6fa4 --- /dev/null +++ b/posts/typescript-extends/index.html @@ -0,0 +1,7 @@ + typescript 조건부타입(extends) | 디피의 개발일지
Posts typescript 조건부타입(extends)
Post
Cancel

typescript 조건부타입(extends)

타입 스크립트 2.8부터 다음과 같은 조건부 타입을 사용할 수 있다.

1
+
T extends U ? X : Y
+

뜻은 T가 U의 서브타입이거나 같은 타입이면 X, 아니면 Y 타입을 할당한다는 것이다.

T가 유니온 타입일 경우

다음과 같이 T가 유니온 타입일 경우가 있다.

1
+
type T = "A" | "B" | "C"
+

이러한 경우엔 분배법칙이 성립된다.

1
+
{"A" extends U ? X : Y} | {"B" extends U ? X : Y} | {"C" extends U ? X : Y}
+


출처

https://mugglim.tistory.com/13

This post is licensed under CC BY 4.0 by the author.

Javscript 메모리 누수 방지 및 성능 개선

useState 원리

Comments powered by Disqus.

diff --git a/posts/typescript-react-hack/index.html b/posts/typescript-react-hack/index.html new file mode 100644 index 000000000..0299ffe8c --- /dev/null +++ b/posts/typescript-react-hack/index.html @@ -0,0 +1,103 @@ + (Typescript) React typescript hack | 디피의 개발일지
Posts (Typescript) React typescript hack
Post
Cancel

(Typescript) React typescript hack

React 에서 Typescript 사용시 참고할만한 Typing 기법을 기록한 곳

React.HTMLArributes<[HTMLElement]>

한 Element에 부여될 속성 값들을 모두 참조할때 사용하면 좋다. 다음과 같이 기본 HTML 엘리먼트를 스타일링해서 사용할때 유용함

1
+2
+3
+
export interface ITextButtonProps extends IProps, React.HTMLAttributes<HTMLDivElement> {
+  text?: string | JSX.Element
+}
+

위와 같이, []로 감싸진 부분에 필요한 엘리먼트를 넣으면 된다.

하지만 위 방법이 통하지 않을 경우, 아래와 같이 엘리먼트 별로 구분된 속성을 사용하면 된다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+
const CustomInput = ({ error, ...props }: React.InputHTMLAttributes<HTMLInputElement> & IProps) => {
+  return (
+    <div>
+      <input
+        {...props}}
+      />
+      <label
+        htmlFor={props.id}
+      >
+        {props.placeholder}
+      </label>
+      <div>{error}</div>
+    </div>
+  )
+}
+

React.[Event]Event<[HTMLElement]>

onChange나, onSubmit 의 콜백함수에 사용하면 좋다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+
// onChange 콜백
+const onChangeText = (e: React.ChangeEvent<HTMLInputElement>) => {
+    setErrorMsg('')
+    setText(e.target.value)
+}
+
+// onSubmit 콜백
+const onClickComplete = (e: React.FormEvent<HTMLFormElement>) => {
+    e.preventDefault()
+    if (text.length === 0) {
+      setErrorMsg('내용을 입력해주세요')
+      return
+    }
+    const res = onClickCompleteCallback(text)
+    if (typeof res === 'string') {
+      setErrorMsg(res)
+    }
+}
+

as [Type]

어떠한 value의 타입을 한 타입으로 강제로 인식 시키는 방법이다.

1
+2
+3
+4
+
const handleResize = (e: UIEvent) => {
+      const w = e.target as Window
+      setScreenSize(calcCurrentScreen(w.innerWidth))
+}
+

사실은 매우 지양해야할 방법이다. 하지만, Typing이 너무 오래 걸려 개발 시간에 지장을 끼칠때 어쩔 수 없이 사용한다.

Axios.isAxiosError

Axios를 사용한 API 통신시 에러가 발생했을 경우, Axios에서 에러를 감싸서 throw하는데, 이때 이것을 다음과 같이 AxiosError라고 인식시켜서 Typing에 문제 없도록 하는 방법이다.

1
+2
+3
+4
+5
+6
+7
+
if (axios.isAxiosError(err)) {
+    switch (err.response?.data.message) {
+      case [Strings.ErrorMessageFromServer.EXCEED_NONMEMBER_MAX_PLAYLIST]:
+        errorMessage = Strings.ErrorMessageToUser.EXCEED_NONMEMBER_MAX_PLAYLIST
+        break
+    }
+}
+

children : React.ReactNode

Wrapper 컴포넌트와 같이 다른 컴포넌트를 감싸는 컴포넌트를 만들었을때, children 값을 받아서 렌더해줘야하는데, 이때 children의 속성은 React.ReactNode이다.

1
+2
+3
+4
+
export default function AppInit({ children }: { children: React.ReactNode }) {
+    ...
+    return <>{children}</>
+}
+
This post is licensed under CC BY 4.0 by the author.

(CSS)다른 엘리먼트의 이벤트 발생 시, 스타일 적용법

DataStructure 개요

Comments powered by Disqus.

diff --git a/posts/typescript/index.html b/posts/typescript/index.html new file mode 100644 index 000000000..8e4ae6bc8 --- /dev/null +++ b/posts/typescript/index.html @@ -0,0 +1,241 @@ + 타입스크립트 | 디피의 개발일지
Posts 타입스크립트
Post
Cancel

타입스크립트

타입스크립트 연습

  • tsconfig.json

    • compilerOptions:{

      ​ “outDir” : “./dist” // 컴파일된 파일 저장. expo 에선 안해도 될듯

      }

  • jsx -> tsx, js -> ts

기본타입

1
+2
+3
+4
+
let mightBeUndefined: string | undefined = undefined; // string 일수도 있고 undefined 일수도 있음
+let nullableNumber: number | null = null; // number 일수도 있고 null 일수도 있음
+
+let color: "red" | "orange" | "yellow" = "red"; // red, orange, yellow 중 하나임
+

interface, type

  • interface, class 사용

    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +14
    +15
    +16
    +
    interface Shape {
    +  getArea(): number;
    +}
    +class Circle implements Shape {
    +  radius: number; // private, public 설정으로 외부에서의 접근 제한 가능
    +  constructor(radius: number) {
    +    this.radius = radius;
    +  }
    +  getArea() {
    +    return this.radius * this.radius * Math.PI;
    +  }
    +}
    +
    +const obj: Shape = new Circle(5); // Shape, Circle 모두 타입으로 넣을 수 있음
    +
    +console.log(obj.getArea());
    +
  • interface extends

    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +
    interface Person {
    +  name: string;
    +  age?: number; // ? 는 있어도 되고 없어도 된다는 뜻.
    +  getAge(age: number): number; // 함수는 이렇게 해도 되고
    +  getAge: (age: number) => number; // 이렇게 해도 된다.
    +}
    +interface Developer extends Person {
    +  skills: string[];
    +}
    +
  • Type alias

    1
    +2
    +3
    +4
    +5
    +6
    +7
    +
    type Person = {
    +  name: string;
    +  age?: number;
    +};
    +type Developer = Person & {
    +  skills: string[];
    +};
    +
  • interface 와 type 모두 객체를 위해 쓸 수 있는데, interface는 클래스를 위해, type은 일반 객체를 위해 쓰는 것이 좋다.

    • 근데 그냥 일관성있게만 써도 됨.

generics

  • 객체 A와 B를 합치는 함수에서 객체 A, B가 무슨 타입일 지 모를때 다음과 같이 any를 사용할 수 있다.

    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +
    function merge(a: any, b: any): any {
    +  return {
    +    ...a,
    +    ...b,
    +  };
    +}
    +
    +const merged = merge({ foo: 1 }, { bar: 1 });
    +
  • 하지만 위처럼하면 타입 추정을 깨버리기에 다음과 같이 generics를 사용해서 표현할 수도 있다

    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +14
    +15
    +16
    +17
    +18
    +19
    +20
    +
    function merge<A, B>(a: A, b: B): A & B {
    +  return {
    +    ...a,
    +    ...b,
    +  };
    +}
    +
    +const merged = merge({ foo: 1 }, { bar: 1 });
    +/*
    +	좀더 확실히 하기 위해 아래처럼 지정할 수도 있다.
    +	const merged = merge<object, object>({foo:1}, {bar:1});
    +*/
    +
    +function wrap<T>(param: T) {
    +  return {
    +    param,
    +  };
    +}
    +
    +const wrapped = wrap(10);
    +
  • interface, type에서도 사용가능하다

    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +
    interface Items<T> {
    +  list: T[];
    +}
    +type Items<T> {
    +   list:T[];
    +}
    +
    +const items: Items<string> = {
    +  list: ['a', 'b', 'c']
    +};
    +
  • class 에서도 비슷한 방식으로 사용하면 된다.

    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +14
    +15
    +16
    +17
    +
    class Queue<T> {
    +  list: T[] = [];
    +  get length() {
    +    return this.list.length;
    +  }
    +  enqueue(item: T) {
    +    this.list.push(item);
    +  }
    +  dequeue() {
    +    return this.list.shift();
    +  }
    +}
    +
    +const queue = new Queue<number>();
    +
    +queue.enqueue(0);
    +console.log(queue.dequeue());
    +

리액트

  • 합수 타입을 다음처럼 지정하면 됨

    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +14
    +15
    +16
    +17
    +18
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +26
    +
    type GreetingsProps = {
    +  name: string;
    +  mark: string;
    +  optional?: string;
    +  onClick: (name: string) => void; // 아무것도 리턴하지 않는다는 함수를 의미합니다.
    +};
    +
    +function Greetings({ name, mark, optional, onClick }: GreetingsProps) {
    +  const handleClick = () => onClick(name);
    +  return (
    +    <div>
    +      Hello, {name} {mark}
    +      {optional && <p>{optional}</p>}
    +      <div>
    +        <button onClick={handleClick}>Click Me</button>
    +      </div>
    +    </div>
    +  );
    +}
    +
    +function App() {
    +  const onClick = (name: string) => {
    +    console.log(`${name} says hello`);
    +  };
    +  return <Greetings name="Hello" onClick={onClick} />;
    +}
    +
  • useState

    1
    +
    const [count, setCount] = useState<number>(0);
    +
    • 그런데 useState에선 알아서 타입을 유추하기에 안써도 된다.
    • 다만 상태가 null일 수도 있고, 아닐수도 있을때 Generics를 사용한다.
    1
    +2
    +
    type Information = { name: string; description: string };
    +const [info, setInformation] = useState<Information | null>(null);
    +
  • onPress, onChange 등의 객체타입이 뭔지 모를때는, 커서를 onChange 위에 올려두면 무엇인지 알려준다.

리덕스

  • 리덕스를 사용할땐 @types/react-redux 를 해줘야 타입스크립트가 지원이 된다.
  • typesafe-actions 라는 라이브러리로 쉽게 redux 파일을 작성할 수 있다.
This post is licensed under CC BY 4.0 by the author.

1918 후위표기식

expo push notification

Comments powered by Disqus.

diff --git a/posts/typescript5/index.html b/posts/typescript5/index.html new file mode 100644 index 000000000..b094b0aeb --- /dev/null +++ b/posts/typescript5/index.html @@ -0,0 +1,659 @@ + Typescript 5.0 | 디피의 개발일지
Posts Typescript 5.0
Post
Cancel

Typescript 5.0

typescript 5.0이 나왔다고 하여 간단히 어떤 기능들이 추가되었는지 살펴보았다. 자세한 내용은 Typescript 5.0 번역에서 확인 가능하다.

데코레이터

데코레이터는 재사용 가능한 방식으로 클래스와 그 멤버를 사용자 정의하는 ECMAScript의 기능이다. 데코레이터는 이전부터 지원되었으나 Typescript 5.0부터는 공식 기능으로 지원한다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+
function loggedMethod(originalMethod: any, context: ClassMethodDecoratorContext) {
+    const methodName = String(context.name);
+
+    function replacementMethod(this: any, ...args: any[]) {
+        console.log(`LOG: Entering method '${methodName}'.`)
+        const result = originalMethod.call(this, ...args);
+        console.log(`LOG: Exiting method '${methodName}'.`)
+        return result;
+    }
+
+    return replacementMethod;
+}
+
+class Person {
+    name: string;
+    constructor(name: string) {
+        this.name = name;
+    }
+
+    @loggedMethod
+    greet() {
+        console.log(`Hello, my name is ${this.name}.`);
+    }
+}
+
+const p = new Person("Ron");
+p.greet();
+

const 타입 파라미터

특정 문자열로 타입을 구성하고 싶을 때, 주로 as const를 사용하였다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+
// The type we wanted:
+//    readonly ["Alice", "Bob", "Eve"]
+// The type we got:
+//    string[]
+const names1 = getNamesExactly({ names: ["Alice", "Bob", "Eve"]});
+
+// Correctly gets what we wanted:
+//    readonly ["Alice", "Bob", "Eve"]
+const names2 = getNamesExactly({ names: ["Alice", "Bob", "Eve"]} as const);
+

하지만 이제는 제네릭에 const를 추가하여 비슷한 효과를 얻을 수 있다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+
type HasNames = { names: readonly string[] };
+function getNamesExactly<const T extends HasNames>(arg: T): T["names"] {
+//                       ^^^^^
+    return arg.names;
+}
+
+// Inferred type: readonly ["Alice", "Bob", "Eve"]
+// Note: Didn't need to write 'as const' here
+const names = getNamesExactly({ names: ["Alice", "Bob", "Eve"] });
+


extends에서 여러 파일 가져오기 가능

typescript 5.0부터는 다음과 같이 tsconfig.jsonextends에서 여러 파일을 작성할 수 있다. 따라서 여러 파일을 확장할 수 있게 된다.

1
+2
+3
+4
+5
+6
+
{
+    "extends": ["a", "b", "c"],
+    "compilerOptions": {
+        // ...
+    }
+}
+

c는 b를 확장하고, b는 a를 확장하는 것과 비슷한 효과를 가진다. 따라서 설정이 충돌되는 경우 후자의 항목이 우선된다.


모든 enum은 유니온 enum이다.

typescript 2.0에서 enum literal type이 도입되면서 enum은 좀 더 특별해졌다. enum literal type은 각 enum 멤버에 고유한 타입을 부여하고 enum 자체를 각 멤버타입의 union으로 만들었다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+
// Color is like a union of Red | Orange | Yellow | Green | Blue | Violet
+enum Color {
+    Red, Orange, Yellow, Green, Blue, /* Indigo */, Violet
+}
+
+// Each enum member has its own type that we can refer to!
+type PrimaryColor = Color.Red | Color.Green | Color.Blue;
+
+function isPrimaryColor(c: Color): c is PrimaryColor {
+    // Narrowing literal types can catch bugs.
+    // TypeScript will error here because
+    // we'll end up comparing 'Color.Red' to 'Color.Green'.
+    // We meant to use ||, but accidentally wrote &&.
+    return c === Color.Red && c === Color.Green && c === Color.Blue;
+}
+

이때 문제는 멤버 타입이 실제 값과 일부 연관되어있다는 것이다. 그래서 멤버가 함수 호출로 초기화되면 값을 계산할 수 없는 경우가 있다.

1
+2
+3
+
enum E {
+    Blah = Math.random()
+}
+

Typescript 5.0 은 계산된 각 멤버에 대해 고유한 타입을 생성하여 모든 enum을 공용체 enum으로 만들 수 있다.

1
+2
+3
+4
+
// typescript 5.0
+const a : E.Blah = 10;		// 문제 없음
+// typescript 5.0 미만
+const a : E.Blah = 10; 		// Enum type 'E' has members with initializers that are not literals.
+


–moduleResolution bundler

typesciprt 4.7에는 --module, --moduleResolutionnode16, nodenext 옵션을 도입하여 ECMAScript 모듈에 대한 정확한 조회 규칙을 모델링할 수 있게 되었다. 하지만 이는 몇몇 개발자에겐 번거로운 설정이었다.

5.0에서는 compilerOptionsdp moduleResolutionbundler 옵션을 추가하여 번들러의 동작을 모델링할 수 있다.

1
+2
+3
+4
+5
+6
+
{
+    "compilerOptions": {
+        "target": "esnext",
+        "moduleResolution": "bundler"
+    }
+}
+

하이브리드 조회 전략을 구현하는 Vite, esbuild, swc, Webpack, Parcel 등의 최신 번들러를 사용 중이라면 새로운 bundler옵션이 적합할 것이다. 하지만, 라이브러리 제작자일 경우 bundler 옵션을 사용하면, 번들러를 사용하지 않는 사용자에게 문제가 생길 수 있으니 node16, nodenext 옵션을 추천한다.


Resolution 커스터마이징 플래그

  • allowImportingTsExtensions : .ts, .mts, .tsx와 같이 Typescript 전용 확장자를 사용하여 서로를 import 할 수 있다.
  • resolvePackageJsonExports : typescript가 node_modules의 패키지를 읽을 경우, package.json의 exports 필드를 참조하도록 한다.
  • resolvePackageJsonImports : package.json을 포함하는 조상 디렉터리의 파일로부터 #으로 시작하는 조회를 수행할 때, typescript가 package.json의 import 플드를 참조하도록 강제함
  • allowArbitraryExtensions
    • typescript 5.0에서 import 경로가 js또는 ts 파일 확장자가 아닌 경우 {파일 기본 이름}.d.{확장자}.ts 형식의 해당 경로에서 타입을 찾는다.
    • Typescript에서는 이 파일 형식을 이해하지 못하여 런타임에서 import를 지원하지 않을 수 있음을 알리는 오류를 내지만, 이 플래그를 통해 억제 할 수 있다.


customConditions

package.json의 exports, imports 필드를 Typescript가 리졸브할 때 성공해야하는 추가 조건의 목록을 받는다. 현존하는 조건에 추가된다.

1
+2
+3
+4
+5
+6
+7
+
{
+    "compilerOptions": {
+        "target": "es2022",
+        "moduleResolution": "bundler",
+        "customConditions": ["my-condition"]
+    }
+}
+
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
{
+    // ...
+    "exports": {
+        ".": {
+            "my-condition": "./foo.mjs",
+            "node": "./bar.mjs",
+            "import": "./baz.mjs",
+            "require": "./biz.mjs"
+        }
+    }
+}
+

위와 같은 경우 exports, imports 필드를 리졸브할 때 항상 my-condition을 고려한다.


–verbatimModuleSyntax

기본적으로 Typescript는 import elision을 수행한다. 예를들어 다음과 같이 import를 type을 가져오기만을 위해 사용한다면, 이를 지운다.

1
+2
+3
+4
+5
+
import { Car } from "./car";
+
+export function drive(car: Car) {
+    // ...
+}
+
1
+2
+3
+4
+
// import 문을 지움
+export function drive(car) {
+    // ...
+}
+

type은 값이 아니기에 지우지 않으면 런타임 에러가 날 수 있기 때문이다. 하지만 몇가지 엣지 케이스가 있을 수 있다. 예를들어 변환된 코드는 import ".car"조차 남기지 않는데, 이는 사이드 이펙트를 일으킬 수도 있다.

따라서 Typescript에서는 type 수정자를 사용하도록 하는 --importsNotUsedAsValues, 일부 모듈 엘리션 동작을 방지하는 --perserveValueImports, Typescript가 여러 컴파일러에서 작동하는지 확인하는 --isolatedModules 플래그가 있다. 하지만 이 플래그들은 이해하기 어렵고, 에지 케이스 역시 존재한다.

Typescript 5.0에서는 --verbatimModuleSyntax 를 적용하여 type 수정자가 없는 모든 import, export는 그대로 유지하도록 했다. type 수정자를 사용하는 모든 항목은 완전히 삭제된다.

1
+2
+3
+4
+5
+6
+7
+8
+
// Erased away entirely.
+import type { A } from "a";
+
+// Rewritten to 'import { b } from "bcd";'
+import { b, type c, type d } from "bcd";
+
+// Rewritten to 'import {} from "xyz";'
+import { type xyz } from "xyz";
+

하지만 이 플래그를 사용하면 ECMAScript importexport는, 다른 모듈 시스템을 암시하는 파일 확장자 또는 세팅이 있을때 require 로 재작성되지 않는다. 대신 오류를 낸다. 만약 requiremodule.exports를 사용하는 코드를 emit 하고 싶다면 ES2015 이전의 Typescript 모듈 구문을 사용해야한다.

Input TypescriptOutput Javascript
import foo = require("foo"); const foo = require("foo");
function foo() {}
function bar() {}
function baz() {}
export = { foo, bar, baz };
function foo() {}
function bar() {}
function baz() {}
module.exports = { foo, bar, baz };

이는 몇몇 상황에서 도움이 되는데, 예를들어 개발자가 package.json--module node16을 설정하지 않은 경우가 있다. 개발자는 인지하지 못한채 ES 모듈 대신 CommonJS 모듈을 작성하기 시작하여 예상치 못한 조회 규칙과 JavaScript 출력을 제공하게 된다. 이 플래그는 구문이 의도적으로 다르기 때문에 사용중인 파일 형식에 대해 의도적으로 확인할 수 있다.

-verbatimModuleSyntax--importsNotUsedAsValues--preserveValueImports보다 더 일관된 스토리를 제공하므로, 기존의 두 플래그는 이 구문으로 대체된다.


Support for export type *

기존엔 export * from "module"이나 export * as ns from "module" 처럼 re-export가 불가능했다. Typescript 5.0 부터는 가능하다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+
// models/vehicles.ts
+export class Spaceship {
+  // ...
+}
+
+// models/index.ts
+export type * as vehicles from "./vehicles";
+
+// main.ts
+import { vehicles } from "./models";
+
+function takeASpaceship(s: vehicles.Spaceship) {
+  // ✅ ok - `vehicles` only used in a type position
+}
+
+function makeASpaceship() {
+  return new vehicles.Spaceship();
+  //         ^^^^^^^^
+  // 'vehicles' cannot be used as a value because it was exported using 'export type'.
+}
+


@satisfies Support in JSDoc

Typescript 4.9에서 satisfies 연산자가 추가됐다. 이 연산자는 타입에 영향을 주지 않고, 표현식이 타입에 호환되는지만 확인한다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+
interface CompilerOptions {
+    strict?: boolean;
+    outDir?: string;
+    // ...
+}
+
+interface ConfigSettings {
+    compilerOptions?: CompilerOptions;
+    extends?: string | string[];
+    // ...
+}
+
+let myConfigSettings = {
+    compilerOptions: {
+        strict: true,
+        outDir: "../lib",
+        // ...
+    },
+
+    extends: [
+        "@tsconfig/strictest/tsconfig.json",
+        "../../../tsconfig.base.json"
+    ],
+
+} satisfies ConfigSettings;
+

위 코드에서 Typescript는 myConfigSettings.extends가 배열임을 아는데, 이는 ConfigSettings와 호환 가능한지만 확인하고, myConfigSettings의 타입을 변경하지는 않았기 떄문이다. 따라서 extends에 map 함수를 사용해도 오류가 나지 않는다.

satisfies가 JSDoc 태그에 추가되었다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+
// @ts-check
+
+/**
+ * @typedef CompilerOptions
+ * @prop {boolean} [strict]
+ * @prop {string} [outDir]
+ */
+
+/**
+ * @satisfies {CompilerOptions}
+ */
+let myCompilerOptions = {
+    outdir: "../lib",
+//  ~~~~~~ oops! we meant outDir
+};
+

다음과 같이 /** @satisfies */ 인라인으로 사용하거나, 함수 호출과 같은 곳에서도 사용할 수 있다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+
let myConfigSettings = /** @satisfies {ConfigSettings} */ ({
+    compilerOptions: {
+        strict: true,
+        outDir: "../lib",
+    },
+    extends: [
+        "@tsconfig/strictest/tsconfig.json",
+        "../../../tsconfig.base.json"
+    ],
+});
+
+// 함수 호출에서 사용
+compileCode(/** @satisfies {ConfigSettings} */ ({
+    // ...
+}));
+


@overload Support in JSDoc

Typescript에서는 함수 오버로드를 만들 수 있다. 이로써 함수를 사용하는 방법을 제한할 수 있다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+
// Our overloads:
+function printValue(str: string): void;
+function printValue(num: number, maxFractionDigits?: number): void;
+
+// Our implementation:
+function printValue(value: string | number, maximumFractionDigits?: number) {
+    if (typeof value === "number") {
+        const formatter = Intl.NumberFormat("en-US", {
+            maximumFractionDigits,
+        });
+        value = formatter.format(value);
+    }
+
+    console.log(value);
+}
+

이러한 오버로드 기능을 @overload 태그를 사용해서 JSDoc 안에서 사용할 수 있다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+
// @ts-check
+
+/**
+ * @overload
+ * @param {string} value
+ * @return {void}
+ */
+
+/**
+ * @overload
+ * @param {number} value
+ * @param {number} [maximumFractionDigits]
+ * @return {void}
+ */
+
+/**
+ * @param {string | number} value
+ * @param {number} [maximumFractionDigits]
+ */
+function printValue(value, maximumFractionDigits) {
+    if (typeof value === "number") {
+        const formatter = Intl.NumberFormat("en-US", {
+            maximumFractionDigits,
+        });
+        value = formatter.format(value);
+    }
+
+    console.log(value);
+}
+


Passing Emit-Specific Flags Under --build

--build 모드에서 다음 플래그가 추가되었다. 빌드 시 커스터마이징을 위해 사용한다.

  • --declaration
  • —-emitDeclarationOnly
  • —-declarationMap
  • —-sourceMap
  • —-inlineSourceMap


Case-Insensitive Import Sorting in Editors

VSCode 와 같은 코드에디터에서 TypeScript는 import, export 목록 정렬을 지원한다. 하지만 Typescript는 기본적으로 대소문자를 구분하여 정렬한다. 아스키 문자 인코딩 상 대문자는 소문자 앞이기에 다음 정렬은 Typescript에서 적절하다.

1
+2
+3
+4
+5
+
import {
+    Toggle,
+    freeze,
+    toBoolean,
+} from "./utils";
+

하지만 대소문자를 구분하지 않고 정렬하고 싶을 수도 있다. 이제는 organizeImports 옵션이 추가되어 해당 설정을 할 수 있다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+
{
+    "typescript.unstable": {
+        // Should sorting be case-sensitive? Can be:
+        // - true
+        // - false
+        // - "auto" (auto-detect)
+        "organizeImportsIgnoreCase": "auto",
+
+        // Should sorting be "ordinal" and use code points or consider Unicode rules? Can be:
+        // - "ordinal"
+        // - "unicode"
+        "organizeImportsCollation": "ordinal",
+
+        // Under `"organizeImportsCollation": "unicode"`,
+        // what is the current locale? Can be:
+        // - [any other locale code]
+        // - "auto" (use the editor's locale)
+        "organizeImportsLocale": "en",
+
+        // Under `"organizeImportsCollation": "unicode"`,
+        // should upper-case letters or lower-case letters come first? Can be:
+        // - false (locale-specific)
+        // - "upper"
+        // - "lower"
+        "organizeImportsCaseFirst": false,
+
+        // Under `"organizeImportsCollation": "unicode"`,
+        // do runs of numbers get compared numerically (i.e. "a1" < "a2" < "a100")? Can be:
+        // - true
+        // - false
+        "organizeImportsNumericCollation": true,
+
+        // Under `"organizeImportsCollation": "unicode"`,
+        // do letters with accent marks/diacritics get sorted distinctly
+        // from their "base" letter (i.e. is é different from e)? Can be
+        // - true
+        // - false
+        "organizeImportsAccentCollation": true
+    },
+    "javascript.unstable": {
+        // same options valid here...
+    },
+}
+


Exhaustive switch/case Completions

switch 문을 작성할 때 검사 대상에 리터럴 타입이 있는지 감지한다. 감지되면 발견된지 않은 각 대소문자를 스카폴딩하는 완결성을 제공한다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+
type Fruit = 
+	| { kind : "apple" }
+	| { kind : "orange" }
+	| { kind : "banana" }
+
+function nom(fruit: Fruit) {
+	switch(fruit.kind) {
+    case 						// case 문을 작성하면 자동 완성으로 case "apple": case "orange": case "banana"가 생김
+  }
+}
+


Speed, Memory, and Package Size Optimizations

Typescript 5.0에서는 코드 구조, 데이터 구조, 알고리즘 구현을 변경하여 전체 경험을 좀 더 빠르게 만들었다.

image

img


Breaking Changes and Deprecations

Runtime Requirements

Typescript 5.0부터는 ECMAScript 2018을 대상으로 한다. 또한, Typescipt 패키지는 최소 요구 엔진을 12.20으로 설정했다.

lib.d.ts Changes

DOM 유형이 생성되는 방식이 변경되어 기존 코드에 영향을 미칠 수 있다. 특히 특정 property가 숫자에서 숫자 리터럴 타입으로 변환되었다. 잘라내기, 복사, 붙여넣기 이벤트 처리를 위한 프로퍼티와 메서드가 인터페이스 전반으로 이동되었다.

API Breaking Changes

모듈로 전환하고, 불필요한 인터페이스를 제거했으며 일부 정확성을 개선했다.

Forbidden Implicit Coercions in Relational Operators

Typescript의 특정 연산은 암시적으로 문자열을 숫자로 강제 변환할 수 있는 코드를 작성할 경우 경고하고 있다.

1
+2
+3
+
function func(ns: number | string) {
+  return ns * 4; // Error, possible implicit coercion
+}
+

5.0 부터는 관계 연산자 >, <, <=, >= 에도 이 기능이 적용된다. 원하는 경우 +를 사용하여 피연산자를 숫자로 명시적으로 강제할 수 있다.

1
+2
+3
+4
+5
+6
+7
+
function func(ns: number | string) {
+  return ns > 4; // Now also an error
+}
+
+function func(ns: number | string) {
+  return +ns > 4; // OK
+}
+

Enum Overhaul

Enum을 사용할 때 Enum 유형에 도메인 외부 리터럴을 할당하면 오류가 발생하도록 추가됐다.

1
+2
+3
+4
+5
+6
+7
+8
+
enum SomeEvenDigit {
+    Zero = 0,
+    Two = 2,
+    Four = 4
+}
+
+// Now correctly an error
+let m: SomeEvenDigit = 1;
+

또 숫자와 간접 string enum 참조가 혼합되어 선언된 값이 있는 enum에서 오류를 제대로 생성하도록 변경됐다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+
enum Letters {
+    A = "a"
+}
+enum Numbers {
+    one = 1,
+    two = Letters.A
+}
+
+// Now correctly an error
+const t: number = Numbers.two;
+

More Accurate Type-Checking for Parameter Decorators in Constructors Under --experimentalDecorators

decorator에 대한 좀 더 정확한 type-checking이 되도록 하는 옵션이 추가되었다. 특히 생성자 파라미터에서 사용할 때 효과적이다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+
export declare const inject:
+  (entity: any) =>
+    (target: object, key: string | symbol, index?: number) => void;
+
+export class Foo {}
+
+export class C {
+    constructor(@inject(Foo) private x: any) {
+    }
+}
+

keystring | symbol을 예상하기에 이 호출은 실패하지만 생성자 파라미터는 undefined키를 받는다. 올바른 수정은 injectkey의 타입을 변경하는 것이다. 만약 변경할 수 없는 라이브러리를 사용중인 경우에는 inject를 좀 더 type-safe한 decorator로 감싸고 key에 type assertion을 사용하는 것이다.

Deprecations and Default Changes

이제 다음 설정값들은 사용되지 않는다.

  • --target: ES3
  • --out
  • --noImplicitUseStrict
  • --keyofStringsOnly
  • --suppressExcessPropertyErrors
  • --suppressImplicitAnyIndexErrors
  • --noStrictGenericChecks
  • --charset
  • --importsNotUsedAsValues
  • --preserveValueImports
  • prepend in project references

Typescript 5.5까지는 허용하지만 이후로는 경고가 발생한다. 단, "ignoreDeperecations": "5.0"을 사용하면 경고를 없앨 수 있다.

프로젝트 내에서 동일한 파일 이름을 참조하는 모든 참조가 케이스에 대해 동의하도록 보장하는 --forceConsistentCasingInFileNames는 이제 true로 기본 설정된다.


출처

https://velog.io/@hustle-dev/TypeScript-5.0-%EB%B2%88%EC%97%AD#%EB%8D%B0%EC%BD%94%EB%A0%88%EC%9D%B4%ED%84%B0

https://devblogs.microsoft.com/typescript/announcing-typescript-5-0/#moduleresolution-bundler

This post is licensed under CC BY 4.0 by the author.

next.js appDir

Javscript 메모리 누수 방지 및 성능 개선

Comments powered by Disqus.

diff --git "a/posts/useEffect-\354\230\254\353\260\224\353\245\270-\354\202\254\354\232\251/index.html" "b/posts/useEffect-\354\230\254\353\260\224\353\245\270-\354\202\254\354\232\251/index.html" new file mode 100644 index 000000000..8989218b9 --- /dev/null +++ "b/posts/useEffect-\354\230\254\353\260\224\353\245\270-\354\202\254\354\232\251/index.html" @@ -0,0 +1,219 @@ + useEffect 올바른 사용 | 디피의 개발일지
Posts useEffect 올바른 사용
Post
Cancel

useEffect 올바른 사용

useEffect 올바른 사용

props, state 변경에 따라 다른 데이터, state를 업데이트해야할 때

useEffect를 사용하면 안된다.

props, state가 변경이 되면 리렌더링이 발생하는데, 리렌더링이 끝난 후 useEffect가 호출되어 다시 리렌더링이 발생하기 때문이다.

따라서 다음과 같이 코드를 변경해야한다.


데이터를 업데이트해야하는 경우

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+
// useEffect로 업데이트
+function TodoList({ todos, filter }) {
+  const [newTodo, setNewTodo] = useState('');
+
+  // Avoid: redundant state and unnecessary Effect
+  const [visibleTodos, setVisibleTodos] = useState([]);
+  useEffect(() => {
+    setVisibleTodos(getFilteredTodos(todos, filter));
+  }, [todos, filter]);
+
+  // ...
+}
+
1
+2
+3
+4
+5
+6
+7
+
// useEffect 사용하지 않음 
+function TodoList({ todos, filter }) {
+  const [newTodo, setNewTodo] = useState('');
+  // This is fine if getFilteredTodos() is not slow.
+  const visibleTodos = getFilteredTodos(todos, filter);
+  // ...
+}
+

컴포넌트가 리렌더링할때마다 컴포넌트의 코드가 위에서부터 다시한번씩 실행되며 getFilteredTodos()도 다시 호출되기 때문에, 최신값으로 업데이트 된다.

만약 getFilteredTodos()가 비싼 연산이라면 useMemo()를 통해 최적화를 하자

1
+2
+3
+4
+5
+6
+7
+8
+
import { useMemo, useState } from 'react';
+
+function TodoList({ todos, filter }) {
+  const [newTodo, setNewTodo] = useState('');
+  // Does not re-run getFilteredTodos() unless todos or filter change
+  const visibleTodos = useMemo(() => getFilteredTodos(todos, filter), [todos, filter]);
+  // ...
+}
+


props 변경에 따라 상태를 초기화 해야 하는 경우

유저 변경에 따라 comment 상태를 초기화해야하는 경우

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+
// useEffect 사용
+export default function ProfilePage({ userId }) {
+  const [comment, setComment] = useState('');
+
+  // 🔴 Avoid: Resetting state on prop change in an Effect
+  useEffect(() => {
+    setComment('');
+  }, [userId]);
+  // ...
+}
+

userId 변경 –> 리렌더링 –> comment 변경 –> 리렌더링. 총 2번 리렌더링이 발생한다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+
// 리팩토링
+export default function ProfilePage({ userId }) {
+  return (
+    <Profile
+      userId={userId} 
+      key={userId}
+    />
+  );
+}
+
+function Profile({ userId }) {
+  // ✅ This and any other state below will reset on key change automatically
+  const [comment, setComment] = useState('');
+  // ...
+}
+

key값을 사용하여, userId가 변경될때마다 Profile 컴포넌트를 초기상태에서 가져오도록 한다.


props 변경에 따라 특정 상태만 업데이트 해야하는 경우

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
// useEffect 사용
+function List({ items }) {
+  const [isReverse, setIsReverse] = useState(false);
+  const [selection, setSelection] = useState(null);
+
+  // 🔴 Avoid: Adjusting state on prop change in an Effect
+  useEffect(() => {
+    setSelection(null);
+  }, [items]);
+  // ...
+}
+
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+
function List({ items }) {
+  const [isReverse, setIsReverse] = useState(false);
+  const [selection, setSelection] = useState(null);
+
+  // Better: Adjust the state while rendering
+  const [prevItems, setPrevItems] = useState(items);
+  if (items !== prevItems) {
+    setPrevItems(items);
+    setSelection(null);
+  }
+  // ...
+}
+

items의 이전상태를 담는 상태를 생성하여 리렌더링을 하나로 줄였음.


useEffect vs EventHandler

쇼핑몰에서 카트에 물건을 넣었을 때 모달을 통해 정상적으로 물품을 담았는지 유저에게 노티피케이션을 알려줘야한다.

다음은 useEffect를 사용한 예시다. product가 카트에 추가되면, isInCart 상태가 true가 되어 useEffect에 등록한 콜백이 호출되고, Notification이 뜨는 방식이다.

모달을 보여줘야하는 버튼이 여러개이면 일일히 각 핸들러에 showNotification 콜백을 붙이는 것보다 useEffect에 넣는게 바람직하다고 착각할 수 있다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+
function ProductPage({ product, addToCart }) {
+  // 🔴 Avoid: Event-specific logic inside an Effect
+  useEffect(() => {
+    if (product.isInCart) {
+      showNotification(`Added ${product.name} to the shopping cart!`);
+    }
+  }, [product]);
+
+  function handleBuyClick() {
+    addToCart(product);
+  }
+
+  function handleCheckoutClick() {
+    addToCart(product);
+    navigateTo('/checkout');
+  }
+  // ...
+}
+

하지만 아니다.

페이지 전환 시 전역상태 값인 product.isInCarttrue면 예상하지 못한 버그가 생길 수 있기 때문이다.

또 유저는 상태가 변경되었을 때가 아닌, 버튼을 눌렀을 때 Notification이 뜨기를 기대할 것이다.

우리의 코드가 상태를 변경했을 때 작동하는 것이 의도인지, 유저의 이벤트에 따라 UI를 변경하는 것이 주 목적인지 생각해볼때, 버그를 양산할 수 있는 useEffect보다 이벤트 핸들러 내부에서 Notification을 호출하는 것이 바람직하다.

useEffect, eventHandler 둘 중 어디에 코드를 작성해야할지 모르겠다면, 코드가 언제 실행되어야하는지 생각해봐라. useEffect는 컴포넌트가 렌더링 되는 시점에 유저에게 표현되어야할 로직을 실행할 때 사용한다.


data fetching

data fetching은 해도 된다. 하지만 race condition을 고려해야한다.

리액트18 strict mode에서 컴포넌트 렌더링 시 useEffect 내부에서 ajax가 일어난다면, 렌더 -> fetch -> 리렌더 - refetch가 일어난다.

이는 api 서버 비용이 두 배로 든다는 점만 감안하면 괜찮다. 사용자는 fetch/refetch간 동일한 데이터가 응답된다면 UI에 변경점을 느끼지 못하기 때문이다.

하지만 특정 상태나 Props 변경에 따라 뷰가 바뀌는 경우라면 어떨까?

언마운트에서의 setState

다음의 코드는 userId가 변경될 때마다 startFetching이 호출된다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+
useEffect(() => {
+  let ignore = false;
+
+  async function startFetching() {
+    const json = await fetchTodos(userId);
+    if (!ignore) {
+      setTodos(json);
+    }
+  }
+
+  startFetching();
+
+  return () => {
+    ignore = true;
+  };
+}, [userId]);
+

컴포넌트가 언마운트 되었을 때는 ignore를 true로 만들어, 클로져를 사용하는 startFetching내부에서 setTodos는 언마운트 이후에도 호출되지 않는다.

하지만 이러한 로직을 매번 따로 작성해야하는 문제점이 남아있다.

race condition

한 페이지에서 특정 id값을 기준으로 모달같은 공통 ui를 그려주는 경우에는 위의 로직이 도움이 되지 않는다.

다음처럼 sartFetching이 테이블 컴포넌트에 있는 open modal을 누를때마다 호출된다고 가정해보자.

img

id 3의 open modal --> close modal --> id 4의 open modal 과 같은 시나리오에서, id 4가 먼저 응답이 올지, id 3가 먼저 응답이 올지 알수가 없다.

이러한 문제를 race condition이라고 한다. useEffect 에서 setState를 호출하는 것만으로는 이러한 상황을 방지할 순 없다. 특별한 조치를 취해야 막을 수 있다. (서버에서 온 id와 props으로 온 id가 일치하는지 확인, AbortController 등)

특정 상태가 변경되었을 때 데이터를 호출하는 상황은?

서버자원이 동나지 않도록 조심해야한다.

어떤 버그상황에도 대비할 수 있게 useEffect를 써야하고, dependencies의 상태 변경이 예측할 수 있는 속도로 변경되는 것을 보장할 수 있어야한다.

하지만 어려운 일이다.

따라서 동일한 api로 호출하는 경우 이전에 받아온 데이터를 재사용하는 방법으로 해결할 수 있다. 바로 캐시를 이용하는 방법이다. 이 캐시를 이용하기 위해 react-query, swr 등 data fetch 라이브러리를 사용할 수 있다.



출처

https://velog.io/@jay/you-might-need-useEffect-diet

This post is licensed under CC BY 4.0 by the author.

바벨

자바 기본

Comments powered by Disqus.

diff --git "a/posts/velog-\353\251\224\353\252\250/index.html" "b/posts/velog-\353\251\224\353\252\250/index.html" new file mode 100644 index 000000000..3cef39ff1 --- /dev/null +++ "b/posts/velog-\353\251\224\353\252\250/index.html" @@ -0,0 +1 @@ + velog 메모 | 디피의 개발일지
Posts velog 메모
Post
Cancel

velog 메모

리액트 리렌더링

https://velog.io/@eunbinn/when-does-react-render-your-component

  • children으로 넘어온 컴포넌트는 사용되는 컴포넌트가 리렌더링되도, 리렌더링의 영향을 받지 않는다.
  • 리렌더링은 현재 컴포넌트에서 변화된 것이 없어도, 자식으로 전파된다.
    • but 현재 컴포넌트에 useMemo를 사용하면, 변화가 없을시 자식으로도 전파되지 않음
  • useMemo도 써야할 때를 알아야한다. (구조변경 등의 방법으로 리렌더링을 막을 수 있음)
This post is licensed under CC BY 4.0 by the author.

함수형 프로그래밍 (이해하기 쉽게)

React 18로 업데이트 방법, 문제점 정리

Comments powered by Disqus.

diff --git a/posts/video-notplaying/index.html b/posts/video-notplaying/index.html new file mode 100644 index 000000000..8dfbc93c9 --- /dev/null +++ b/posts/video-notplaying/index.html @@ -0,0 +1,126 @@ + 영상이 자동재생되지 않는 문제 | 디피의 개발일지
Posts 영상이 자동재생되지 않는 문제
Post
Cancel

영상이 자동재생되지 않는 문제

다음과 같은 요구사항을 가지는 웹페이지를 개발하고 있었다.

  • swiper로 여러 슬라이드를 띄움
  • 슬라이드 내에는 영상이 존재하고, 각 영상은 자동으로 재생되어야함.

단순히 swiper를 연동하고 video 태그에 autoplay 속성을 주면 될거라 생각하여 다음과 같이 코드를 작성했었다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+
<li className="swiper-slide">
+        <video
+            poster="poster 주소"
+            autoPlay muted playsInline loop>
+            <source
+                src="영상주소"
+                type="video/mp4"/>
+        </video>
+</li>
+<li className="swiper-slide">
+        <video
+            poster="poster 주소"
+            autoPlay muted playsInline loop>
+            <source
+                src="영상주소"
+                type="video/mp4"/>
+        </video>
+</li>
+...
+

실제로 당시 개발 환경(mac, chrome)에서는 문제없이 동작했었다. 하지만 QA 단계에서 영상이 재생되지 않으며 사이트 자체가 매우 느리다는 제보를 받았다. 그리고 이 문제를 해결한 과정은 다음과 같다.


필요한 영상들만 재생

swiper에서 loop 옵션을 사용하고 있었다. 끝까지 가면 다시 처음 슬라이드가 나오도록 하기 위해서였다. 하지만, loop 옵션을 사용하면 다음과 같이 실제로 보여질 swiper-slide 앞 뒤로 swiper-slide-duplicate를 생성한다. 그리고 각 swiper-slide-duplicateslide-swiper와 구성이 같다.

image-20230603185315584

즉, 필요한 슬라이드 개수의 3배 정도 슬라이드가 만들어지고, 각 슬라이드 안에는 영상이 포함되어있다는 것이다. 이처럼 많은 영상을 한번에 재생하는 것은 사이트 성능에 악영향을 줄거라 생각했다. 따라서 다음과 같이 swiper에 옵션을 주어 swiper-slide-duplicate 안에 있는 비디오의 재생은 멈추었다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+
new Swiper('.myswiper1', {
+            touchRatio: 0,
+            autoHeight: true,
+            speed: 650,
+            observer: true,
+            observeParents: true,
+            slidesPerView: 'auto',
+            loop:true,
+            navigation: {
+                nextEl: '.swiper-button-next',
+                prevEl: '.swiper-button-prev',
+            },
+            on: {
+                slideChangeTransitionEnd : function() {
+                    $('.myswiper1 .swiper-slide.swiper-slide-duplicate')
+                      .find(".video_box video")?
+                      .each(function(idx, item) {
+                        item.pause();
+                    })
+                },
+            },
+        });
+

하지만 이렇게 하니 마지막 -> 첫번쨰, 첫번쨰 -> 마지막으로 갈때 이동한 후 슬라이드의 영상이 재생되지 않는 것을 발견했다. 그 이유는 앞 두 경우에는 swiper-slide-duplicate가 화면에 보이는 경우였기 떄문이다. 다음 사진을 보면 swiper-slide-activeswiper-slide-duplicate에 붙은 걸 확인할 수 있다.

image-20230603190020412

따라서 다음과 같이 이벤트를 수정하여 swiper-slide-active가 붙은건 명시적으로 실행해주어 문제를 해결했다.

new Swiper('.myswiper', {
+            touchRatio: 0,
+            autoHeight: true,
+            speed: 650,
+            observer: true,
+            observeParents: true,
+            slidesPerView: 'auto',
+            loop:true,
+            navigation: {
+                nextEl: '.swiper-button-next',
+                prevEl: '.swiper-button-prev',
+            },
+            on: {
+                slideChangeTransitionEnd : function() {
+                    $('.myswiper .swiper-slide.swiper-slide-duplicate')
+                      .find(".video_box video")?
+                      .each(function(idx, item) {
+                        item.pause();
+                    })
+                    $('.myswiper .swiper-slide.swiper-slide-active')
+                      .find(".video_box video")?
+                      .each(function(idx, item) {
+                       item.play()
+                    });
+                },
+            },
+        });
+

이렇게 꼭 필요한 영상들만 자동재생을 적용하니 사이트가 빨라졌다는 답변을 받을 수 있었다.


영상 최적화

위에서 슬라이드가 복사되는 것을 보고 영상이 너무 많아서 사이트가 느려지고 자동재생이 안되는게 아닐까라는 생각이 들었다. 따라서 영상 자체를 최적화하였다.

  • 영상 압축
  • 필요한 사이즈로 줄이기
  • 음성은 필요없으므로 음성 제거

위 세가지 작업을 진행하니 영상 사이즈를 90%정도 줄일 수 있었다.

그리고 사이트가 빨라졌으며 자동재생이 안되는 문제도 크게 개선되었다는 답변이 왔다.

하지만 여전히 자동재생은 가끔씩 안되고 있다는 애기를 들었다.


수동으로 영상 실행

최종 수단으로 chatGPT에게 물어보니 다음과 같은 코드를 실행해보라는 답변을 받았다. 간헐적으로 영상이 자동 재생이 안되면 네트워크 이슈를 비롯하여 다양한 이슈가 있을 수 있으므로 수동으로도 영상을 실행해보라는 얘기였다.

1
+2
+3
+4
+5
+6
+7
+8
+
function playVideo() {
+	var videos = document.querySelectorAll('video');
+	for (var i = 0; i < videos.length; i++) {
+		videos[i].play();
+	}
+}
+playVideo();
+window.addEventListener('load', playVideo);
+

실제로 적용해보니 영상이 제대로 재생되었다..

This post is licensed under CC BY 4.0 by the author.

DB index

es6에서 도입된 문법

Comments powered by Disqus.

diff --git a/posts/view/index.html b/posts/view/index.html new file mode 100644 index 000000000..7fe3c11b1 --- /dev/null +++ b/posts/view/index.html @@ -0,0 +1 @@ + view | 디피의 개발일지
Posts view
Post
Cancel

view

View

다른 테이블을 기반으로 만들어진 가상 테이블. 논리적으로만 존재하는 테이블.

장점

  • 질의문을 쉽게 작성할 수 있음
  • 데이터 보안유지에 도움이 됨.
  • 데이터 관리가 편해짐

특징

  • ALTER 문으로 뷰를 재정의하는 것은 불가능
  • 기본 테이블 삭제 시 같이 삭제된다. 독자적인 인덱스를 가질 수 없음.


**뷰생성 **

CREATE VIEW 뷰_이름[(속성_리스트)] AS SELECT 문 [WITH CHECK OPTION]

  • AS SELECT 문 : 뷰를 정의. ORDER BY는 사용불가
  • WITH CHECK OPTION : 뷰에 삽입이나 수정연산을 할 때, SELECT 문에서 제시한 뷰의 정의조건을 위반하면 수행되지 않도록하는 제약조건을 지정


뷰 INSERT, UPDATE, DELETE문

  • 뷰에대한 연산은 실제론 기본 테이블에 대해 수행되므로 제한적으로 이루어짐
  • 변경 불가능한 뷰
    • 기본 테이블의 기본키를 포함하고 있지 않는 뷰
    • 집계합수, DISTINCT, GROUP BY, 조인을 사용하여 정의한 기본테이블의 부분이 아닌 뷰


뷰 삭제

DROP VIEW 뷰_이름

  • 삭제할 뷰를 참조하는 제약조건이 존재하면 삭제되지 않음.
  • 관련된 제약조건을 먼저 삭제해야함.
This post is licensed under CC BY 4.0 by the author.

IPv6

함수형 프로그래밍 (이해하기 쉽게)

Comments powered by Disqus.

diff --git a/posts/virtual-Dom/index.html b/posts/virtual-Dom/index.html new file mode 100644 index 000000000..8deabdc04 --- /dev/null +++ b/posts/virtual-Dom/index.html @@ -0,0 +1,61 @@ + virtual DOM와 리렌더링 | 디피의 개발일지
Posts virtual DOM와 리렌더링
Post
Cancel

virtual DOM와 리렌더링

Virtual DOM은 DOM을 추상화한 가상의 객체이다.


DOM 이란?

HTML 문서를 파싱하여 문서의 구성요소들을 객체로 구조화하여 나타낸 것.

img

HTML Elements, 속성, CSS style, Events, Methods 를 구조화해서 나타낸 객체고, 이 객체를 이용해서 웹페이지 구성요소를 제어할 수 있다.

Virtual DOM은 이러한 DOM을 추상화한 가상의 객체이다.


Virtual DOM 이 해결하고자 하는 문제

React 작동 방식은 상태가 변경된 컴포넌트는 그 컴포넌트부터 자식 컴포넌트까지 리렌더링한다. 하지만 이런 식의 교체는 많은 PC자원을 소모하게 된다. 이는 브라우저의 렌더링 방식 때문이다.

  1. HTML을 파싱하여 DOM 객체를 생성하고, CSS를 파싱하여 스타일 규칙을 만든다.
  2. 이 두개를 합쳐 실제로 웹 브라우저에 보여질 요소를 표현한 “렌더트리”를 만든다.
  3. 이 렌더 트리를 기준으로 레이아웃을 배치하고, 색을 칠하는 등의 작업을 한다.

만약 변경해야할 사항이 100개라면, 100번 위와 같은 과정을 반복하게 된다.


어떻게 해결했는가?

Virtual DOM이라는 DOM을 추상화한 가상의 객체를 메모리에 만들어 놓고, DOM과 유사한 역할을 담당하게 한다.

만약 변경사항이 발생할 경우, DOM을 직접 수정하는 것이 아니라 중간단계로 Virtual DOM을 수정하고 Virtual DOM을 통해서 DOM을 수정하게 한다. 이를 통해 Virtual DOM에 변경내역을 한 번에 모아서(버퍼링) 실제 DOM과 변경된 Virtual DOM의 차이를 판단한 후, 구성요소의 변경이 필요한 부분만 찾아 그에 따른 렌더링을 한번만 하도록 하여 해결하였다.

즉, 리액트에서 상태변화가 발생했을 경우 교체해야할 컴포넌트를 먼저 Virtual DOM에서 교체한다음 실제 DOM과 비교하여 rerender 해야할 부분만 rerender한다는 뜻이다.


리렌더링 과정

  1. 상태 변경, props 변경 등으로 컴포넌트 리렌더링 시작
  2. 구현부 실행, hook 실행, 내부 변수 및 함수 재생성
  3. return 실행, 렌더링 시작
  4. 렌더단계 : 새로운 가상 DOM 생성 후 이전 가상 DOM과 비교해 달라진 부분을 탐색하여 실제 DOM에 반영할 부분을 결정
  5. 커밋단계 : 달라진 부분만 실제 DOM에 반영
  6. useLayoutEffect : 브라우저가 화면에 Paint 하기 전에 useLayoutEffect에 등록해둔 effect 가 동기적으로 실행되며, 이때 상태가 변한다면 다시 리렌더링
  7. Paint : 브라우저가 실제 DOM을 화면에 그림. componentDidUpdate 실행
  8. useEffect : update되어 화면에 그려진 직후, useEffect에 등록해둔 effect가 비동기로 실행


virtual dom의 한계

  • 0.1초마다 변경되는 화면을, Virtual DOM에 0.5초씩 모아서 렌더링할 수 있을까?
    • 안된다. 동시에 변경되는 것에 한해서만 렌더링됨
  • Virtual DOM은 무조건 빠른가?
    • 아니다. 반복렌더링 하지 않도록 해줘야한다.
  • Virtual DOM은 메모리에 있기에 메모리의 사용이 늘어난다.
  • Virtual DOM을 조작하는 것도 엄청나게 많은 컴포넌트를 조작하게 된다면 오버헤드가 생기기마련이다. Virtual DOM 제어가 직접 DOM제어에 비해 상대적으로 비용이 적게 들 뿐이다.


오해

  • React는 virtual DOM을 사용하기에 DOM API로 직접 엘리먼트를 수정하는 것보다 빠르다?
    • 아니다. React 도 결국 DOM API로 엘리먼트를 리렌더하기에 DOM API로 직접 수정하는 것보다 빠르지 않다.
    • 다만, 100번의 DOM API 사용을, Virtual DOM으로 모아 1번의 DOM API 사용으로 줄여주기에, virtual DOM 없이 리액트를 사용하는 것보다 더 빠른 것이다.
    • 즉, Virtual DOM은 리액트를 빠르게 하는 수단이다.


재조정(Reconciliation)

리액트에서 가상돔을 실제 DOM에 반영하는 과정을 재조정이라고 한다. 위 리렌더링 과정의 렌더에 해당한다. 이때 하나의 트리를 다른 트리로 변환하기 위한 최소한의 연산 수는 노드가 n일때 O(n^3)이다.

이는 매우 느리기에 리액트에서는 실제 DOM과 가상 DOM을 비교하기 위해 diffing 알고리즘을 사용한다. 리액트는 아래의 2가지 가정을 기반으로 O(n) 시간 복잡도를 가지는 휴리스틱 알고리즘을 구현하였다.

  • 서로 다른 타입을 가진 두 엘리먼트는 다른 트리를 만들어낸다.
  • 개발자가 key prop을 통해 자식 엘리먼트의 변경 여부를 표시할 수 있다.

좀 더 자세히, 리액트에서 두 개의 트리를 비교할 때 두 엘리먼트의 루트 엘리먼트부터 비교한다. 이후의 동작은 루트 엘리먼트의 타입에 따라 달라진다.

  • 엘리먼트의 타입이 다른 경우
    • 두 루트 엘리먼트의 타입이 다르면, React는 이전 트리를 버리고 새로운 트리를 구축한다. <a> 에서 <img>로, <Article>에서 <Comment>로 바뀌는 경우를 말한다.
    • 트리를 버릴 때 이전 DOM 노드들은 모두 파괴된다. 컴포넌트 인스턴스는 componentWillUnmount()를 실행한다. 새로운 트리가 만들어질 떄 새로운 DOM 노드들이 DOM에 삽입된다. 그에 따라 컴포넌트 인스턴스는 UNSAFE_componentWillMount()가 실행되고 componentDidMount()가 실행된다. 이전 트리와 연관된 모든 state는 사라진다.
  • DOM 엘리먼트의 타입이 같은 경우
    • 같은 타입의 두 React DOM 엘리먼트를 비교할 때, React는 동일한 속성은 유지하고 변경된 속성들만 갱신한다.
    • style이 갱신될 때는 변경된 속성만 변경한다. style= -> style=일때, fontWeight는 건들지 않고, color만 수정한다.
  • 같은 타입의 컴포넌트 엘리먼트
    • 컴포넌트가 갱신되면 인스턴스는 동일하게 유지되어 렌더링 간 state가 유지된다. React는 새로운 엘리먼트의 내용을 반영하기 위해 현재 컴포넌트 인스턴스의 props를 갱신한다. 이때 해당 인스턴스의 componentDidUpdate를 호출한다.

이후 render() 메서드가 호출되고 diffing 알고리즘은 재귀적으로 자식에 대해 처리한다. 리액트는 기본적으로 동시에 두 리스트를 순회하고 차이점이 있으면 변경을 생성한다.

예를 들어 리스트 끝에 엘리먼트를 추가하면 두 트리 사이의 변경은 잘 작동한다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+
<ul>
+  <li>first</li>
+  <li>second</li>
+</ul>
+
+<ul>
+  <li>first</li>
+  <li>second</li>
+  <li>third</li>
+</ul>
+

하지만 리스트의 맨 앞에 엘리먼트를 추가하면 성능은 좋지 않다. 모든 엘리먼트를 다시 생성하기 때문이다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+
<ul>
+  <li>Duke</li>
+  <li>Villanova</li>
+</ul>
+
+<ul>
+  <li>Connecticut</li>
+  <li>Duke</li>
+  <li>Villanova</li>
+</ul>
+

이러한 문제를 해결하기 위해 리액트는 key 속성을 제공한다. 자식들이 같은 key를 가지고 있으면 React는 key를 통해 기존 트리와 이후 트리의 자식들이 일치하는지 확인한다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+
<ul>
+  <li key="2015">Duke</li>
+  <li key="2016">Villanova</li>
+</ul>
+
+<ul>
+  <li key="2014">Connecticut</li>
+  <li key="2015">Duke</li>
+  <li key="2016">Villanova</li>
+</ul>
+

리액트는 이제 2014 key를 가진 엘리먼트가 추가되었고, 2015, 2016 key를 가진 엘리먼트는 그저 이동만 하면 되는 것을 알 수 있다. 이때 사용하는 key는 형제들 사이에서 유일하면 된다.

최후의 경우 배열의 인덱스를 key로 사용할 수 있다. 하지만 이 경우 배열이 재배열되면 컴포넌트의 state가 꼬일 수 있다. 컴포넌트 인스턴스는 key를 기반으로 갱신되고 재사용되기 때문에, 인덱스를 key로 사용하면 항목의 순서가 바뀌었을 때 key 또한 바뀌기 때문이다. 예시 개선된 예시


출처

https://jeong-pro.tistory.com/210

https://stackoverflow.com/questions/61245695/how-exactly-is-reacts-virtual-dom-faster

https://narup.tistory.com/272

This post is licensed under CC BY 4.0 by the author.

React 18로 업데이트 방법, 문제점 정리

학습 내용 요약

Comments powered by Disqus.

diff --git a/posts/was-webserver/index.html b/posts/was-webserver/index.html new file mode 100644 index 000000000..8b86ff018 --- /dev/null +++ b/posts/was-webserver/index.html @@ -0,0 +1 @@ + WAS vs 웹서버 | 디피의 개발일지
Posts WAS vs 웹서버
Post
Cancel

WAS vs 웹서버

WAS(Web Application Server)

  • DB 조회나 다양한 로직처리를 요구하는 동적인 컨텐츠를 제공하기 위해 만들어진 Application Server
  • HTTP를 통해 컴퓨터나 장치에 애플리케이션을 수행해주는 미들웨어(소프트웨어 엔진)이다.
  • 웹 컨테이너 혹은 서블릿 컨테이너 라고도 불린다.
    • 컨테이너란, jsp, servlet을 실행시킬 수 있는 소프트웨어를 말함
    • 즉, WAS란 JSP, servlet 구동환경을 제공한다.
  • 쉬운 설명 : 포트에 연결되어 요청을 프로그램으로 쏴주고, 응답을 보내주는 미들웨어.
  • 예시 : Tomcat, JBoss, Jeus, Web Sphere

역할

  • WAS = Web Server + Web container
  • 웹 서블릿 기능들을 구조적으로 분리하여 처리하고자 하는 목적으로 제시되었다.
    • 분산 트랜잭션, 보안, 메시징, 스레드 처리등의 기능을 처리하는 분산환경에서 사용된다.
    • 주로 DB 서버와 함께 수행된다.
  • 현재는 WAS가 가지고 있는 Web server도 정적인 컨텐츠를 처리하는데 있어서 성능상 큰차이가 없다.

주요 기능

  • 프로그램 실행환경과 DB 접속 기능 제공
  • 여러 개의 트랜잭션 관리기능
  • 업무를 처리하는 비즈니스 로직 수행


Web server

  • 웹 브라우저 클라이언트로부터 HTTP 요청을 받아 정적인 컨텐츠를 제공하는 컴퓨터 프로그램
  • 예시 : Apache Server, Nginx, IIS

기능

  • HTTP 프로토콜을 기반으로 하여 클라이언트의 요청을 서비스하는 기능을 담당한다.
  • 기능 1)
    • 정적인 컨텐츠 제공
    • WAS를 거치지 않고 바로 자원을 제공한다.
  • 기능 2)
    • 동적인 컨텐츠 제공을 위한 요청전달
    • 클라이언트의 요청을 WAS에 보내고, WAS가 처리한 결과를 클라이언트에게 전달한다.


WAS와 웹서버를 분리한 이유

  1. 기능을 분리하여 서버부하 방지
    • 단순한 정적 컨텐츠는 Web Server에서 빠르게 클라이언트에 제공하는 것이 좋기에.
  2. 물리적으로 분리하여 보안 강화
    • SSL에 대한 암복호화 처리에 웹서버 사용
  3. 여러 대의 WAS 연결 가능
    • 로드 밸런싱을 위해 웹서버를 사용하고, 여러 개의 WAS를 웹서버에 붙일 수 있다.
    • fail over, fail back 처리에 유리하다
    • 대용량 웹 어플리케이션(여러 개의 서버 사용)의 경우 웹서버와 WAS를 분리하여 무중단 운영을 위한 장애 극복에 쉽게 대응할 수 있다.
      • ex) 오류가 발생한 WAS를 이용하지 못하도록, 앞 단의 웹 서버에서 처리할 수 있음. 그틈에 오류가 난 WAS를 재시작함으로써 사용자는 오류를 느끼지 못함

즉, 자원 이용의 효율성 및 장애극복, 배포 및 유지보수의 편의성을 위해 WAS와 웹서버를 분리한다.


웹서버 + WAS 아키텍처

img

  1. 웹 서버는 웹 브라우저 클라이언트로부터 HTTP 요청을 받는다.
  2. 웹 서버는 클라이언트의 요청을 WAS에 보낸다.
  3. WAS는 관련된 Servlet을 메모리에 올린다.
  4. WAS는 web.xml을 참조하여 해당 Servlet에 대한 Thread를 생성한다.(Thread Pool 이용)
  5. HttpServletRequest와 HttpServletResponse 객체를 생성하여 Servlet에 전달한다
    1. Thread는 Servlet의 service() 메서드를 호출한다
    2. service() 메서드는 요청에 맞게 doGet() 또는 doPost() 메서드를 호출한다.
    3. protected doGet(HttpServletRequest request, HttpServletResponse response)
  6. doGet() 또는 doPost() 메서드는 인자에 맞게 생성된 적절한 동적 페이지를 Response 객체에 담아 WAS에 전달한다.
  7. WAS는 Response 객체를 HttpResponse 형태로 바꾸어 Web Server에 전달한다
  8. 생성된 Thread를 종료하고, HttpServletRequest와 HttpServletResponse 객체를 제거한다.


여담

node.js의 경우 엄밀히 말하면 런타임이지 웹 서버는 아니다. 웹서버를 가진 런타임이라고 보는 것이 타당하다.

기능으로는 정정파일 제공, WAS 기능 모두 수행한다.


출처

https://gmlwjd9405.github.io/2018/10/27/webserver-vs-was.html

This post is licensed under CC BY 4.0 by the author.
diff --git a/posts/web-CORS/index.html b/posts/web-CORS/index.html new file mode 100644 index 000000000..d63cfa19a --- /dev/null +++ b/posts/web-CORS/index.html @@ -0,0 +1 @@ + CORS | 디피의 개발일지
Posts CORS
Post
Cancel

CORS

CORS : Cross-Origin Resource Sharing(교차 출처 리소스 공유)

서로 다른 출처(Origin)에서 리소스를 공유할 때 적용되는 정책이다.


출처(Origin)이란?

URL은 다음과 같이 여러 개의 구성요소로 이루어져있다.

여기서 출처란 protocol + host + port 을 뜻한다. 이 조합이 같아야 같은 출처로 인정된다.

이때 http, https는 각각 80 443 포트로 추정되어 생략되어도 된다

하지만 만약 http://google.com:80 같이 포트 번호가 명시되어있다면 포트번호까지 모두 일치해야 같은 출처라고 인정된다.


SOP(Same-Origin Policy)

같은 출처에서만 리소스를 공유할 수 있다는 규칙을 가지며, CORS와 함께 다른 출처로 리소스를 공유할때 적용되는 또다른 정책이다.

하지만 웹은 다른 출처에서 리소스를 가져오는 일이 매우 많기에 몇가지 예외조항을 두었고, 그 중 하나가 “CORS 정책을 지킨 리소스 요청”이다. 따라서 웹에서 다른 출처로 리소스를 요청할 때는 CORS 정책을 지키든가 몇가지 SOP의 예외사항을 지켜야한다.

예외사항 : 실행 가능한 스크립트, 렌더될 이미지, 스타일 시트을 요청하는 경우


CORS 동작원리

  1. 웹 클라이언트 어플리케이션에서 다른 출처로 리소스 요청을 할 때, 브라우저는 요청헤더에 다음과 같이 Origin 필드에 출처를 담아 보낸다.

    Origin : https://google.com

  2. 서버는 이 요청에 대한 응답을 할 때, 응답 헤더에 Access-Control-Allow-Origin이라는 값에 이 리소스를 접근하는 것이 허용된 출처를 내려준다

  3. 응답을 받은 브라우저는 자신이 보냈던 요청의 Origin 과 응답의 Access-Control-Allow-Origin를 비교해본 후 이 응답이 유효한지 확인한다.


CORS 정책 로직의 위치

CORS 정책이 적용되어 출처를 비교하는 로직은, 서버가 아니라 브라우저에 구현되어있다.

만약 우리가 CORS 정책을 위반하는 리소스 요청을 하더라도 해당 서버가 같은 출처에서 보낸 요청만 받겠다는 로직을 가지고 있지 않으면 서버는 정상적으로 응답하고, 브라우저가 이 응답을 분석해서 CORS 정책 위반이라고 판단되면 그 응답을 버린다.

따라서 CORS 정책은 브라우저를 통하지 않은 서버간 통신일 때는 적용되지 않는다. 또한 CORS 정책을 위반하는 리소스 요청 때문에 에러가 발생했다고 해도 서버쪽 로그에는 정상적으로 응답을 했다는 로그만 남는다.


Preflight Request

브라우저에서 요청을 보낼 때 한번에 보내지 않고, 예비요청(Preflight Request)과 본 요청으로 나누어서 보낸다. 이 Preflight는 HTTP 메소드 중 OPTIONS 메소드가 사용된다.

이렇게 나누어서 보내는 이유는, 본 요청을 보내기 전에 브라우저 스스로 이 요청을 보내는 것이 안전한지 확인하기 위함이다.

과정

  1. 사용자가 보낸 요청이 브라우저에 전달하면, 브라우저는 먼저 예비요청을 서버로 보낸다.
  2. 서버는 예비요청을 받으면 응답으로 현재 자신이 어떤 것들을 허용하고 금지하는지에 대한 정보를 응답 헤더에 담아서 브라우저에게 다시 보내준다.
  3. 브라우저는 자신이 보낸 예비 요청과 서버의 응답에 담긴 허용정책을 비교하고, 이 요청을 보내는 것이 안전하다고 판단되면 본요청을 보낸다. 하지만 안전하지 않다고 판단되면 이때 CORS 에러를 내게 된다.

다만 모든 상황에서 이렇게 두 번씩 요청을 보내진 않는다. 이러한 예비요청을 보내지 않는 요청을 Simple Request 라고 한다.


Simple Request

Simple Request를 보낼 때는, 예비 요청을 보내지 않고 바로 서버에게 본 요청을 보낸 후, 서버의 응답 헤더에 Access-Control-Allow-Origin를 확인하여 CORS 정책을 검사하는 방식으로 이루어진다.

Simple Request 조건

  1. 요청의 메소드는 GET, HEAD, POST 중 하나여야 한다.
  2. Accept, Accept-Language, Content-Language, Content-Type, DPR, Downlink, Save-Data, Viewport-Width, Width를 제외한 헤더를 사용하면 안된다.
  3. 만약 Content-Type를 사용하는 경우에는 application/x-www-form-urlencoded, multipart/form-data, text/plain만 허용된다.


Credentialed Request

쿠키나 인증정보가 헤더에 담긴 요청을 뜻하며, 이 경우에는 CORS 정책 위반을 검사하는 룰에 다음 두가지가 추가된다.

  1. Access-Control-Allow-Origin에는 *를 사용할 수 없으며, 명시적인 URL이어야한다.
  2. 응답 헤더에는 반드시 Access-Control-Allow-Credentials: true가 존재해야한다.


CORS를 해결하기 위해 개발 시 지켜야하는 상황

기본적으로 정책을 다 지키면 되지만, 아래 사항을 개발할 때 항상 생각하고 진행하자

  1. 서버에서는 클라이언트에서 사용할 도메인을 Access-Control-Allow-Origin에 항상 명시적으로 세팅하자
  2. 프론트 개발 환경에서는 ` Webpack dev server`로 리버스 프록싱을 통해 서버에서 세팅한 Origin과 맞추어주자. (1에서 세팅한 프론트가 배포환경에서 사용할 주소)


쿠키의 samesite vs CORS

samesite 옵션은 같은 도메인인지를 판단하는 것이고, CORS 정책은 도메인을 포함한 포트, 프로토콜을 모두 검사하는 정책이다. 둘은 무관계하다.

samesite의 경우 서브도메인도 같은 도메인으로 판별해준다. 다만 모든 서브도메인을 같다고 판별하지는 않고, Public Suffix Lists에 명시된 기준을 참고하여 어느 범위까지 동일할 때 same site로 판단할지를 결정한다.

.co.kr 같이 세번째 자리부터 등록이 가능한 경우는 kr 앞에 co가 이미 추가되어있어서 same-site의 범위가 너무 넓어진다.


참고문서 : CORS

참고문서 : sameorigin vs samesite

This post is licensed under CC BY 4.0 by the author.
diff --git a/posts/web-Content-Disposition/index.html b/posts/web-Content-Disposition/index.html new file mode 100644 index 000000000..62e606fa8 --- /dev/null +++ b/posts/web-Content-Disposition/index.html @@ -0,0 +1,27 @@ + Content-Disposition | 디피의 개발일지
Posts Content-Disposition
Post
Cancel

Content-Disposition

HTTP Response Body에 오는 컨텐츠의 기질/성향을 알려주는 HTTP 헤더 속성이다.

  • inline : 디폴트 값. body에 있는 값이 웹 페이지에 표시되어야한다는 뜻.

    Content-Disposition : inline

    • 최신 브라우저에서 <a> 태그의 download 속성은 Content-Disposition : inline으로 가져온다.
  • attachment : body에 있는 데이터를 다운 받아야한다는 뜻. filename이 명시되어있다면, 해당 filename으로 다운 받는다.

    Content-Disposition : attachment

    Content-Disposition : attachment; filename="filename.jpg"

  • form-data : body가 multipart/form-datwa일 경우 적용된다. 데이터가 여러 파트로 나눠져서 보내지도록 해줌. 다음과 같이 name으로 나눠진 필드를 나타낸다.

    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +
    POST /test.html HTTP/1.1
    +Host: example.org
    +Content-Type: multipart/form-data;boundary="boundary"
    +  
    +--boundary
    +Content-Disposition: form-data; name="field1"
    +  
    +value1
    +--boundary
    +Content-Disposition: form-data; name="field2"; filename="example.txt"
    +  
    +value2
    +--boundary--
    +
    • name : 가리키는 subpart가 속한 필드의 이름
      • 같은 필드에서 여러 개의 파일을 다룰 때, 여러개의 subpart가 같은 값을 가질 수 있다.
      • 값이 _charset_ 이면 해당 part가 HTML 필드가 아니라, 디폴트 charset을 사용하겠다는 것을 의미한다.

출처

https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition

https://lannstark.tistory.com/8

This post is licensed under CC BY 4.0 by the author.

스프링 캐시

equals(), hashCode()

Comments powered by Disqus.

diff --git a/posts/web-Headless-CMS/index.html b/posts/web-Headless-CMS/index.html new file mode 100644 index 000000000..5a957e219 --- /dev/null +++ b/posts/web-Headless-CMS/index.html @@ -0,0 +1,29 @@ + Headless CMS | 디피의 개발일지
Posts Headless CMS
Post
Cancel

Headless CMS

Headless CMS 란?

웹사이트를 만들 때는 반드시 컨텐츠(데이터)가 필요하다. Headless CMS는 컨텐츠를 보여줄 수단인 Head와 컨텐츠를 분리한 구조로, 컨텐츠가 Head에 독립적으로 동작할 수 있도록 하는 구조이다.

![1E3qz8MZ8zR7Y3NRghFOvJQ](https://miro.medium.com/v2/resize:fit:1400/format:webp/1E3qz8MZ8zR7Y3NRghFOvJQ.png)

기존의 CMS는 컨텐츠와 Head가 강하게 묶여있었다. Html에 하드코딩된 문자열들을 생각하면 된다.

1
+2
+3
+4
+
<!-- 기존의 CMS -->
+<div>
+  content	
+</div>
+

이렇게하면 구현은 매우 간단하지만, html과 문자열이 하나의 파일로 강하게 결합되어있다.

이때 이러한 html이 배포된 상황이라고 해보자. 시간이 흘러 문자열을 수정하고 싶을 때가 온다. 그럼 이 html 파일을 직접 수정하고, 다시 배포하는 과정을 거쳐야한다. 단순히 html 파일 하나만 배포하면 되는 상황이면 큰 문제는 되지않는다. 하지만, 시스템이 복잡해지고 배포과정이 복잡해지면 배포작업은 큰 부담이 된다. 리얼환경 반영까지 오랜 시간이 걸리수도 있다.

하지만 다음과 같이 Headless CMS로 구현하면 상황은 다르다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+
<!-- Headless CMS -->
+<div id="content">
+  <!-- content가 삽입될 곳 -->
+</div>
+<script>
+  const contentDiv = document.getElementById("content")
+	fetch("/path-to-api").then((res) => {
+    contentDib.innerText = res.content;
+  })
+</script>
+

데이터베이스, 스토리지 등에 저장된 컨텐츠를 가져와 적절한 위치에 삽입해줌으로써 배포과정없이 변경된 컨텐츠를 반영할 수 있다. 새로운 문자열을 그저 데이터베이스나 스토리지에서 업데이트하면 되기 때문이다.

물론 코드는 복잡해지고, 네트워크 비용이 발생한다는 단점이 있다. 하지만, 자주 변경될 것이라 예상되는 컨텐츠에만 Headless CMS 방식을 적용하면 배포횟수가 줄어들고 결과적으로 개발자는 다른 일에 집중할 수 있게 된다. 또한 수정이 반영되는 속도도 매우 빠르다.

여기서는 배포횟수와 반영속도만을 가지고 장점을 얘기했지만 다음과 같은 장점들도 존재한다.

  • Flexibility : 컨텐츠를 하나만 만들면 웹, 앱 등 다양한 채널에 적용이 가능하다.
  • Scalability : Headless CMS는 많은 컨텐츠에 대응이 쉬워서, 성장해가는 서비스에서 적합하다.
  • Security : presentation layer와 결합되어있지 않아 기존의 CMS보다 안전하다. 유저데이터 등 민감한 정보를 코드상에 저장하지 않아도 된다.
This post is licensed under CC BY 4.0 by the author.
diff --git a/posts/web-STOMP/index.html b/posts/web-STOMP/index.html new file mode 100644 index 000000000..957561af9 --- /dev/null +++ b/posts/web-STOMP/index.html @@ -0,0 +1,149 @@ + STOMP | 디피의 개발일지
Posts STOMP
Post
Cancel

STOMP

STOMP(Simple Text Oriented Messaging Protocol)란?

웹소켓 위에서 동작하는 서브 프로토콜로, 클라이언트와 서버가 서로 통신하는데 있어 메시지의 형식, 유형, 내용 등을 정의해주는 프로토콜이다.

웹 소켓 프로토콜은 Text 또는 binary 두가지 유형의 메시지 타입을 정의하지만 메시지의 내용에 대해서는 정의하지 않는다. 즉, 웹 소켓만 사용해서 구현하게 되면 해당 메시지가 어떤 요청인지, 어떤 포맷으로 오는지 정해져있지 않아 일일이 구현해야한다.

이때 STOMP라는 프로토콜을 서브 프로토콜로 사용하여, 단순한 binary, text가 아니라 규격을 갖춘 메시지를 보낼 수 있다.

또한 기본적으로 pub/sub 구조로 되어있어 메시지를 전송하고 메시지를 받아 처리하는 부분이 확실히 정해져 있기 때문에 개발자 입장에서 명확하게 인지하고 개발할 수 있는 이점이 있다.

STOMP를 이용하면 메세지의 헤더에 값을 줄 수 있어 헤더 값을 기반으로 통신시 인증 처리를 구현하는 것도 가능하며, STMOP 스펙에 정의한 규칙만 잘 지키면 여러 언어 및 플랫폼간 메시지를 상호 운영할 수 있다.

스프링은 spring-websocket 모듈을 통해서 STOMP를 제공하고 있다.


형식

형식

STOMP는 HTTP에서 모델링되는 Frame 기반 프로토콜이다. Frame은 몇 개의 Test line으로 지정된 구조인데 첫번째 라인은 Text이고, 이후 Key:Value 형태로 Header의 정보를 포함한다. 그 다음 빈 라인을 추가하고 Payload가 존재한다.

아래 형식을 보면 HTTP와 유사하다는 것을 알 수 있다.

1
+2
+3
+4
+5
+
COMMAND
+header1:value1
+header2:value2
+
+Body^@
+
  • COMMAND

    • 클라이언트는 메시지를 전송하기 위해 COMMAND로 SEND 또는 SUBSCRIBE 명령을 사용한다
    • MESSAGE COMMAND는 모든 구독자에게 메세지를 브로드캐스팅할 때 사용한다.
    • 그외 다음과 같은 COMMAND가 있다
      • UNSUBSCRIBE, BEGIN, COMMIT, ABORT, ACK, NACK, DISCONNECT
  • header, value

    • 메시지의 수신 대상과 메시지에 대한 정보를 설명한다.

    • 기존 웹소켓만으로는 표현할 수 없는 형식이다.

    • destination : 헤더 중 하나로, 이 헤더로 지정한 주소로 메세지를 보내거나 구독할 수 있다.

      • destination은 의도적으로 정보를 불분명하게 정의하였는데, 이는 STOMP 구현체에서 문자열 구문에 따라 직접 의미를 부여하도록 하기 위함이다.

      • 일반적으로 다음의 형식을 따른다.

        1
        +2
        +
        "/topic/..." : publish - subscribe(1:N)
        +"/queue/..." : point-to-point (1:1)
        +

메시지 브로커

publisher로부터 전달 받은 메시지를 subscriber로 전달해주는 중간역할을 수행한다. 동작방식은 다음과 같다.

  1. 각각 A, B, C라는 유저가 차례로 5번방에 입장한다.

    1
    +2
    +3
    +4
    +
    SUBSCRIBE
    +destination:/subscribe/chat/room/5
    +   
    +^@
    +
    • 유저가 입장하면서 5번방에 대한 구독을 한다.
    • 메시지 브로커는 클라이언트의 SUBSCRIBE 정보를 자체적으로 메모리에 유지한다.
  2. A가 5번방에서 채팅을 전송한다.

    1
    +2
    +3
    +4
    +5
    +
    SEND
    +content-type:application/json
    +destination:/publish/chat
    +   
    +{"chatRoomId":5,"type":"MESSAGE","writer":"clientB"}^@
    +
  3. 5번방 메시지 브로커가 메시지를 받는다.

  4. 5번방 메시지 브로커가 5번방 구독자(A, B, C)에게 메시지를 전송한다.


Spring에서 STOMP로 구현

웹 소켓에 STOMP, 메시지 브로커 연동

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+
@Configuration
+@EnableWebSocketMessageBroker
+public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
+    @Override
+    public void configureMessageBroker(MessageBrokerRegistry registry) {
+      registry.enableSimpleBroker("/subscribe");
+      registry.setApplicationDestinationPrefixes("/publish");
+    }
+    
+    @Override
+    public void registerStompEndpoints(StompEndpointRegistry registry) {
+      registry.addEndpoint("/ws-connection")
+              .setAllowedOrigins("*")
+              .withSockJS();
+    }
+}
+
  • @EnableWebSocketMessageBroker
    • STOMP를 사용하기 위해 선언하는 애노테이션
  • WebSocketMessageBrokerConfigurer
    • 메시지 브로커가 지원하는 웹소켓 메시지 처리를 활성화한다.
  • configureMessageBroker()
    • 인 메모리 기반의 Simple Message Broker를 활성화한다.
      • RabbitMQ, ActiveMQ 같은 외부 메세징 시스템을 사용할 수 있다.
    • 메시지 브로커는 enableSimpleBroker()로 설정한 값으로 시작하는 주소의 subscriber들에게 메시지를 전달하는 역할을 한다. 예시에서는 /subscribe로 설정하여 /subscribe로 들어온 요청은 구독으로 정해진다.
    • 마찬가지로 setApplicationDestinationPrefixes()로 설정한 값은 publish를 위해 사용된다,
  • registerStompEndpionts()
    • HandShake와 통신을 담당한 EndPoint를 지정한다.
    • 예시에서는 클라이언트에서 서버로 WebSocket 연결을 하고 싶을 때, /ws-connection으로 요청을 보내도록 설정하였다.

ChatRequest DTO

1
+2
+3
+4
+5
+6
+
public class ChatRequest {
+    private Long senderId;
+    private Long receiverId;
+    private Long roomId;
+    private String message;
+}
+

Controller

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+
@RestController
+@RequiredArgsConstructor
+public class ChattingController {
+    private final ChattingService chattingService;
+    private final SimpMessagingTemplate simpMessagingTemplate;
+  
+    public ChattingController(ChattingService chattingService, SimpMessagingTemplate simpMessagingTemplate) {
+      this.chattingService = chattingService;
+      this.simpMessagingTemplate = simpMessagingTemplate;
+    }
+    
+    @MessageMapping("/messages")
+    public void chat(@Valid ChatRequest chatRequest) {
+      chattingService.save(chatRequest);
+      simpMessagingTemplate.convertAndSend("/subscribe/rooms/" + chatRequest.getRoomId(), chatRequest.getMessage());
+    }
+}
+
  • SimpMessagingTemplate

    • @EnableWebSocketMessageBroker을 통해서 등록되는 빈이다. 브로커로 메시지를 전달한다.
  • @MessageMapping

    • 클라이언트가 SEND 할 수 있는 경로.

    • WebSocketConfig 에서 등록한 setApplicationDestinationPrefixes() 주소와 합쳐진다

      /publish/messages

클라이언트 연동

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+
function connect() {
+    var socket = new WebSocket('/ws-connection');
+    stompClient = Stomp.over(socket);
+    stompClient.connect({}, function () {
+        setConnected(true);
+        stompClient.subscribe('/subscribe/rooms/5', function (greeting) {
+            console.log(greeting.body);
+        });
+    });
+}
+
+function sendMessage() {
+    stompClient.send("/publish/messages", {}, JSON.stringify({
+        'message': $("#name").val(),
+        'senderId': 7,
+        'receiverId': 14,
+        'roomId': 5
+    }));
+}
+


출처

https://tecoble.techcourse.co.kr/post/2021-09-05-web-socket-practice/

https://dev-gorany.tistory.com/235

This post is licensed under CC BY 4.0 by the author.
diff --git a/posts/web-SockJS/index.html b/posts/web-SockJS/index.html new file mode 100644 index 000000000..dffe47fce --- /dev/null +++ b/posts/web-SockJS/index.html @@ -0,0 +1,31 @@ + SockJS | 디피의 개발일지
Posts SockJS
Post
Cancel

SockJS

SockJS

주로 Spring을 사용할 때, WebSocket Emulation 을 위한 라이브러리이다.

WebSocket Emulation 이란 우선 웹 소켓으로 소켓 연결을 시도하고, 실패할 경우 HTTP Streaming, Long-Polling 같은 HTTP 기반의 다른 기술로 전환해 다시 연결을 시도하는 것을 말한다. 따라서 다음과 같은 상황에서 도움이 된다

  • WebSocket을 지원하지 않는 브라우저
  • Server/Client 중간에 위치한 Proxy가 Upgrade 헤더를 해석하지 못해 서버에 전달하지 못한 상황
  • Server/Client 중간에 위치한 Proxy가 유휴 상태에서 도중에 connection을 종료시킬 수도 있다.

WebSocket Emulation을 위해서 node.js를 사용한다면 Socket.io를 사용하고, Spring을 사용하면 SockJS를 사용하는 것이 일반적이다.

Spring 프레임워크는 Servlet 스택 위에서 Server/Client 용도의 SockJS 프로토콜을 모두 지원한다


SockJS의 구성

  • SockJS 프로토콜
  • SockJS javascript client : 브라우저에서 사용되는 클라이언트 라이브러리
  • SockJS Server 구현 : spring-websocket 모듈을 통해 제공
  • SockJS Java Client : spring-weboscket 모듈을 통해 제공

WebSocket Emulation Process

SockJS 클라이언트는 서버의 기본 정보를 얻기위해 GET /info를 호출하는데, 이는 다음과 같은 정보를 얻기 위함이다

  • 서버가 WebSocket을 지원하는지
  • 전송과정에서 Cookies 지원이 필요한지
  • CORS를 위한 Origin 정보

info 요청

info 응답

이후 SockJS는 어떤 전송 타입을 사용할 지 결정한 후 아래 와 같은 순서대로 사용하려고 시도한다.

  1. WebSocket
    • WebSocket Handshaking을 위한 하나의 HTTP 요청을 더 보낸다.
    • 그 이후 모든 메세지들은 Socket을 통해 교환된다.
  2. HTTP Streaming
    • Ajax/XHR Streaming은 서버 -> 클라이언트로의 메세지들을 위해 하나의 Long-running 요청이 있다.
    • 추가적인 HTTP POST 요청은 클라이언트 -> 서버로의 메세지를 위해 사용된다.
  3. HTTP Long Polling
    • 서버 -> 클라이언트로의 응답 후 현재의 요청을 끝내는 것을 제외하고는 XHR Streaming과 유사하다


동작

전송 요청 형식

모든 전송 요청은 다음의 URL 구조를 갖는다.

https://host:port/myApp/myEndpoint/{server-id}/{session-id}/{transport}

  • server-id : 클러스터에서 요청을 라우팅하는데 사용하나 이외에는 의미 없음
  • session-id : SockJS session에 소속하는 HTTP 요청과 연관성 있음
  • transport : 전송타입 (ex : websocket, xhr-streaming, xhr-polling)

SockJS 메세지 최소화

SockJS는 메세지 Frame 크기를 최소화하려고 노력한다. 예를 들어 서버는 다음과 같은 축약 메세지를 보낸다.

  • o : open frame. frame을 열 때 사용. 메세지는 ["msg1", "msg2"]와 같은 JSON-Encoded 배열로서 전달된다.,
  • h : heartbeat frame. 25초간 메세지 흐름이 없는 경우에 전송
    • 프록시가 연결이 끊겼다고 결론을 내리는 것을 방지하기 위해 보냄. 기본값은 25초.
    • STOMP를 이용해 heartbeat를 주고 받는 경우 SockJS Heartbeat 설정은 비활성화된다.
  • c : close frame. 해당 세션을 종료한다.


SockJS 사용

스프링에서

스프링에서 웹 소켓을 사용하기 위한 의존성인 spring-websocket은 SockJS 프로토콜을 모두 지원한다. 따라서 간단히 적용가능한데, 웹소켓을 설정하는 곳에서 다음과 같이 .withSockJS()를 사용하면 된다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+
@EnableWebSocketMessageBroker
+@Configuration
+public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
+	@Override
+	public void registerStompEndpoints(StompEndpointRegistry registry) {
+		registry.addEndpoint("/ws-stomp")
+			.setAllowedOrigins("*")
+			.withSockJS();		// SockJS 활성화
+	}
+}
+

internet explorer 8 & 9

SockJS는 Ajax/XHR Streaming을 microsoft의 XDomainRequest(xdr)를 통해 지원하고 있다. 이는 서로 다른 도메인 간에도 동작하지만, Cookie 전송은 지원하지 않는다.

Cookie는 Java 기반 서버에서는 필수적이지만, SockJS는 자바만이 아닌 다양한 서버 타입을 위해 고안되었기에 Cookie를 중요하게 다룰지 여부를 알려줘야한다.

SockJS는 처음에 GET /info 요청을 날리는데, 이때의 응답으로 cookie_needed 속성을 볼 수 있다. 만약 Cookie가 필요하다고 표시되냐 안되냐에 따라 사용하는 기술이 달라진다

  • cookie_needed false : XDomainRequest (xdr) 사용
  • cookie_needed true : iframe 기반의 기술 사용

스프링에서는 setSessionCookieNeeded를 통해 쿠키 사용여부를 표시할 수 있다. 기본값은 true이다.

여기서 iframe 기반의 기술 사용하려면 X-Frame-Options 응답 헤더에 대해 알아야한다.

X-Frame-Options

  • 해당 페이지를 <frame> 또는 <iframe>, <object> 에서 렌더링 할 수 있는지 여부를 나타내는데 사용되고, 사이트내 컨텐츠들이 다른 사이트에 포함되지 않도록 해 clickjacking 공격을 막아내기 위해 사용된다.
  • 다음 옵션을 포함
    • deny : 어떤 사이트에서도 frame 상에서 보여질 수 없다
    • sameorigin : 동일한 사이트의 frame에서만 보여진다
    • allow-from uri : 지정된 특정 uri의 frame에서만 보여진다.

X-Frame-Options 속성의 값이 sameorigin이나 allow-from uri이면 iframe에서 동작된다. deny일 경우에는 다음 두가지 SockJS 객체 선언 중 2번째로 선언하였을 때만 동작한다

1
+2
+3
+4
+5
+
1.
+var sockJs = new SockJS("http://localhost:8080/ws/chat");
+
+2.
+var sockJs = new SockJS("http://localhost:8080/ws/chat", null, {transports: ["websocket", "xhr-streaming", "xhr-polling"]});
+

internet explorer 10 이상

IE 10 이상부터는 웹 소켓이 지원된다. 다만 화살표 함수가 지원되지 않아 이부분만 바벨이나 직접 수정으로 해결해주면 된다.


출처

https://velog.io/@koseungbin/WebSocket

This post is licensed under CC BY 4.0 by the author.

서블릿 컨테이너

nginx + spring boot+ react로 구성된 앱 dockerfile 작성

Comments powered by Disqus.

diff --git a/posts/web-keep-alive/index.html b/posts/web-keep-alive/index.html new file mode 100644 index 000000000..2514f16e4 --- /dev/null +++ b/posts/web-keep-alive/index.html @@ -0,0 +1 @@ + Keep-Alive | 디피의 개발일지
Posts Keep-Alive
Post
Cancel

Keep-Alive

아래와 같이 http 헤더를 보다보면 keep-alive라고 명시된 부분이 보일 때가 있다.

  • connection : keep-alive
  • Keep-Alive:timeout=5, max=1000

keep-alive는 무슨 뜻일까 궁금하여 찾아보았다.


keep-alive

connection : keep-alive

  • connection은 네트워크 연결을 현재 요청이 종료된 이후에도 유지할 것인가를 설정하는 헤더이다.
  • 다음과 같은 값을 지정할 수 있음
    • close : 한 요청이 종료되면 연결을 끊음. 디폴트
    • keep-alive : 요청이 종료되어도 연결을 유지한다.

Keep-Alive:timeout=5, max=1000

  • 송신자가 연결에 대한 타임아웃과 요청 최대 갯수를 어떻게 정했는지 알려주는 헤더
    • timeout : 연결이 idle한 채로 얼마동안 유지할 것인가
    • max : 최대 몇개의 요청을 주고 close없이 처리할 것인가

Keep-alive를 사용하는 이유

  • http 프로토콜은 tcp을 기반으로 만들어져있다. 그리고 tcp에서 두 호스트가 통신을 할 때는 연결을 맺는 과정이 필요하다. 이 연결을 맺는 과정은 3-way handshaking으로 진행되며 총 세번 두 호스트가 통신을 해야 연결이 맺어지며 본래 원하는 통신이 가능해진다. 이렇게 세 번 통신하는 과정은 낭비가 있다.
  • keep-alive를 설정해주면 요청이 종료되어도 connection을 끊지 않아 다음 요청 때 한번 맺은 연결을 재사용할 수 있어 서버 요청/응답이 빨라진다.

단점

  • 만약 서버가 많은 클라이언트로부터 요청을 받아야하고, 모든 클라이언트가 keep-alive라면 서버는 모든 요청마다 연결을 유지해야하기에 프로세스 수가 기하급수적으로 늘어나 MaxClient 값을 초과하게 된다.
  • 따라서 서버에서는 keep-alive를 지원할 때는 적절히 maxClient나 KeepAliveTimeout을 설정해줘야한다.


http 버전별 차이

http 1.0에서

기본적으로 비연결지향으로 한번 통신을 하면 연결을 끊는다.

http 1.1에서

connection : keep-alive가 디폴트로 설정되어있다.

http/2에서

http/2에서는 keep-alive 기능을 사용할 필요가 없다. 그 이유는 다음과 같다.

http/2에서는 http body가 binary로 전송된다.(과거엔 text로 전송됨). 동시에 이전에는 header-body를 묶은 http message가 전송 최소단위였다면, http/2에서는 binary frame 이 전송 최소 단위가 된다. 이렇게 변화한 이유에는 기존 http 1.x가 가진 다음과 같은 단점 때문이다.

http 1.x가 가진 단점들

  • 본문은 압축이 되지만 헤더는 압축이 되지 않는다.
  • 연속된 메세지는 비슷한 헤더구조를 가지는데, 그래도 매번 헤더를 전송해야함
  • 다중전송(Multiplexing)이 불가능하다.
    • multiplexing : 하나의 연결에 여러 요청/응답을 병렬적으로 실어 보내는 것. 병렬적이라는 것은 요청을 보내고 응답을 기다리지 않고 또 다른 요청을 보내는 것을 의미
    • 이는 keep-alive를 사용하더라도 [요청-응답] [요청-응답]으로 이루어지지, [요청-요청-요청-응답-응답-응답]으로 이루어지지 않는다.

이를 해결하기 위해 http/2에서는 하나의 연결에 여러 스트림을 사용하여 multiplexing이 가능하도록 했다. 이것이 가능한 이유는 http/2 패킷을 binary frame 단위로 세분화하여 순서에 상관없이 받는 쪽에서 조립할 수 있도록 설계하였기 때문이다. 이러한 multiplexing 덕분에 http/2 에서는 연결을 유지하기 위한 설정인 keep-alive가 더이상 필요없어지게 되었다.

multiplexing

이외에도 http/2에서는 다음과 같은 성능 향상 기법이 적용되었다.

  • 헤더 압축
    • http/1.x에서 매 요청마다 헤더를 보내야했던 것과 다르게, http/2에서는 중복되는 헤더를 압축하여 사용한다.
  • 서버 푸쉬
    • http/1.x에서는 클라이언트가 요청을 하고 서버가 그에 응답을 하는 것이 일반적이었다.
    • 하지만 http/2에서는 서버 푸쉬 기능을 통해 클라이언트가 요청하지 않은 데이터를 서버가 스스로 전송해줄 수 있다.
    • 따라서 만약 index.html안에 style.css, script.js, image.jpg가 있다면 http/1.x에서는 매번 [요청-응답]을 반복하며 받아와야했지만, http/2에서는 한번에 내려주어 성능을 개선해준다.
  • 우선순위
    • 응답에 대한 우선순위를 정하여 먼저 전송처리 해줄 수 있다.
    • ex) css는 브라우저 렌더링을 막는데, 이를 최대한 빨리 내보내줘서 브라우저 렌더링을 진행함.

참고문서

참고문서 2

참고문서 3

참고문서 4

This post is licensed under CC BY 4.0 by the author.

CORS

유닛테스트 구현 검증이란?

Comments powered by Disqus.

diff --git a/posts/web-module-federation/index.html b/posts/web-module-federation/index.html new file mode 100644 index 000000000..597bbba04 --- /dev/null +++ b/posts/web-module-federation/index.html @@ -0,0 +1 @@ + module federation | 디피의 개발일지
Posts module federation
Post
Cancel

module federation

webpack module federation

여러 개의 개별 빌드가 단일 어플리케이션을 형성할 수 있도록 해주는 webpack의 기능이다. 개별 빌드는 컨테이너처럼 작동하며, 빌드 간에 코드를 노출하고 소비하여 단일 통합 애플리케이션을 생성할 수 있다.

Low-Level concepts

로컬 모듈과 원격 모듈을 구별한다.

  • 로컬모듈 : 현재 빌드의 일부인 일반 모듈
  • 원격모듈 : 현재 빌드의 일부가 아니며, 원격 컨테이너에서 런타임에 로드되는 모듈

원격 모듈을 로드하는 것은 비동기 작업으로 간주된다. 원격 모듈을 사용할 때 이러한 비동기 작업은 원격 모듈과 엔트리포인트 사이에 있는 다음 청크 로드 작업에 배치된다. 청크 로드 작업 없이는 원격 모듈을 사용할 수 없다.

청크 로드작업은 일반적으로 import()를 사용하며, require.ensure 또는 require([...]) 같은 이전 구조도 지원한다.

특정 모듈에 대한 비동기 접근을 노출하는 컨테이너 엔트리를 통해 컨테이너가 생성된다. 노출된 접근은 두 단계로 구분된다.

  1. module load (비동기)
  2. module validation (동기)

1단계는 청크 로드 중에 수행된다. 2단계는 다른 로컬 및 원격 모듈과 interleave된 module validation 중에 수행된다. 이렇게 하면 모듈을 로컬에서 원격으로, 또는 그 반대로 변환해도 평가 순서가 영향을 받지 않는다.

컨테이너를 중첩할 수도 있다. 컨테이너가 다른 컨테이너의 모듈을 사용하거나, 컨테이너 간의 순환 의존성도 가능하다.

Use cases

페이지마다 빌드 분리

SPA에서 각 페이지를 서로 다른 빌드에서 컨테이너 빌드로부터 노출된다. 또한 애플리케이션 쉘은 모든 페이지를 원격 모듈로 참조하는 별도의 빌드이다. 이렇게 하면 각 페이지를 별도로 배포할 수 있다.

공통 컴포넌트 라이브러리

공통 컴포넌트를 별도의 빌드로 분리하여, 다른 애플리케이션들에서 그 컴포넌트를 불러와 사용할 수 있다. 그 컴포넌트에 변경점이 생기면 그 컴포넌트만 다시 빌드하여 배포하면 된다.


출처

  • webpack module federation : https://webpack.kr/concepts/module-federation/
This post is licensed under CC BY 4.0 by the author.
diff --git "a/posts/web-nginx\354\227\220\354\204\234-\355\231\230\352\262\275\353\263\200\354\210\230-\354\202\254\354\232\251/index.html" "b/posts/web-nginx\354\227\220\354\204\234-\355\231\230\352\262\275\353\263\200\354\210\230-\354\202\254\354\232\251/index.html" new file mode 100644 index 000000000..f92fb0ccf --- /dev/null +++ "b/posts/web-nginx\354\227\220\354\204\234-\355\231\230\352\262\275\353\263\200\354\210\230-\354\202\254\354\232\251/index.html" @@ -0,0 +1,143 @@ + nginx에서 환경변수 사용하는 방법 | 디피의 개발일지
Posts nginx에서 환경변수 사용하는 방법
Post
Cancel

nginx에서 환경변수 사용하는 방법

nginx.conf 에서 환경변수 사용

nginx.conf에서 환경변수를 사용하여 동적으로 nginx 설정을 변경하는 방법을 알아보자.

envsubst 사용하는 방법

envsubst는 인풋으로부터 $VARIABLE 또는 ${VARIABLE}로 되어있는 값을 읽어 환경변수로 바꾸어주는 프로그램이다. envsubst을 활용해서 nginx.template 파일로 환경변수가 사용된 nginx 설정파일을 작성하고, nginx가 실행될 때 envsubst를 사용해 변환해주면 된다.

step 1 : nginx.template 파일 작성

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+
worker_processes  1;
+
+events {
+    worker_connections  1024;
+}
+
+
+http {
+    include       mime.types;
+    default_type  application/octet-stream;
+
+    sendfile        on;
+    keepalive_timeout  65;
+
+    server {
+        listen       80;
+
+        root   /home1/irteam/deploy/client/build;
+        index  index.html index.htm;
+
+        location / {
+                try_files ${DOLLAR}uri ${DOLLAR}uri/ /index.html = 404;
+        }                                                                                                               
+        location /api {
+                proxy_pass http://${SPRING_HOST_NAME}:${SPRING_HOST_PORT};
+        }
+  }
+}
+
  • 환경 변수가 들어갈 자리는 ${}로 감쌌다.
    • ${SPRING_HOST_NAME} : 스프링 서버의 호스트 이름
    • ${SPRING_HOST_PORT} : 스프링 서버의 포트번호
    • ${DOLLAR} : envsubst을 사용하면 $로 시작하는 값은 전부 치환한다. 값이 존재하지 않을 경우 공백으로 변하므로, DOLLAR 환경변수를 새롭게 선언하여 $가 필요할 땐 ${DOLLAR}로 사용하자.

step 2 : 실행 시 envsubst 사용

1
+2
+3
+4
+5
+6
+
curl -L https://github.com/a8m/envsubst/releases/download/v1.2.0/envsubst-`uname -s`-`uname -m` -o envsubst # 1
+chmod +x envsubst 
+export DOLLAR='$'  # 2
+export SPRING_HOST_NAME='localhost'
+export SPRING_HOST_PORT="8080"
+./envsubst '${SPRING_HOST_NAME} ${SPRING_HOST_PORT} ${DOLLAR}' < nginx.template > nginx.conf  # 3
+
  1. envsubst가 없는 환경에서는 새로 설치한다.
  2. 환경변수를 설정하였다
  3. envsubst을 사용해 nginx.template 파일을 nginx.conf 파일로 컴파일했다.

step 3 : 결과 확인

다음과 같이 nginx.conf 파일이 환경변수로 설정한 대로 나왔다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+
worker_processes  1;
+
+events {
+    worker_connections  1024;
+}
+
+
+http {
+    include       mime.types;
+    default_type  application/octet-stream;
+
+    sendfile        on;
+    keepalive_timeout  65;
+
+    server {
+        listen       80;
+
+        root   /home1/irteam/deploy/client/build;
+        index  index.html index.htm;
+
+        location / {
+                try_files $uri $uri/ /index.html = 404;
+        }                                                                                                               
+        location /api {
+                proxy_pass http://localhost:8080;
+        }
+  }
+}
+


docker nginx 이미지 사용하는 방법.

만약 nginx를 도커 컨테이너에서 사용하고, 베이스 이미지가 nginx라면, 더 쉬운 방법이 있다.

nginx 1.19 이미지부터는 envsubst를 자동으로 지원한다. 다만 docker-compose와 함께 사용해야한다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+
web:
+  image: nginx
+  volumes:
+   - ./templates:/etc/nginx/templates
+  ports:
+   - "8080:80"
+  environment:
+   - NGINX_HOST=foobar.com
+   - NGINX_PORT=80
+

링크


출처

  • https://serverfault.com/questions/577370/how-can-i-use-environment-variables-in-nginx-conf
This post is licensed under CC BY 4.0 by the author.

kubernetes 실습

쿠버네티스 Ingress

Comments powered by Disqus.

diff --git "a/posts/web-\354\277\240\355\202\244-\354\235\270\354\246\235-vs-\354\204\270\354\205\230-vs-JWT/index.html" "b/posts/web-\354\277\240\355\202\244-\354\235\270\354\246\235-vs-\354\204\270\354\205\230-vs-JWT/index.html" new file mode 100644 index 000000000..bd15c9b46 --- /dev/null +++ "b/posts/web-\354\277\240\355\202\244-\354\235\270\354\246\235-vs-\354\204\270\354\205\230-vs-JWT/index.html" @@ -0,0 +1 @@ + 쿠키 인증 vs 세션 vs JWT | 디피의 개발일지
Posts 쿠키 인증 vs 세션 vs JWT
Post
Cancel

쿠키 인증 vs 세션 vs JWT

쿠키

사용자가 로그인하면, 사용자 식별자를 암호화 또는 평문으로 쿠키에 담는 방식. 요청이 오면 쿠키에 담긴 식별자를 보고 유저를 구분한다.

  • 장점
    • 유효한 쿠키인지만 확인하면 되기에 서버자원과 비용을 아낄 수 있다.
    • 서버를 무상태(stateless)로 만들어줌.
    • 암호화를 하면 brute force 대응 가능(특정 횟수 이상 인증 실패시 ip 잠금)
    • 간단히 구현 가능
  • 단점
    • 쿠키가 탈취되면 위장 가능

세션

인증이 완료되면 사용자 식별자를 서버(DB 등)에 저장하고 그 저장한 곳에 접근하기 위한 해시 값을 세션 ID로 지정. 이후 세션 ID를 쿠키에 담아 보내주고, 사용자로부터 요청이 오면 세션id로 사용자 식별자를 가져온다.

  • 장점
    • 세션id가 노출되어도 유의미한 정보를 가지고 있지 않음.
    • 같은 유저가 다른 곳에서 로그인할 시, 누가 로그인 되어있는지 저장하고 있기에, 하나를 막을 수 있음.
  • 단점
    • 쿠키가 탈취되면 위장 가능
    • 요청이 많아지면 서버에 부하가 심해져, 세션 DB와 서버를 확장해야 한다.
    • 세션 관리 시스템에 문제가 생기면 시스템 전체에 문제가 생긴다.

JWT

인증이 되면, JWT 토큰(access token)을 클라이언트로 보내줌. 클라이언트는 헤더에 JWT를 적용하여 서버로 요청을 보내면 서버는 JWT을 디코딩하여 유저를 식별함.


쿠키/세션 다룰 때 주의점

  • httponly을 통해 XSS 공격 막기
  • secure 옵션을 통해 패킷 탈취에 대비
  • 식별자는 암호화하여, brute force 막기.
  • 특정 횟수 이상 인증 실패 시 ip 차단
  • CSRF 에 대비하기 위해 게시글에 html 입력 불가능하도록 하기.(sanitize)
  • 쿠키 탈취에 대비하기 위해 너무 동떨어진 위치에서 요청 시 차단
  • 암호화 시에는 레인보우 테이블 공격 방어로 salt를 섞고 한번 더 암호화하는 기법(bcrypt 등) 사용
  • 만료기간 설정 or 브라우저 닫을 시 만료

JWT 다룰 때 주의점

  • refresh token 탈취 시에 대응하기 위해, 같은 refresh token 사용이 두 번 감지되면 그 refresh token 이후 발급 된 모든 refresh token을 폐기
    • https://developer-ping9.tistory.com/239
    • 다만 이 경우에도 access token만 계속 탈취하거나, 공격자가 먼저 reissue 했을 땐, 감지 할 수 없다.
  • access token 만료 전에 reissue 시 탈취된 것으로 판단하여 access token을 폐기시킬 수 있음
    • 이 경우에도 access token 만료 후 리이슈해서 접근하면 감지할 수 없음.

쿠키 vs 세션

장단점만 보면 쿠키가 서버 확장에도 대응이 돼서 쿠키가 더 좋아보인다. 그런데 왜 사람들은 세션이 좋다고 하는 것일까? 로그인 상태를 저장하기에 동시 로그인 시 하나를 막을 수 있다지만, 서비스에 따라서는 동시로그인을 허용해야하는 경우가 많다. 정확히 어떤 차이점이 있을까?

Session ID in cookies vs. Encrypted cookie

  • 쿠키 암호화가 세션에 비해 단점인 것
    • 복잡한 암호화 알고리즘 필요, secret key 관리 필요, 비중앙화됨(동시로그인 못막음)
    • 신뢰할 수 있는 암호화 알고리즘을 사용해야함
    • 크기가 커서 네트워크 비용 발생 (바이트 수준 차이라 크게 문제는 안될듯)

글을 읽었을 때 게임과 같이 동시로그인을 꼭 막아야하는 경우가 아니면, 쿠키 암호화가 성능상/구현상 더 좋은 거 같음. 암호화 알고리즘도 라이브러리화 된 신뢰성 있는 것이 많기에 글에서 본 단점도 상쇄된다. secret key 관리만 개발자가 주의하면 될 거 같음.


물리적 해킹일 땐 어떻게?

ex) PC방에서 A유저가 로그인 하고 자리를 떴는데, B가 와서 그대로 사용할 경우

  • 쿠키 / 세션의 경우 : 만료기간 두기가 최선일 거 같음.
  • JWT : refresh token 사용 + refresh token 탈취 대비책은 위에서 “주의점”에 적은 대로.

네이버 로그인 방식

  • NID_AUT, NID_SES 사용
    • 링크에 나온대로 두 쿠키를 복사한 후 다른 브라우저에 넣어봤는데, 로그인이 되었다.
    • 그런데 다른 페이지로 이동하려니 로그인이 막힘. 다른 쿠키도 차례대로 넣어봤는데 동작하지 않음
  • 네이버에 접속하자마자 바로 JSESSIONID란 쿠키가 생성됨. 이름이나 값이 단순 숫자인것으로 보아 세션은 사용하는거 같은데, 로그인 후 새로 생기는 것들은 암호화 되어있고 기존 JSESSIONID와는 독립되어보임.
  • 서비스 형태, 위에서 나온 현상을 봤을때 SSO을 네이버에서도 사용하는 것 같음. 그런데 SSO를 어떤 방식으로 인증하는 걸까?
    • 찾기가 어려움..
    • 다만 쿠키를 봤을때, JWT는 사용하지 않는 것으로 보임. 그리고 SSO는 큰 규모의 서비스를 대상으로 하는데, 세션을 사용하면 성능에 한계가 있어보임. 그럼 쿠키/세션/JWT 중에 있다면, 쿠키를 암호화하여 사용하는 것으로 보이는데 구체적인 내부 방식은 모르겠음.
    • Redis를 사용하면 세션 성능 문제를 해결할 수 있지만, 그렇게까지 세션을 고집할 이유는 없어보임.
This post is licensed under CC BY 4.0 by the author.

프론트 아키텍쳐 흐름

서블릿 필터

Comments powered by Disqus.

diff --git a/posts/yourlist/index.html b/posts/yourlist/index.html new file mode 100644 index 000000000..53faf97a4 --- /dev/null +++ b/posts/yourlist/index.html @@ -0,0 +1 @@ + Yourlist | 디피의 개발일지
Posts Yourlist
Post
Cancel

Yourlist

서비스 소개

  • 사용자가 원하는 유튜브 영상의, 원하는 부분만을 가지고 재생목록을 만들 수 있도록 하는 서비스

  • 회원가입을 통해 회원이 될 수 있고, 회원은 재생목록을 무제한으로 만들 수 있지만, 비회면원 5개까지만 가능

  • i18n을 통한 글로벌 언어 제공

  • 앱, 웹에서 모두 사용가능하도록 개발.

서비스 링크

새롭게 배운 점

react-native, expo

  • 처음으로 react-native와 react-native의 개발 플랫폼인 expo를 통해 앱개발을 하였다.
  • 이번 프로젝트를 통해서 웹과 앱의 차이를 이해하였고, 다양한 개발환경을 구축하는 것에 익숙해졌다.

i18n

  • 서비스의 특성상 언어의 제약에 없다는 점에서, 글로벌 고객을 대상으로 앱을 개발하였다.
  • 따라서 앱에 사용되는 문자열을 별도의 파일로 관리하고, i18n을 통해 사용자의 환경에 맞는 언어를 제공하였다.

JWT

  • 회원 인증방식으로 JWT를 사용하였고, 토근 기반의 인증방식에 익숙해졌다.
  • accesstoken이 만료되었을때, 자동으로 refreshToken을 이용하여 axios.interceptor를 통해 재인증하는 것을 배웠다.
  • 만약 refreshToken마저 만료되었을 경우엔, 자동으로 로그라웃 처리하여 다시 로그인하도록 유도하였다.

redux-saga

  • redux 에서 비동기적으로 상태를 업데이트하기위한 redux-saga를 사용하여, 서버와의 통신과정에서 발생하는 비동기적인 상태 변화에 유연하게 대처하였다.

배포

  • 처음으로 google playstore에 배포를 하였다.
  • 개발자 계정을 만들고, 심사를 받는 과정을 거치면서 앱의 가치에 대한 진지한 토론을 할 수 있었다.

어려웠던 부분

크로스 플랫폼

  • ios - android
    • react-native는 원래 크로스플랫폼 언어이지만, ios, android 사이에서 뷰가 다르게 보여지는 부분이 존재하였고, 이 부분을 적절히 처리해주는데 애를 먹었다.
    • 잦은 테스트를 통해 다르게 보여지는 부분을 확인하였고, 웬만하면 git issue를 확인하여 두 os에서의 처리방법을 익혔다.
    • 또, ios에서는 android와 다르게 하단에 조작버튼이 없다는 점을 감안하여 상단에 뒤로가기 버튼을 따로 추가해주었다.
  • app - web
    • 기존엔 app으로 개발하였으나, web으로의 활용성도 뛰어나다고 판단되었기에 react-native-web을 활용하여 웹으로도 개발하였다.
    • app과는 다르게 큰 화면사이즈에 대응하는게 힘이 들었다.
    • 또 react-native-web의 동작방식 차이로 app과 다르게 동작하는 부분이 많이 발생하여 이부분을 해결하는데 시간이 걸렸다.
    • 현재는 완전히 동일한 동작을 하지는 못하지만, 핵심기능은 동일하게 동작하고 뷰만 조금 다르게 보여지도록 해결하였다.

UI

img_1

img_2

img_3

img_4

img_5

img_6

img_7

This post is licensed under CC BY 4.0 by the author.

flutter 첫 걸음

LocateC

Comments powered by Disqus.

diff --git "a/posts/\353\215\260\354\275\224\353\240\210\354\235\264\355\204\260\355\214\250\355\204\264/index.html" "b/posts/\353\215\260\354\275\224\353\240\210\354\235\264\355\204\260\355\214\250\355\204\264/index.html" new file mode 100644 index 000000000..09d2ad316 --- /dev/null +++ "b/posts/\353\215\260\354\275\224\353\240\210\354\235\264\355\204\260\355\214\250\355\204\264/index.html" @@ -0,0 +1,111 @@ + 자바스크립트 데코데이터 패턴 | 디피의 개발일지
Posts 자바스크립트 데코데이터 패턴
Post
Cancel

자바스크립트 데코데이터 패턴

데코레이터 패턴

  • 하나의 코드를 다른 코드로 래핑하거나 javascript 함수를 래핑하는 방법

  • 동일한 클래스의 다른 객체에는 영향을 주지 않고, 정적/동적으로 개별 객체에 동작을 추가할 수 있는 디자인 패턴이다.

  • 문법

    1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +
    let variable = function(object) {
    +    object.property = 'characteristic';
    +}
    +  
    +@variable
    +class GFG {
    +      
    +}
    +console.log(GFG.property);
    +
  • 현재는 아래 두가지 유형의 데코레이터를 지원함

    • Class Member decorators
    • Members of classes

클래스 멤버 데코레이터

  • 클래스 단일 멤버에 적용되며, attribute, method, getter, setter를 가지고 있음
  • 아래 3가지 매개변수를 받음
    • target : 멤버가 속해있는 클래스
    • name : 클래스의 멤버이름
    • descriptor : 멤버 디스크립터 객체
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+
function gfg(target, name, descrioptor) {
+    var fn = descriptor.value;
+    
+    if(typeof fn == 'function') {
+        descriptor.value = function(...args) {
+            console.log(`parameters : ${args}`);
+            var result = fn.apply(this, args);
+            
+            console.log(`addition : ${result}`);
+            
+            return result;
+        }
+    }
+    return descriptor;
+}
+
+class geek {
+    @gfg
+    add(a, b) {
+        return a + b;
+    }
+}
+
+var e = new geek();
+e.add(100, 200);
+
1
+2
+
parameters : 100, 200
+addition : 300
+

클래스 데코레이터

  • 전체 클래스에 적용되며 단일 매개변수로 호출됨
  • 클래스의 각 인스턴스에 적용되지 않으며, 생성자 함수에만 적용된다.
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+
function log() {
+    return function decorator() {
+        return (...args) => {
+            console.log(`Parameters : ${args}`)
+            return new Class(...args);
+        }
+    }
+}
+
+@log
+class gfg {
+    constructor(name, category) {
+        
+    }
+}
+const e = new gfg('geek', 'code');
+console.log(e);
+
(...args) => {
+	console.log(`Parameter : ${args}`);
+	return new Class(...args);
+}
+
This post is licensed under CC BY 4.0 by the author.

14658 하늘에서 별똥별

Decision Tree를 이용한 서울 미세먼지 예측 모델

Comments powered by Disqus.

diff --git "a/posts/\353\246\254\354\225\241\355\212\270\353\246\254\355\214\251\355\206\240\353\247\201/index.html" "b/posts/\353\246\254\354\225\241\355\212\270\353\246\254\355\214\251\355\206\240\353\247\201/index.html" new file mode 100644 index 000000000..4ad5b5385 --- /dev/null +++ "b/posts/\353\246\254\354\225\241\355\212\270\353\246\254\355\214\251\355\206\240\353\247\201/index.html" @@ -0,0 +1,175 @@ + 리액트 리팩토링 | 디피의 개발일지
Posts 리액트 리팩토링
Post
Cancel

리액트 리팩토링

리액트 리팩토링

useEffect 사용

useEffect 올바른 사용

  • props, state 변경에 따라 다른 데이터, state를 업데이트해야할 때 useEffect의 사용을 자제하자.
  • useEffect vs EventHandler 중 어느 쪽에서 유저 인터랙션을 처리해야할까?
  • useEffect 내에서 data fetching을 할 때 주의해야할 점.


race condition 문제

주로 API 요청을 보낼 때 발생함. 요청을 여러 번 보낼 때, 앞 요청이 끝나기 전에 뒤 요청이 먼저 끝날 수도 있다. 이러한 상황을 항상 고려하자.

예시

  1. “true”란 응답이 올 때까지 앞 요청에서 “false”란 응답이 왔는데, 뒷 요청에서 “true”란 응답이 온다고 해보자. 뒷 요청이 먼저 끝나면 나중에 오는 앞 요청의 응답이 잘


Silent Error를 없애자

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+
const onClickCorrect = async () => {
+  if (
+    selectedUserState !== null 
+  ) {
+    try {
+      const res = await applyForUser({selectedUserState})
+      setApplicationId(res.applicationId);
+      navigate('/waiting');
+    } catch (e) {
+      console.log(e);
+    }   
+}};
+
  • 위와 같이 클릭 시 발동되는 이벤트핸들러가 있다고 해보자. 만약 selectedUserState 가 null 일 경우 유저는 아무리 클릭을 해도 아무런 반응을 얻지 못할 것이다.
  • 따라서 else 문을 추가하여 selectedUserState 가 null 일 경우를 대비하자.

새로고침, 뒤로가기 고려

컴포넌트가 새로고침이나 뒤로가기를 통해 다시 마운트 될 시, 고려 해야 하는 부분이 몇 가지 있다.

  • 상태가 초기화 된다.
  • useEffect가 실행된다.
  • 컴포넌트 내에서 react-query 사용 시, refetch가 이루어진다.


아래 코드를 보자. 컴포넌트가 마운트 되면, react-query는 유저타입 데이터를 받아온다. 그리고 useEffect 에서는 그 중 첫 번째 타입을 selectedUserState 상태에 설정한다.

유저타입 데이터는 선택된 상태로 렌더링 되고, 유저가 리스트에서 원하는 Row를 선택하면 그 타입으로 selectedUserState 를 업데이트한다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+
export function CreditCardTypePage() {
+    const { data: userTypes } = useQuery(reactQueryKey.USER_TYPE, fetchUserTypes);
+    const [{ userType: selectedUserType }, setSelectedUserState] = useRecoilState(recoil_SelectedUserState);
+
+    useEffect(() => {
+      if (userTypes !== undefined) {
+        setSelectedUserState(prev => ({ ...prev, userType: userTypes[0] }));
+      }
+    }, [userTypes, setSelectedUserState]);
+
+    const onClickUserTypeRow = (userType: UserType) => {
+      setSelectedUserState(prev => ({ ...prev, userType }));
+    };
+
+    return () => {
+        <List>
+            {userTypes?.map(item => (
+              <ListRow.Selectable
+                key={item.id}
+                contents={<ListRow.Text1Row top={item.title} />}
+                right={<Checkbox readOnly={true} checked={selectedUserType !== null && item.id === selectedUserType.id} />}
+                onClick={() => onClickUserTypeRow(item)}
+              />
+            ))}
+        </List>
+    }
+}
+

이 코드에는 새로고침, 뒤로가기 시 유저가 선택한 상태가 초기화 되어 첫 번째 Row가 선택된 상태로 렌더링 된다는 문제점이 존재한다. 이러한 문제점을 해결하기 위해서는 다음과 같이 처리 해주어야 한다.

  1. recoil-persist 등을 활용하여 상태를 로컬스토리지에 저장하고, 페이지가 리로드 될 시 상태를 로컬에 저장된 상태로 초기화한다.
  2. useEffect내에서 상태를 업데이트할 때, selectedUserType이 null일 때만 업데이트하도록 한다.

1번을 통해 새로고침 되어 상태가 전부 초기화 되었을 때를 대응하고, 2번을 통해 유저가 선택된 상태를 유지하도록 한다.


중복코드 분리

위 코드 예시에서 보여 준 컴포넌트는 recoil_SelectedUserState에서 userType 데이터만 사용한다. 만약 recoil_SelectedUserState가 다음과 같이 작은 상태이고 한 부분에서만 사용한다면 큰 문제는 없다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+
const recoil_SelectedUserState = atom<{
+  userType: UserType | null;
+  userDesign: UserDesign | null;
+  userFields: SelectedUserField[] | null;
+}>({
+  key: 'selectedUserState',
+  default: {
+    userType: null,
+    userDesign: null,
+    userFields: null,
+  },
+});
+

하지만 만약 userType만, userDesign만, userFields만 따로 사용하는 부분이 반복된다면 어떻게 될까? 만약 새로고침과 뒤로가기에 대응하느라 그러한 부분들을 전부 같은 형식으로 수정해야 한다면 어떻게 될까?

이럴 때가 바로 중복되는 부분을 분리해줘야 할 때다.

같은 부분을 두 번 이상 반복하고 있다면, 바로 그때가 코드를 분리해야 할 때다.

이러한 부분은 hook이나 컴포넌트로 분리할 수 있다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+
// hook
+const reactQueryPropsByUse = {
+    "userType" : {
+        reactQueryKey : reactQueryKey.USER_TYPE,
+        fetch : fetchUserTypes,
+    }, 
+}
+
+export function SingleSelectedUserState({use = "userType", defaultValue = null}) {
+    const { data } = useQuery(reactQueryPropsByUse[use].reactQueryKey, reactQueryPropsByUse[use].fetch);
+    const [{ [use]: selectedUserData }, setSelectedUserState] = useRecoilState(recoil_SelectedUserState);
+    
+    useEffect(() => {
+      if (data !== undefined && selectedUserData === null && defaultValue === null) {
+        setSelectedUserState(prev => ({ ...prev, [use]: data[0] }));
+      }
+      if(defaultValue !== null) {
+        setSelectedUserState(prev => ({ ...prev, [use]: defaultValue	] }));
+      }
+    }, [data, selectedUserData, use, defaultValue, setSelectedUserState]);
+    
+    const setSingleSelectedUserState = (userData) => {
+        setSelectedUserState(prev => ({...prev, [use] : userData}));
+    }
+    
+    
+    return [selectedUserData, setSingleSelectedUserState];
+}
+

컴포넌트로 분리할 때는 원하는 로직을 따로 뺀 후, children으로 그 로직을 사용할 컴포넌트를 불러오면 된다.

1
+2
+3
+4
+5
+6
+7
+8
+
const ModalLayout = (props) => {
+  const {children, setContent, isOpenModal} = props;
+  return (
+    <ModalBox>
+      {React.cloneElement(children, {setContent, isOpenModal})}
+    </ModalBox>
+  )
+}
+


참고

https://velog.io/@hyunjoogo/React-children-Component%EC%97%90-props-%EC%A0%84%EB%8B%AC%ED%95%98%EA%B8%B0

This post is licensed under CC BY 4.0 by the author.

자바 기본

Mysql Count 속도

Comments powered by Disqus.

diff --git "a/posts/\353\257\270\354\204\270\353\250\274\354\247\200\354\230\210\354\270\241\353\252\250\353\215\270/index.html" "b/posts/\353\257\270\354\204\270\353\250\274\354\247\200\354\230\210\354\270\241\353\252\250\353\215\270/index.html" new file mode 100644 index 000000000..a0023cb31 --- /dev/null +++ "b/posts/\353\257\270\354\204\270\353\250\274\354\247\200\354\230\210\354\270\241\353\252\250\353\215\270/index.html" @@ -0,0 +1 @@ + Decision Tree를 이용한 서울 미세먼지 예측 모델 | 디피의 개발일지
Posts Decision Tree를 이용한 서울 미세먼지 예측 모델
Post
Cancel

Decision Tree를 이용한 서울 미세먼지 예측 모델

소스코드

모델 소개

  • 중국 도시별 미세먼지 PM10 농도와, 서울의 기온, 풍속, 습도를 가지고, 서울의 하루 뒤 미세먼지 PM10 농도를 예측하는 모델
    • 서울의 기온, 풍속, 습도는 현재(중국 미세먼지 데이터 날짜와 일치)와 다음날(서울 미세먼지 데이터 날짜와 일치) 데이터를 모두 넣음.
  • 학습할 중국의 도시는 공업이 발달한 도시로 선정하였으며, 다음과 같이 총 10개 시를 선정
    • 베이징, 항저우, 청도, 충칭, 칭다오, 난징, 톈진, 쑤저우, 우한, 상하이

데이터

데이터 출처

전처리

  • feature : 일별 중국 10개도시의 미세먼지 농도, 서울의 오늘/내일 기온, 풍속, 습도
  • label : 서울의 하루 뒤 PM10 농도
    • 이때, 서울 각 구별 데이터를 하나의 독립적인 label로 취급함.
    • 즉, 하나의 feature에 조금씩 다른 label을 매칭시켜서 데이터를 급격히 증가시킴.

학습

  • DecisionTree 모델 사용

결과

  • Learning Curve

image-20220102181654794

  • RMSE : 7.2

image-20220102181633815

참조

Jeong, Yemin & Youn, Youjeong & Cho, Subin & Kim, Seoyeon & Huh, Morang & Lee, Yangwon (2020). Prediction of Daily PM10 Concentration for Air Korea Stations Using Artificial Intelligence with LDAPS Weather Data, MODIS AOD, and Chinese Air Quality Data. 대한원격탐사학회지, 대한원격탐사학회

This post is licensed under CC BY 4.0 by the author.

자바스크립트 데코데이터 패턴

generator 문법

Comments powered by Disqus.

diff --git "a/posts/\353\270\214\353\235\274\354\232\260\354\240\200-\353\217\231\354\236\221-\354\233\220\353\246\254/index.html" "b/posts/\353\270\214\353\235\274\354\232\260\354\240\200-\353\217\231\354\236\221-\354\233\220\353\246\254/index.html" new file mode 100644 index 000000000..3e945f5d7 --- /dev/null +++ "b/posts/\353\270\214\353\235\274\354\232\260\354\240\200-\353\217\231\354\236\221-\354\233\220\353\246\254/index.html" @@ -0,0 +1,21 @@ + 브라우저 동작원리 | 디피의 개발일지
Posts 브라우저 동작원리
Post
Cancel

브라우저 동작원리

기본 구조

  1. 사용자 인터페이스
  2. 브라우저 엔진 : 사용자 인터페이스와 렌더링 엔진 사이의 동작을 제어
  3. 렌더링 엔진 : 요청한 콘텐츠를 표시. HTML 을 요청하면 HTML과 CSS를 파싱하여 화면에 표시함.
  4. 통신
  5. UI 백엔드 : 콤보박스와 창 같은 기본적인 장치를 그림. 플랫폼에서 명시하지 않은 일반적인 인터페이스로서 OS 사용자 인터페이스 체계를 사용함.
  6. 자바스크립트 해석기
  7. 자료저장소 : Localstorage, SessionStorage, Cookie
  • 크롬은 각 탭마다 별도의 렌더링 엔진 인스턴스를 유지함. 즉, 각 탭은 독립된 프로세스로 처리됨.

렌더링 엔진

  • HTML 및 XML 문서와 이미지를 표시할 수 있음
    • 플러그인이나 브라주어 확장 기능을 통해 PDF 와 같은 유형도 가능하다.
  • 엔진 종류
    • 게코 : 파이어폭스
    • 웹킷 : 사파리, 크롬
  • 동작과정
    1. DOM 트리 구축을 위한 html 파싱
      • HTML을 파싱하고, 콘텐츠 트리 내부에서 태그를 DOM 노드로 변환함.
      • 그 다음 외부 css 파일과 함께 스타일 요소도 파싱함.
      • 스타일 정보와 HTML 표시 규칙은 렌더 트리라고 부르는 또 다른 트리를 생성한다.
    2. 렌더 트리(게코에서는 frame tree) 구축
      • 색상 또는 면적과 같은 시각적 속성이 있는 사각형을 포함하고, 정해진 순서대로 화면에 표시한다.
    3. 렌더 트리 배치
      • 각 노드가 화면의 정확한 위치에 표시되는 것을 의미함.
    4. 렌더 트리 그리기
      • ui 백엔드에서 렌더 트리의 각 노드를 가로지르며 형상을 만들어냄.

파싱과 DOM 트리 구축

  • 파싱 : 브라우저가 코드를 이해하고 사용할 수 있는 구조로 변환하는 것

    • 결과 : 보통 문서 구조를 나타내는 노드트리가 나옴. 파싱트리, Syntax tree 라고도 불림
  • 파서-어휘 분석기 조합

    • 어휘분석 : 자료를 토큰으로 분해하는 과정.

      • 토큰 : 유효하게 구성된 단위의 집합체로 용어집이라고도 함.
    • 구문분석 : 언어의 구문 규칙을 적용하는 과정

    • 파서 : 문서 -> 어휘분석 -> 구문분석 -> 파싱트리로 만듬.

      • 자료를 유효한 토큰으로 분해하는 어휘 분석기와 언어 구문 규칙에 따라 문서 구조를 분석함으로써 파싱트리를 생성하는 파서가 있음.
      • 어휘 분석기는 공백과 줄바꿈 같은 의미없는 문자를 제거
    • 파싱과정은 반복됨.

      • 파서는 어휘 분석기로부터 새 토큰을 받아서 구문 규칙과 일치하는지 확인함.
      • 규칙에 맞으면 토큰에 해당되는 노드가 파싱트리에 추가되고, 파서는 또 다른 토큰을 요청함.
      • 규칙에 안맞으면 파서는 토큰을 내부적으로 저장하고 토큰과 일치하는 규칙이 발견될 때까ㅏ지 요청함. 맞는 규칙이 없으면 구문 오류가 있다는 것.
  • 변환 : 파싱과정을 통해 나온 파싱트리를 기계코드로 변환시키는 과정

  • 어휘화 구문에 대한 공식적인 정의

    • 어휘는 보통 정규표현식으로 표현된다.
    • 구문은 보통 BNF라고 부르는 형식에 따라 정의된다.
    • 문법이 문맥 자유 문법이라면 언어는 정규 파서로 파싱할 수 있음.
      • 문맥 자유문법 === 완전히 BNF로 표현가능한 문법
  • 파서의 종류

    • 하향식 파서 : 구문의 상위 구조로부터 일치하는 부분을 찾음.

      • 가장 높은 수준의 규칙을 먼저 찾음.
    • 상향식 파서 : 하위 구조로부터 일치하는 부분을 찾음.

      • 입력 값이 규칙에 맞을 때까지 찾아서 맞는 입력값을 규칙으로 바꿈.

      부준적으로 일치하는 표현식은 파서 스택에 쌓이며, 오른쪽으로 갈수록 남는 것이 점차 감소하기때문에 이동-감소 파서라고 부름.

  • 파서 자동생성

    • 파서 생성기 : 파서를 생성해 줄 수 있는 도구

    • 웹킷

      • 플렉스(Flex) : 어휘 생성. 토큰의 정규 표현식 정의를 포함하는 파일을 입력 받음.

      • 바이슨(Bison) : 파서 생성. BNF 형식의 언어구문규칙을 입력받음.

HTML 파서

  • Html 마크업을 파싱 트리로 변환함.

  • 문법 정의 : W3C 에서 명세로 정의되어있음.

  • 문맥 자유문법이 아니다. 따라서 BNF로 표현불가능.

  • HTML vs XML

    • HTML : 더 너그로운편. 암묵적으로 태그에 대한 생략이 가능함. 가끔 시작 또는 종료 태그 등을 생략함.
    • 따라서 HTML은 파싱하기 어렵고 전통적인 구문 분석이 불가능하기때문에 문맥 자유 문법이 아님.
  • HTML DTD

    • HTML의 정의는 DTD 형식 안에 있는데 SGML 계열 언어의 정의를 이용한 것이다.
    • DTD 형식은 허용되는 모든 요소와 그들의 속성 그리고 중첩구조에 대한 정의를 포함한다.
  • DOM : 문서 객체 모델(Document Object Model)

    • 파싱트리는 DOM 요소와 속성 노드의 트리로서 출력 트리가 된다. 트리의 최상위 객체는 문서이다ㅏ.
    • DOM은 HTML 문서의 객체 표현이고, 외부로 향하는 자바스크립트와 같은 HTML 요소의 연결지점이 된다.
  • 파싱 알고리즘

    • 일반적인 하향식, 상향식 파서로 파싱이 안됨. 그 이유는

      • 언어의 너그러운 속성
      • HTML 오류에 대한 브라우저의 관용
      • 변경에 의한 재파싱. document.write를 포함하고 있는 스크립트 태그는 토큰을 추가할 수 있기 때문에 실제로는 입력과정에서 파싱이 수정됨.
    • 파싱 알고리즘은 HTML 문서에 자세히 설명되어있다.

    • 알고리즘은 토큰화와 트리 구축으로 나누어져있음

    • 토큰화 : 어휘 분석으로서 입력값을 토큰으로 파싱함.

      • HTML 에서 토큰은 시작태그, 종료태그, 속성이름과 속성 값이다.
  • 토큰화 알고리즘

    • State Machine으로 볼 수 있음.
    • 각 상태는 하나 이상의 연속된 문자를 입력받아 이문자에 따라 다음 상태를 갱신함.
    • 결과는 현재의 토큰화 상태와 트리 구축 상태의 영향을 받음.
  • 트리구축 알고리즘

    • 트리 구축이 진행되는 동안 문서 최상단에서는 DOM 트리가 수정되고, 요소가 추가됨.
    • 토큰화에 의해 발행된 각 노드는 트리 생성자에 의해 처리됨.
    • 각 토큰을 위한 DOM 요소의 명세는 정의되어있다.
    • DOM 트리에 요소를 추가하는 것이 아니라면 열린 요소는 스택에 추가됨.
      • 부정확한 중첩과 종료되지 않은 태그를 교정함.
    • 알고리즘은 상태 기계라고 설명할 수 있고, 상태는 ‘삽입모드’라고 부른다.
  • 파싱이 끝난 이후의 동작

    • 브라우저는 문서와 상호작용할 수 있게 됨.
    • ‘지연’ 모드 스크립트를 파싱하기 시작함
    • 문서 상태는 ‘완료’가 되고, ‘로드’ 이벤트가 발생한다.
  • 브라우저의 오류처리

    • 브라우저는 HTML 의 모든 오류구문을 교정해준다.

    • 파서는 적어도 ㄷ음과 같은 오류를 처리해야함

      • 어떤 태그의 안쪽에 추가하려는 태그가 금지된 것일 때, 일단 허용된 태그를 먼저 닫고 금지된 태그는 외부에 추가한다.
      • 파서가 직접 요소를 추가해서는 안된다. 문서 제작자에 의해 뒤늦게 요소가 추가될 수 있고 생략 가능한 경우도 있다. HTML, HEAD, BODY, TBODY, TR, TD, LI 태그가 이런 경우에 해당한다.
      • 인라인 요소 안쪽에 블록 요소가 있는 경우 부모 블록 요소를 만날 때까지 모든 인라인 태그를 닫는다.
      • 이런 방법이 도움이 되지 않으면 태그를 추가하거나 무시할 수 있는 상태가 될 때까지 요소를 닫는다.
    • ex)

      • <br> 대신 </br>
      • 어긋난 표
      • 중첩된 폼요소
      • 태그 중첩이 너무 깊을 때
      • 잘못 닫힌 html 또는 body 태그
  • CSS 파싱

    • CSS는 문맥 자유 문법. 즉 위에서 설명한 파서로 파싱 가능하다.
    • 웹킷 CSS 파서 : 플렉스와 바이슨 파서 생성기 사용
    • 게코 : 하향식 파서 사용
  • 스크립트와 스타일 시트 진행 순서

    • 스크립트
      • 웹은 파싱과 실행이 동시에 수행되는 동기화 모델임.
      • 제작자는 파서가 script 태그를 만나면 즉시 파싱하고 실행하기를 기대한다.
      • 스크립트가 실행되는 동안 문서의 파싱은 중단됨
      • 제작자가 스크립트를 ‘지연(defer)’로 표시하면 문서파싱은 중단되지 않고 문서 파싱이 완료된 이후에 스크립트가 실행된다.
      • HTML5는 스크립트는 비동기로 처리하는 속성을 추가했기 때문에 별도의 맥락에 의해 파싱되고 실행됨.
    • 예측 파싱
      • 스크립트를 실행하는 동안 다른 스레드는 네트워크로부터 다른 자원을 찾아 내려받고 문서의 나머지 부분을 파싱한다.
      • 예측 파서는 DOM 트리를 수정하지 않고 메인파서의 일로 넘긴다.
    • 스타일 시트
      • 이론적으로 스타일 시트는 DOM 트리를 변경하지 않기 때문에 문서파싱을 기다리거나 중단할 이유가 없음
      • 그러나 스크립트가 문서를 파싱하는 동안 스타일 정보를 요청하는 경우라면 문제가 됨.
      • 파이어폭스는 로드 중이거나 파싱 중인 스타일 시트가 있는 경우 모든 스크립트의 실행을 중단함.
      • 웹킷은 로드되지 않은 스타일 시트 가운데 문제가 될만한 속성이 있을 때에만 스크립트를 중단함.

렌더 트리 구축

  • DOM 트리가 구축되는 동안 브라우저는 렌더 트리를 구축함.

    • 표시해야 할 순서와 문서의 시각적인 구성요소로써 올바른 순서로 내용을 그려낼 수 있도록 하기 위한 목적
    • 파이어폭스에선 “형상(Frame)”이라 부르고, 웹킷은 “렌더러” 또는 “렌더 객체”라고 부른다.
  • 렌더러

    • 렌더러는 자신과 자식 요소를 어떻게 배치하고 그려내야하는지 알고 있음.

    • 웹킷 렌더러 기본 객체 : RenderObject 클래스

      1
      +2
      +3
      +4
      +5
      +6
      +7
      +8
      +
      class RenderObject { virtual
      +    void layout(); virtual
      +    void paint(PaintInfo); virtual
      +    void rect repaintRect();
      +    Node * node; //the DOM node
      +    RenderStyle * style; // the computed style
      +    RenderLayer * containgLayer; //the containing z-index layer
      +}
      +
      • 각 렌더러는 CSS2 명세에 따라 노드의 CSS 박스에 부합하는 사각형을 표시함.
      • 렌더러는 너비, 높이와 같은 기하학적 정보를 포함함.
      • 박스유형은 노드와 관련된 display 스타일 속성의 영향을 받는다.
      • 요소 유형도 고려해야함.
        • ex) 폼 컨트롤과 표는 특별한 구조이다.
        • 요소가 특별한 렌더러를 만들어야 한다면 웹킷은 createRenderer 메서드를 무시하고 비기하학 정보를 포함하는 스타일 객체를 표시함.
  • Dom 트리와 렌더 트리의 관계

    • 렌더러는 DOM 요소에 부합하지만 1:1로 대응하지는 않는다.

      • 예를들어 head나, display:none 으로 할당된 요소는 트리에 나타나지 않는다.
      • visibility :hidden 은 나타난다.
    • 여러개의 시각 객체와 대응하는 DOM 요소도 있다.

      • ex) select 요소 : ‘표시영역, 드롭다운 목록, 버튼’을 위한 3개의 렌더러가 있음.
      • 한줄에 표시할 수 없는 문자가 여러 줄로 바뀔때 새 줄은 별도의 렌더러로 추가됨.
    • 어떤 렌더객체는 DOM 노드에 대응하지만 트리의 동일한 위치에 있지 않다.

      • float 처리된 요소 / position:absolute 처리된 요소 등은 흐름에서 벗어나 트리의 다른 곳에 배치된 상태로 그려짐.
      • 대신 자리표시자가 원래 있어야할 곳에 배치된다.
  • 트리를 구축하는 과정

    • 파이어폭스 : 프레젠테이션을 DOM 업데이트를 위한 리스너로 등록함
      • 프레젠테이션은 형상만들기를 FrameConstructor에 위함하고, FrameConstructor는 스타일을 결정하고 형상을 만든다.
    • 웹킷 : 스타일을 결정하고 렌더러를 만드는 과정을 ‘attachment’라고 부른다.
      • 모든 DOM 노드에는 attach 메서드가 있다.
      • attachment는 동기적인데, DOM트리에 노드를 추가하면 새 노드의 attach 메서드를 호출함.
    • html 태그와 body 태그를 처리함으로써 렌더트리루트를 구성한다.
    • 루트렌더 객체 : CSS명세에서 포함블록이라고 불림. 파이어폭스에서는 ViewPortFrame, 웹킷은 RenderView라고 부른다.
      • 문서가 가리키는 렌더 객체이다.
      • 트리의 나머지부분은 DOM 노드를 추가함으로써 구축된다.
  • 스타일계산

    • 렌더 트리를 구축하려면 각 렌더 객체의 시각적 속성에 대한 계산이 필요한데 이것은 각 요소의 스타일 속성을 계산함으로써 처리된다.

    • 스타일 계산에는 다음과 같은 어려움이 따름.

      • 스타일 데이터는 구성이 매우 광범위한데 수 많은 스타일 속성들을 수용하면서 메모리 문제를 야기할 수 있다.

      • 최적화되어 있지 않다면 각 요소에 할당된 규칙을 찾는 것은 성능 문제를 야기할 수 있다. 예를 들어 이런 복합 선택자가 있다.

        div div div div { … }

        규칙을 적용할 <div> 요소를 확인하려면 트리로부터 임의의 줄기를 선택하고 탐색하는 과정에서 규칙에 맞지 않는 줄기를 선택했다면 또 다른 줄기를 선택해야 한다.

      • 규칙을 적용하는 것은 계층 구조를 파악해야 하는 복잡한 다단계 규칙을 수반한다.

    • 이 문제를 어떻게 해결하는가? -> 아래 설명-

    • 웹킷 노드 : 스타일 정보 공유

      • 웹킷 노드는 스타일 객체(RenderStyle)를 참조하는데, 일정 조건 아래서 공유할 수 있게한다.
    • 파이어폭스 규칙 트리

      • 파이어폭스는 스타일 계산을 쉽게 처리해주는 규칙 트리와 스타일 문맥 트리라고 하는 두 개의 트리를 더 가지고 있다.

      • 스타일 문맥에는 최종값이 저장되어있다.

      • 값은 올바른 순서 안에서 부합하는 규칙을 적용하고, 논리로부터 구체적인 값으로 변환함으로써 계산된다.

      • 노드 사이에서 이 값을 공유함으로써 다시 계산하는 일을 방지한다.

      • 부합하는 모든 규칙은 트리에 저장하는데, 경로의 하위 노드가 높은 우선순위를 갖는다.

      • 규칙 저장은 느리게 처리된다. 트리는 처음부터 모든 노드를 계산하지 않지만 노드 스타일이 계산될 필요가 있을 때 계산된 경로를 트리에 추가한다.

      • 트리가 작업량을 줄이는 방법

        1. 구조체로 분리 :

          • 스타일 문맥을 구조체로 나눈다.

          • 상속할 데이터와 상속하지 않을 데이터를 나눔.

        2. 규칙 트리를 사용하여 스타일 문맥을 계산

          • 어떤 요쇼의 스타일 문맥을 계산할 때 가장 먼저 규칙트리의 경로를 계산하거나 또는 이미 존재하는 경로를 사용한다.
          • 그 다음 새로운 스타일 문맥으로 채우기 위해 경로 안에서 규칙을 적용한다.
          • 가장 높은 우선순위(최하위) 부터 시작하여 구조체가 가득찰 떄까지 트리의 상단으로 거슬러 올라간다.
          • 규칙 노드 안에서 구조체를 위한 특별한 선언이 없다면 상당한 최적화가 가능.
          • 구조체에서 어떤 선언도 발견할 수 없는 경우 구조체는 “상속(inherit)” 타입인데 문맥 트리에서 부모 구조체를 향하면서 구조체를 공유한다.
          • 같은 트리노드를 가리키는 형제 요소가 있는 경우 전체 스타일 문맥이 공유된다.
  • 쉬운 선택을 하기 위한 규칙 다루기

    • 인라인 스타일 속성과 HTML 시각적 속성은 자신이 스타일 속성을 가지고 있거나 HTML 소고성을 이용하여 연결할 수 있기 때문에 요소에 쉽게 연결된다.

      1
      +2
      +
      <p style="color:blue"></p>
      +<p bgcolor="blue"></p>
      +
    • 외부 스타일 시트에 선언하거나, style 요소안에서 선언하면 까다로울 수 있다.

      • 스타일 시트를 파싱한 후 규칙은 선택자에 따라 여러 해시맵 중 하나에 추가된다.
        • 선택자가 아이디인 경우 규칙은 아이디 맵에, 클래스인 경우 클래스 맵에 추가된다.
      • 이런 처리 작업을 통해, 필요할때 해당 맵을 찾아보아서 모든 선언을 찾아 볼 필요가 없다.
  • 다단계(cascade) 순서에 따라 규칙 적용하기

    • 어떤 규칙과도 일치하지 않는 일부 속성은 부모 요소의 스타일 객체로부터 상속 받는다. 그 외 다른 속성들은 기본 값으로 설정된다.
    • 문제는 하나 이상의 속성이 정의될 때 시작되고, 다단계 순서가 이 문제를 해결하게 된다.
    • 스타일 시트 다단계 순서 : 낮은 거에서 높은 순
      1. 브라우저 선언 (browser declarations)
      2. 사용자 일반 선언 (user normal declarations)
      3. 저작자 일반 선언 (author normal declarations)
      4. 저작자 중요 선언 (author important declarations)
      5. 사용자 중요 선언 (user important declarations)
    • 선택자 특정성
      • CSS2 명세
        • 선택자 없이 ‘style’ 속성이 선언된 것이면 1을 센다. 그렇지 않으면 0을 센다. (=a)
        • 선택자에 포함된 아이디 선택자 개수를 센다. (=b)
        • 선택자에 포함된 속성 선택자(클래스 선택자와 속성 선택자)와 가상 클래스 선택자의 숫자를 센다. (=c)
        • 선택자에 포함된 요소 선택자와 가상 요소 선택자의 숫자를 센다. (=d)
      • 네 개의 연결된 숫자 a-b-c-d 를 연결하면 특정성의 값이 된다.
      • 사용할 진법은 분류 중에 가장 높은 숫자에 의해서 정의된다.(17이면 17진법 사용)
    • 규칙 정렬
      • 맞는 규칙을 찾으면, 다단계 규칙에 따라 정렬된다.
      • 웹킷은 목록이 적으면 버블 정렬을 사용하고, 많을 때는 병합정렬을 사용한다.
      • 웹킷은 규칙에 “>” 연산자를 덮어쓰는 방식으로 정렬을 실행한다.
  • 점진적 처리

    • 웹킷은 @import를 포함한 최상위 수준의 스타일 시트가 로드되었는지 표시하기 위해 플래그를 사용한다.
    • DOM 노드와 시각정보를 연결하는 과정(attaching)에서 스타일이 완전히 로드되지 않았다면 문서에 자리 표시자를 사용하고 스타일 시트가 로드됐을 때 다시 계산한다.

배치(리플로)

  • 크기와 위치정보를 계산하는 것 (렌더러가 생성되어 트리에 추가될 때는 이러한 정보가 없다.)

  • HTML 은 흐름기반의 배치 모델을 사용함 == 단일 경로를 통해 크기와 위치 정보를 계산할 수 있다는 것을 의미.

    • 일반적으로 흐름 속에서 나중에 등장하는 요소는 앞서 등장한 요소의 위치와 크기에 영향을 미치지 않기 때문에 배치는 왼쪽에서 오른쪽으로, 또는 위에서 아래로 흐른다.
    • 단, 표는 크기와 위치를 계산하기 위해 하나 이상의 경로를 필요로 하기 때문에 예외가 된다.
  • 배치는 반복되며 HTML 문서의 <html> 요소에 해당하는 최상위 렌더러에서 시작된다.

    • 배치는 프레임 계층의 일부 또는 전부를 통해 반복되고, 각 렌더러에 필요한 크기와 위치정보를 계산한다.
    • 최상위 렌더러의 위치는 0,0 이고, 뷰포트만큼의 면적을 갖는다.
  • 모든 렌더러는 “배치” 또는 “리플로” 메서드를 갖는데, 각 렌더러는 배치해야할 자식의 배치 메서드를 불러온다.

  • 더티 비트 체제

    • 소소한 변경 때문에 전체를 다시 배치하지 않기위해 브라우저는 더티비트 체제를 사용한다.
    • 렌더러는 다시 배치할 필요가 있는 요소와 그 자식을 “더티”라고 표시함.
    • “더티”와 “자식이 더티” 라는 두가지 플래그가 있다.
  • 전역배치와 점증배치

    • 배치는 렌더러 트리 전체에서 일어날 수 있는데 이것을 “전역”배치라고 한다. 다음과 같은 상황에서 발생
      • 글꼴 크기 변경과 같이 모든 렌더러에 영향을 주는 전역 스타일 변경
      • 화면 크기 변경에 의한 결과
    • 점증 배치 : 렌더러가 더티일 때 비동기적으로 일어남.
  • 비동기 패치와 동기배치

    • 전역배치 : 동기적으로 실행.
    • 점증배치 : 비동기적으로 실행
      • 파이어폭스 : 리플로 명령을 쌓아놓고 스케줄러는 이 명령을 한꺼번에 실행함.
      • 웹킷 : 타이머가 존재하고, 트리를 탐색하여 더티 렌더러를 배치한다.
  • 최적화

    • 배치가 크기변경 또는 렌더러 위치변화 때문에 실행되는 경우, 렌더러는 크기는 다시 계사나하지 않고 캐시로부터 가져온다.
    • 입력필드에 틱스트를 입력하는 경우와 같이 변화범위가 한정적이어서 주변에 영향을 끼치지 않을 경우, 하위트리만 수정이 되고 최상위로부터 배치가 시작되지 않음
  • 배치과정

    1. 부모 렌더러가 자신의 너비를 결정.
    2. 부모가 자식을 검토.
      1. 자식 렌더러를 배치(자식의 x와 y를 설정)
      2. (부모와 자식이 더티하거나 전역 배치 상태이거나 또는 다른 이유로) 필요하다면 자식 배치를 호출하여 자식의 높이를 계산한다.
    3. 부모는 자식의 누적된 높이와 여백, 패딩을 사용하여 자신의 높이를 설정한다. 이 값은 부모 렌더러의 부모가 사용하게 된다. -> 즉 높이는 자식에서부터 계산이 된다.
    4. 더티 비트 플래그를 제거한다.
    • 파이어폭스는 상태객체(nsHTMLReflowState)를 배치하기 위한 매개변수로 사용하는데, 상태는 부모의 너비를 포함한다
    • 파이어폭스 배치늬 결과는 매트릭트 객체(nsHTMLReflowMatrics)인데, 높이가 계산된 렌더러를 포함한다.
  • 너비계산

    • 렌더러의 너비는 포함하는 블록의 너비, 렌더러의 너비와 여백, 테두리를 이용하여 계산된다.

    • 웹킷 계산

      1. 컨테이너의 너비는 컨테이너 availableWidth와 0 사이의 최대값이다. 이 경우 availableWidth는 다음과 같이 계산된 contentWidth이다.

        clientWidth() - paddingLeft() - paddingRight()

        clientWidth와 clientHeight는 객체의 테두리와 스크롤바를 제외한 내부 영역을 의미한다.

      2. 요소의 너비는 “width” 스타일 속성의 값이다. 이 컨테이너 너비의 백분률 값은 절대 값으로 변환될 것이다.

      3. 좌우측 테두리와 패딩 값이 추가된다.

    • 위는 미리 획득한 너비의 계산.

    • 최소 너비와 최대너비 계산

      • 미리 획득한 너비가 최대 너비보다 크면 최대너비가 사용된다.
      • 미리 획득한 너비가 최소 너비보다 작으면 최소너비가 사용된다.
    • 배치할 필요가 있지만 너비가 고정된 경우 값은 캐시에 저장된다.

  • 줄바꿈

    • 렌더러가 배치되는 동안 줄을 바꿀 필요가 있을 때 배치는 중단되고 줄 바꿀 필요가 있음을 부모에게 전달함.
    • 부모는 추가 렌더러를 생성하고 배치를 호출함.

그리기

  • 화면에 내용을 표시하기 위한 렌더 트리가 탐색되고 렌더러의 “paint” 메서드가 호출됨. 그리키는 UI 기반의 구성요소를 사용한다.
  • 전역과 점증
    • 배치와 마찬가지로 전역 또는 점증 방식으로 수행됨.
    • 점증 그리기 : 일부 렌더러는 전체 트리에 영향을 주지 않는 방식으로 변경됨.
      • 변경된 렌더러는 화면 위의 사각형을 무효화하는데 OS는 이것을 더티영역으로 보고 paint 이벤트를 발생시킨다.
      • OS는 몇 개의 영역을 하나로 합치는 방법으로 효과적으로 처리한다.
      • 크롬은 렌더러가 별도의 처리 과정이기 때문에 더 복잡하다. OS의 동작을 어느정도 모방하는 방식
      • 파이어폭스 프레젠테이션은 이런 이벤트를 리슨하고 렌더 최상위로 메시지를 전달한다.
        • 그러면 트리는 적절한 렌더러에 이를 때까지 탐색되고 스스로 다시 그러진다.
  • 그리기 순서
    • CSS2 명세에 정의되어있음.
      1. 배경 색
      2. 배경 이미지
      3. 테두리
      4. 자식
      5. 아웃라인
  • 파이어폭스 표시 목록
    • 파이어폭스는 렌더트리를 검토하고 그려진 사각형을 위한 표시목록을 구성한다.
    • 목록은 올바른 그리기 순서에 따라 사각형을 위한 적절한 렌더러를 포함함.
    • 이런 방법으로 트리는 여러번 리페인팅을 실행하는 대신 한번만 탐색한다.
    • 최적화 : 다른 불투명 요소 뒤에 완전히 가려진 요소는 추가하지 않는 방법으로 최적화를 진행
  • 웹킷 사각형 저장소
    • 기존의 사각형을 비트맵으로 저장하여 새로운 사각형과 비교하고 차이가 있는 부분만 다시 그린다.

동적변경

  • 브라우저는 변경에 대해 가능한 한 최소한의 동작으로 반응하려고 한다.
    • 요소의 색깔이 바뀌면 해당 요소의 리페인팅만 발생한다.
    • 요소의 위치가 바뀌면 요소와 자식, 형제의 리페인팅과 재배치가 발생.
    • DOM 노드를 추가하면 노드의 리페인팅과 재배치가 발생
    • html 요소의 글꼴 크기를 변경하는 것과 같은 큰 변경은 캐시를 무효화하고 트리 전체의 배치와 리페인팅 발생

렌더링 엔진의 스레드

  • 렌더링 엔진은 통신을 제외한 거의 모든 경우에 단일 스레드로 동작함.
  • 파이어폭스와 사파리의 경우 렌더링 엔진의 스레드는 브라우저의 주요 스레드에 해당함.
  • 크롬에서는 탭 프로세스의 주요 스레드임.
  • 통신은 몇개의 병렬 스레드에 의해 진행될 수 있는데, 병렬 연결의 수는 보통 2개에서 6개로 제한.
  • 이벤트순환
    • 브라우저의 주요 스레드는 이벤트 순환으로 처리과정을 유지하기 위해 무한 순환된다.
    • 배치와 그리기 같은 이벤트를 위해 대기하고 이벤트를 처리함.

CSS2 시각모델

  • 캔버스

    • CSS2 명세에선 “서식 구조가 표현되는 공간”이라고 설명됨. 브라우저가 내용을 그리는 공간이라는 뜻
    • 캔버스 공간 각각의 면적은 무한하지만 브라우저는 뷰포트의 크기를 기초로 초기너비를 결정함.
    • 캔버스는 기본적으로 투명하기 때문에 다른 캔버스와 겹치는 경우 비쳐보이고, 투명하지 않을 경우에는 브라우저에서 정의한 색이 지정됨.
  • CSS 박스 모델

    • CSS 박스모델은 문서 트리에 있는 요소를 위해 생성되고, 시각적 서식 모델에 따라 배치된 사각형 박스를 설명함.
    • 각 박스는 콘텐츠영역(문자, 이미지 등)과 패딩과 테두리, 여백이 있다.
    • 각 노드는 이런 박스를 0에서 n개 생성한다.
    • 모든 요소는 만들어질 박스의 유형을 결정하는 display 속성을 갖는데, 이 속성의 유형은 다음과 같다.
      • block - 블록 상자를 만든다.
      • inline - 하나 또는 그 이상의 인라인 상자를 만든다.
      • none - 박스를 만들지 않음.
    • 기본 값은 인라인이지만 브라우저의 스타일 시트는 다른 기본값을 설정함. 예를 들어 “div” 요소의 기본값은 block이다.
  • 위치 결정방법

    • 세가지

      1. Normal - 객체는 문서 안의 자리에 따라 위치가 결정된다. 이것은 렌더 트리에서 객체의 자리가 DOM 트리의 자리와 같고 박스 유형과 면적에 따라 배치됨을 의미한다.
      2. Float - 객체는 우선 일반적인 흐름에 따라 배치된 다음 왼쪽이나 오른쪽으로 흘러 이동한다.
      3. Absolute - 객체는 DOM 트리 자리와는 다른 렌더 트리에 놓인다.
    • position속성과 float 속성에 의해 결정된다.
    • 박스가 배치되는 방법은 다음과 같은 방법으로 결정된다.
      1. 박스 유형(display, inline …)
      2. 박스 크기(width, height …)
      3. 위치 결정 방법(position, float)
      4. 추가적인 정보 - 이미지 크기와 화면 크기 등
  • 박스 유형

    • 블록박스 : 브라우저 창에서 사각형 블록을 형성한다.
    • 인라인박스 : 블록이 되지 않고 블록 내부에 포함된다.
    • 블록은 다른 블록 아래 수직으로 배치되고, 인라인은 수평으로 배치된다.
    • 인라인 박스는 “라인박스” 안쪽에 놓인다. 라인은 적어도 가장 큰 박스만큼 크지만, baseline 정렬일 때 더 커질 수 있다. 이것은 요소의 하단이 다른 상자의 하단이 아닌 곳에 배치된 경우를 의미함.
    • 포함하는 너비가 충분하지 않으면 인라인은 몇 줄의 라인으로 배치되는데 이것은 보통 문단 안에서 발생한다.
  • 위치잡기

    • 상대적인 위치
    • 플로트 : 플로트 박스는 라인의 왼쪽 또는 오른쪽으로 이동함. 다른 박스는 이 주변을 흐른다.
    • absolute, fixed : 일반적인 흐름과 무관하게 결정됨.
  • 층 표현 : z-index 속성에 의해 명시됨.

    • 뒤쪽 요소가 먼저 그려지고 앞쪽 요소는 사용자에게 가까운 쪽으로 나중에 그려진다.

    • 가장 앞쪽에 위치한 요소는 겹치는 이전 요소를 가린다.

출처

https://d2.naver.com/helloworld/59361

This post is licensed under CC BY 4.0 by the author.

10875 뱀

14529 Where's Bessie

Comments powered by Disqus.

diff --git "a/posts/\354\204\240\354\226\270\355\230\225/index.html" "b/posts/\354\204\240\354\226\270\355\230\225/index.html" new file mode 100644 index 000000000..358aab117 --- /dev/null +++ "b/posts/\354\204\240\354\226\270\355\230\225/index.html" @@ -0,0 +1,115 @@ + 선언형프로그래밍 | 디피의 개발일지
Posts 선언형프로그래밍
Post
Cancel

선언형프로그래밍

선언형, 명령형 그리고 추상화

선언형이란?

명령형은 어떻게(How)에, 선언형은 무엇(What)에 집중한다.

선언형은 명령형 코드에서 ‘어떻게’를 감추고 ‘무엇을’만 노출하는 방식의 추상화이다. 일종의 리팩토링이다.

예시를 들면 다음과 같다.

1
+2
+3
+4
+5
+6
+7
+8
+
// 명령형 코드 : 배열에 있는 모든 숫자를 하나씩 제곱해서 result 배열에 넣는다.
+function double(arr) {
+  let results = [];
+  for (let i = 0; i < arr.length; i++) {
+    results.push(arr[i] * 2);
+  }
+  return results;
+}
+
1
+2
+3
+4
+
// 선언형 코드 : 모든 배열에서 숫자가 제곱된다.
+function double(arr) {
+  return arr.map((item) => item * 2);
+}
+

배열의 모든 요소를 돌며 하나씩 제곱하는 것이 아니라, 배열의 각 요소를 대상으로 제곱이 되라고 선언한 형태이다. How는 map 함수 안에 숨겨져있다.


함수로 묶으면 다 선언적인가?

선언형 프로그래밍의 대중적인 정의 몇가지

  • A high-level program that describes what a computation should perform.
  • Any programming language that lacks side effects (or more specifically, is referentially transparent)
  • A language with a clear correspondence to mathematical logic.

여기서 두 번째 불렛의 사이드 이펙트가 적고 순수하다라는 점이 중요하다.

1
+
moveToEmptyTable(myPosition, tables);
+

위 함수는 충분히 선언적이지 못하다.

  1. 먼저 순수하지 못하다. 빈자리를 얻어내는 계산함수와 , 빈자리에 배치하는 액션함수가 하나의 메소드로 묶여있다.
  2. 여러 번 호출하거나, 특수한 상황에서 호출했을 때 다른 결과물을 줄 수 있다. 따라서 재사용이 어렵다. 이는 계산과 액션이 묶여있기에 발생한다.
    • 빈자리에 이미 앉아있는 경우에 moveToEmptyTable()을 호출한다면 빈자리에 찾아갈지 아니면 오류가 날지 모른다.

따라서 언제 불러도 같은 결과가 나오도록, 또 순수해지도록 리팩트링하면 다음과 같이 두 함수로 나뉠 수 있다.

1
+2
+
const emptyTablePosition = getEmptyTablePosition(tables);
+move(me, emptyTablePosition);
+
  • 코드의 절차적인 순서와 상관없이 언제 어디서 불러도 동일한 결과값을 준다.(재사용성 up)
  • what이 함수명에 적절히 표현되어있다.
  • 세부 구현은 함수 내부에 추상화되었다.


선언적 프로그래밍의 또다른 특징 : 코드순서 노상관

라인 바이 라인의 코드순서가 중요하지 않아질수록 더 선언적이게 된다.

순서의존도가 없기 때문에 사이드이펙트도 줄어들고 이해하기도 더 쉬워진다.

예를들어 리액트 컴포넌트의 props은 순서에 상관없이 동일한 동작을 한다.

1
+2
+3
+4
+5
+
<Modal
+    title="뭐먹지"
+    onClick={() => alert("짬뽕")}
+    description="중국음식?"
+/>
+

여기에 title, onClick, description의 코드 순서는 중요하지 않다.


명령형 추상화, 선언형 추상화

다음은 동,읍,면 Input이 있고, 동을 입력하면 자동으로 읍으로, 읍을 입력하면 자동으로 면으로 넘어가는 코드이다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
// 명령형 코드 원본
+if (value.length < maxLength) {
+  return;
+}
+if (cursorPosition === "") {
+  읍input.focus();
+  읍input.selectionStart = 읍input.value.length;
+} else if (cursorPosition === "") {
+  면input.focus();
+  면input.selectionStart = 면input.value.length;
+}
+
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+
// 명령형 추상화
+const isInputFull = value.length === maxLength;
+if (!isInputFull) {
+  return;
+}
+if (cursorPosition === "") {
+  moveToInput("");
+} else if (cursorPosition === "") {
+  moveToInput("");
+}
+
1
+2
+3
+4
+
// 선언형 추상화
+<Input name="" onFull={() => moveFocusTo('')}/>
+<Input name="" onFull={() => moveFocusTo('')}/>
+<Input name=""/>
+


정리

명령형 코드를

  • What을 적절히 인터페이스에 노출하면서
  • How를 내부에 숨기고
  • 언제 어디서 불러도 동일한 결과가 나와서 재사용하기 편하게 추상화한다면

선언적 코드라고 볼 수 있다.


명령형 코드를 해석할 때는, 흐름을 따라가면서 읽어야 하는 시간축이 있다. 하지만 선언적코드는 흐름에 따라 읽지 않아도 되기에 읽기도, 디버깅하기도, 재사용하기도 좋다. 또한 내부를 몰라도 사용할 수 있다는 점에서 사용성이 좋다.

선언적 프로그래밍은 코드 자체에 어떤일이 벌어진지 설명하기 때문에, 좀 더 추론하기 좋다.

아래 예시를 통해 알아보자

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+
const loadAndMapMembers = compose(
+  combineWith(sessionStorage, "members"),
+  save(sessionStorage, "members"),
+  scopeMembers(window),
+  logMemberInfoToConsole("name.first"),
+  countMemberBy("location.state"),
+  prepStatesForMapping,
+  save(sessionStorage, "map"),
+  renderUSMap
+);
+
+getFakeMembers(100).then(loadAndMapMembers);
+

많은 함수가 결합되어있지만, 선언형으로 되어있기에 각 함수가 어떻게 구현되어있을 거라고 추론하기 쉬워진다.


출처

https://milooy.github.io/dev/220810-abstraction-and-declarative-programming/

This post is licensed under CC BY 4.0 by the author.

React-query 에서 cache를 사용하는 방법

바벨

Comments powered by Disqus.

diff --git "a/posts/\354\212\244\355\224\204\353\247\201-\352\263\204\354\270\265\352\265\254\354\241\260/index.html" "b/posts/\354\212\244\355\224\204\353\247\201-\352\263\204\354\270\265\352\265\254\354\241\260/index.html" new file mode 100644 index 000000000..ee4ec1f52 --- /dev/null +++ "b/posts/\354\212\244\355\224\204\353\247\201-\352\263\204\354\270\265\352\265\254\354\241\260/index.html" @@ -0,0 +1 @@ + 스프링 게층구조 | 디피의 개발일지
Posts 스프링 게층구조
Post
Cancel

스프링 게층구조

스프링 계층구조

img

Web 계층

  • 컨트롤러(@Controller)와 JSP/Freemarker 등의 뷰 템플릿 영역
  • 이외에도 필터(@Filter), 인터셉터, 컨트롤러 어드바이스(@Controller Advice) 등 외부 요청과 응답에 대한 전반적인 영역을 나타낸다.
  • 외부 요청과 응답에 대한 전반적인 영역. 어플리케이션의 진입점이기 때문에 다른 레이어에서 발생한 예외도 처리함. 인증을 관리하고 권한없는 사용자의 인가를 거부하는 역할도 함.
  • 데이터 처리 : DTO만 처리해야함.

Service 계층

  • @Service가 사용되는 서비스 영역. @Transactional이 사용되어야하는 영역이기도 함.
  • 일반적으로 Controller와 Dao 중간영역에서 사용됨.
  • 트랜잭션에 대한 경계역할. 어플리케이션과 인프라 서비스를 모두 포함하고 있음.
  • 데이터 처리 : DTO를 메소드의 매개변수로 사용. 도메인 모델 객체를 처리. 데이터 전송 객체만 웹 레이어로 반환할 수 있음.
  • 주의 사항 : 서비스는 다른 서비스를 조합하거나, DAO를 연결하는 역할을 수행. 서비스는 가볍게 둬야하며 비즈니스 로직을 구현하면 안됨. (비즈니스 로직은 도메인 영역에서 처리)

Repository 계층

  • 가장 낮은 계층으로 사용되는 Database와 같이 데이터 저장소에 접근하는 영역
  • Dao(Data Access Object) 영역이라고 부름
  • 데이터 처리 : 엔티티를 메소드 매개변수로 가져올 수 있고, 엔티티를 리턴함

DTOs

  • DTO는 계층간에 데이터 교환을 위한 객체이며, DTOs는 이들의 영역을 말함.
  • ex) 뷰 템플릿 엔진에서 사용될 객체나 Repository 계층에서 결과로 넘겨준 객체 등이 Dto이다.

Domain Model

  • 도메인이라 불리는 개발 대상을 모든 사람이 동일한 관점에서 이해할 수 있고, 공유할 수 있도록 단순화 시킨 것을 도메인 모델이라고 한다.
  • 비즈니스 로직을 처리함.
  • @Entity가 사용된 영역 역시 도메인 모델이다.
  • 무조건 DB 테이블과 관계가 있어야하는 것은 아니다.(VO처럼 값 객체들도 도메인 모델에 해당)
  • 예시 : 택시 앱에서, 배차/탑승/요금 등이 모두 도메인임.


계층간 흐름도

img


비즈니스 로직을 도메인에 넣는 이유

기존에는 서비스 계층에서 처리하였으나, 복잡한 서비스에서는 더 많은 모델을 읽어 서비스를 제공하기 때문에, 서비스 계층은 매우 복잡해짐.

반면 도메인에서 처리할 경우, 각자 본인의 취소 이벤트처리를 하며, 서비스 메소드는 트랜잭션과 도메인간의 순서만 보장한다.


출처

https://loosie.tistory.com/296

https://velog.io/@smallcherry/Spring-%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9B%B9-%EA%B3%84%EC%B8%B5-Spring-Web-Layer

This post is licensed under CC BY 4.0 by the author.

WAS vs 웹서버

프론트 아키텍쳐 흐름

Comments powered by Disqus.

diff --git "a/posts/\354\213\244\354\232\251\354\243\274\354\235\230\355\224\204\353\241\234\352\267\270\353\236\230\353\250\270-1\354\236\245-\354\213\244\354\232\251\354\243\274\354\235\230-\354\262\240\355\225\231/index.html" "b/posts/\354\213\244\354\232\251\354\243\274\354\235\230\355\224\204\353\241\234\352\267\270\353\236\230\353\250\270-1\354\236\245-\354\213\244\354\232\251\354\243\274\354\235\230-\354\262\240\355\225\231/index.html" new file mode 100644 index 000000000..1c23e2d93 --- /dev/null +++ "b/posts/\354\213\244\354\232\251\354\243\274\354\235\230\355\224\204\353\241\234\352\267\270\353\236\230\353\250\270-1\354\236\245-\354\213\244\354\232\251\354\243\274\354\235\230-\354\262\240\355\225\231/index.html" @@ -0,0 +1 @@ + 실용주의프로그래머 1장 실용주의 철학 | 디피의 개발일지
Posts 실용주의프로그래머 1장 실용주의 철학
Post
Cancel

실용주의프로그래머 1장 실용주의 철학

Topic 1 - 당신의 인생이다

변화를 피하지 말고 불만이 있는 것이 있으면 고치기 위해 노력하라

  • 업무환경, 적성이 안맞으면 바꾸되, 너무 오래 노력하지는 말기
  • 뒤쳐지는 기분이 들면 여가 시간에 공부하기

기회는 많고 적극적으로 행동해 그 기회를 잡아라

Topic 2 - 고양이가 내 소스 코드를 삼켰어요

실용주의 철학의 초석 중 하나는 자신과 자신의 행동에 대해 책임을 지는 것이다. 아무리 잘해도 발생하는 문제는 있고, 이를 정직하고 솔직하게 인정하고, 처리하려고 노력해야한다.

팀 내 신뢰 팀이 나를 믿고 의지할 수 있어야하고, 나도 팀을 의지할 수 있어야한다. 신뢰를 바탕에 둔 환경에서는 안전하게 아이디어를 교류할 수 있다.

책임지기 책임을 지지 않을 권리는 있지만, 지기로 결정했다면 결과를 감당해야한다.

어설픈 변명 말고 대안을 제시하라.

  • 나쁜 소식을 전하기 전에 시도해볼만한 것은 없는지.
  • 해결을 위해 필요한 것이 있고, 그 이유를 설명할 수 있을지
  • “잘 모르겠어요” 다음에는 “알아보겠습니다”

Topic 3 - 소프트웨어 엔트로피

소프트웨어의 무질서도가 증가하면 이를 ‘소프트웨어의 부패’ (기술 부채)라고 부른다.

깨진 창문을 내버려두지 마라

  • 나쁜 설계, 잘못된 코드 등이 모두 깨진 창문이다.
  • 고칠 시간이 없다면, 주석처리 / Not implemented 등의 메시지를 표시하여 상황을 관리하고 있음을 보여줘라

방치는 다른 어떤 요인보다 부패를 가속화시킨다. 만약 고칠 시간이 없다면 아예 버리거나 새롭게 대체하라

우선 망가트리지 말라 상황을 냉정하게 평가하여 어떤 위기가 왔다고 필요이상의 피해를 주지마라. (= 깨진 창문을 더 만들지 말라)

Topic 4 - 돌멩이 수프와 삶은 개구리

변화의 촉매가 되라

  • 어떤 시스템을 개발하려고할 때 시작 피로에 빠지기 쉽다. 이때는 개발에 필요한 자원을 얻기 위해 사람들을 설득하는 것은 쉽지 않다.
  • 하지만 작은 것을 잘 개발하여 사람들에게 보여주고, 사람들로 하여금 조금씩 추가해달라고 할때까지 기다려라
  • 계속되는 성공에 합류하기는 쉽고, 미래를 살짝이라도 보여주면 사람들은 도와주기 위해 모여들 것이다

큰 그림을 기억하라

  • 소프트웨어 참사는 대부분 작은 문제들이 쌓이다가 어느날 갑자기 폭주한다.
  • 큰 그림에 늘 주의를 기울여 참사가 발생하지 않도록 하자.
  • 당장의 일에만 정신을 쏟지말고 주변에서 벌어지는 일을 살펴보라

Topic 5 - 적당히 괜찮은 소프트웨어

너무 완벽한 소프트웨어를 만들려하지말고, 적당히 괜찮게 만드는 것이 중요하다. 하지만 이때 적당히 괜찮은은 기본적인 성능, 보안, 요구조건을 만족해야한다.

타협과정에 사용자를 참여시켜라 소프트웨어가 얼마나 좋아야하는지 사용자에게 물어라. 좋은 소프트웨어를 위해 요구조건을 무시하거나, 요구조건을 위해 기본적인 것을 빼는 것 모두 전문가답지 못하다.

품질을 요구사항으로 만들어라.

우리는 적당한 타협이 필요한 상황에 자주 처한다. 사용자는 완벽한 내일의 소프트웨어보다 적당한 오늘의 소프트웨어를 원한다. 사용자에게 직접 만질 수 있는 기회를 주면 피드백을 통해 더 나은 해결책에 도달할 수 있다.

멈춰야할 때를 알라 완벽하게 훌륭한 프로그램을 만드느라 망치지마라.

‘기능 블로트’ 소프트웨어가 실제로 사용되는 기능에 비패 훨씬 더 많은 기능을 가지고 있고, 그만큼 버그나 보안 취약점이 생길 가능성이 높은 것을 말함

Topic 6 - 지식 포트폴리오

새로운 것을 배우는 능력은 가장 중요한 전략 자산이다. 그런데 배우는 방법은 어떻게 배워야하고, 무엇을 어떻게 배워야할까?

지식 포트폴리오

  1. 주기적인 투자
    • 소량으로라도 주기적으로 지식 포트폴리오에 투자해야한다.
    • 습관 자체가 중요하며, 방해받지 않을 수 있는 시간과 장소를 정기적으로 이용할 계획을 마련하라
  2. 다각화
    • 기본적으로 현재 작업에 사용하는 기술에 관해서는 속속들이 알아야한다.
    • 추가적으로 더 많은 기술에 익숙하다면 변화에 더 잘 적응할 수 있다.
    • 기술 외의 분야도 잊지말라
  3. . 리스크 관리
    • 위험하지만 잠재적으로 보상이 높은 것에서 리스크가 낮고 보상도 낮은 것 모두 적절히 담아라
  4. 싸게 사서 비싸게 팔기
    • 새롭게 떠오르는 기술이 인기를 끌기 전에 미리 알고 학습하면 보상도 크다.
  5. 검토 및 재조정
    • 소프트웨어 업계는 매우 동적이기에 주기적으로 어떤 걸 배워야할지 검토하여 재조정하라

목표

  1. 매년 새로운 언어를 최소 하나는 배워라
  2. 기술 서적을 한달에 한 권씩 읽어라
    • 현재 프로젝트와 관련이 있는 흥미로운 주제의 기술 서적을 읽어라
    • 현재 사용하는 기술을 일단 완전히 익혔다면, 가지를 쳐서 다른 분야까지 공부 범위를 넓혀라
  3. 기술 서적이 아닌 책도 읽어라
  4. 수업을 들어라
  5. 지역 사용자 단체나 모임에 참여하라
    • 회사 밖에서 사람들이 어떤 일을 하는지 알아보라
  6. 다른 환경에서 실험해보라
    • 윈도우에서만 일했다면 리눅스도 사용해봐라.
    • IDE에서만 작업했다면 makefile/텍스트 에디터로 작업해봐라
  7. 요즘 흐름을 놓치지 마라
    • 현재 사용하는 것과는 다른 기술을 다루는 뉴스와 게시물을 읽어라.

한 기술에 익숙해지면 다음으로 나아가라. 직접 사용할일은 없어도, 학습과정에서 사고가 확장될 것이다.

학습의 기회

  • 무엇인가 모르는게 생기면 답을 찾도록 노력하라
  • 비는 시간을 위한 읽을거리를 준비하라

비판적 사고

읽고 듣는 것을 비판적으로 분석하라

  • ‘왜’냐고 다섯번 묻기
  • 누구에게 이익이 되나?
  • 어떤 맥락인가?
  • 언제 혹은 어디서 효과가 있을까?
  • 왜 이것이 문제인가?

Topic 7 - 소통하라!

청중을 알라

  • 청중에 따라 포인트를 짚을 부분을 다르게 설정하라.
  • 피드백을 모아라
    • 질문을 기다리지말고 먼저 물어보라

말하고 싶은 게 무언지 알라 무엇을 말할지 미리 계획하고, 개요를 작성하라. 그리고 자문하라.

때를 골라라 무언가를 말할때 그 때가 적절한지 생각해봐라

스타일을 골라라 청중에 어울리도록 전달하는 스타일을 조정하라

  • 청중이 사실만 전달하는 브리핑을 원하는가? 한담을 원하는가?
  • 청중이 전문가인가?

하지만 조정할 수 없는 내용이라면, 사실이 그렇다고 전달하자

멋져보이게 하라 내용만 집중하기보단 멋져보이게 얘기하는 것도 중요하다

청중을 참여시켜라 가능하면 독자를 문서 초안에 참여하도록하여 피드백을 받아라

경청하라 회의를 대화로 바꾸면 생각을 더 효과적으로 전달할 수 있다

응답하라 “다음에 답해 드리겠습니다”라고 하더라도 사람들에게 응답해주면 더 긍정적으로 봐준다.

문서화 문서화는 꼭 필요한 작업이다.

문서를 애초부터 포함하고, 나중에 집어넣으려고 하지 말라.

모듈과 외부로 노출하는 함수에는 주석을 다는 것을 추천한다. But 모든 함수나 자료구조에 주석을 남기는 것은 유지보수를 어렵게 만든다. 주석을 달 때는 코드의 용도와 목적을 논해야한다. 어떻게 동작하는지는 코드가 이미 보여주고 있기 때문이다.

This post is licensed under CC BY 4.0 by the author.

next.js 최적화과정

실용주의프로그래머 2장 실용주의 접근법

Comments powered by Disqus.

diff --git "a/posts/\354\213\244\354\232\251\354\243\274\354\235\230\355\224\204\353\241\234\352\267\270\353\236\230\353\250\270-2\354\236\245-\354\213\244\354\232\251\354\243\274\354\235\230-\354\240\221\352\267\274\353\262\225/index.html" "b/posts/\354\213\244\354\232\251\354\243\274\354\235\230\355\224\204\353\241\234\352\267\270\353\236\230\353\250\270-2\354\236\245-\354\213\244\354\232\251\354\243\274\354\235\230-\354\240\221\352\267\274\353\262\225/index.html" new file mode 100644 index 000000000..c70397f1f --- /dev/null +++ "b/posts/\354\213\244\354\232\251\354\243\274\354\235\230\355\224\204\353\241\234\352\267\270\353\236\230\353\250\270-2\354\236\245-\354\213\244\354\232\251\354\243\274\354\235\230-\354\240\221\352\267\274\353\262\225/index.html" @@ -0,0 +1,8 @@ + 실용주의프로그래머 2장 실용주의 접근법 | 디피의 개발일지
Posts 실용주의프로그래머 2장 실용주의 접근법
Post
Cancel

실용주의프로그래머 2장 실용주의 접근법

Topic 8 - 좋은 설계의 핵심

좋은 설계는 나쁜 설계보다 바꾸기 쉽다.

ETC(Easy To Change)는 규칙이 아니라 가치

가치란 결정을 내릴때 도움을 주는 것이다. ETC는 가치로서 내제화되어야하며, 의식적으로 방금 한 일이 ETC 한지 항상 물어봐야한다. 일반적으로 상식선에서 추측이 가능하다. 하지만 결정하기 어려울때가 있는데, 이럴땐 다음 두가지 방법이 있다.

  • 코드가 교체되기 쉬워야한다. (결합도를 낮추고 응집도를 높여라)
  • 직관적으로 선택하고, 그 이유에 대해 기록해둬라. 그리고 추후 변경이 실제로 발생했을때 다시보며 피드백하라.

Topic 9 - DRY : 중복의 해악

소프트웨어를 신뢰성 높게 개발하는 유일한 길, 유지보수를 쉽게 만드는 유일한 길은 DRY 원칙을 따르는 것이다.

DRY : 반복하지 말라

DRY를 따르지 않으면 똑같은 것이 두 군데 이상 표현될 것이다. 그러면 하나를 바꾸면 나머지를 바꿔야하는데, 언제 잊어버릴지 모른다.

1) DRY는 코드 밖에서도

DRY는 똑같은 개념을 다른 곳에서 표현하면 안된다는 것이다. 코드의 어떤 부분을 바꿔야할때 문서도 바꿔야하는가? 그럼 DRY를 위반한 것이다.

2) 모든 코드 중복이 지식의 중복은 아니다

요구조건) 연령과 주문 수량은 모두 숫자이고, 0보다 커야한다

def validate_age(value):
+	validate_type(value, :integer)
+	validate_min_integer(value, 1)
+
+def validate_quantity(value):
+	validate_type(value, :integer)
+	validate_min_integer(value, 1)
+

이때 두 함수가 표현하는 지식은 다르다. 다만 우연히 규칙이 같은 것일 뿐이다. 이건 우연이지 중복이 아니다.

3) 문서화 중복

과도하게 많은 주석은 함수의 의도를 두번 설명하게 한다. 이는 추후 함수를 변경해야할때 주석도 변경해야한다. 따라서 함수명, 변수명 등으로 코드로서 의도를 표현하는 것이 제일 좋다.

4) 데이터의 DRY 위반

자료구조 내에서 중복된 의미의 데이터가 포함될 수 있다.

  • ex) line 이라는 클래스에 start, end, length 변수가 있는 경우 -> length를 계산하는 메서드를 만들어라

하지만 계산로직이 복잡한 경우 캐싱을 위해 포함할 수도 있는데, 중요한 것은 자료구조 외부로 드러내지 않는 것이다.

  • ex) line 클래스 생성 시 start, end만 받고, length는 자동으로 계산되게

5) 표현상의 중복

다른 라이브러리나 API와 연결할때 해당 API에 표현된 지식들(스키마, 에러코드 등)을 우리 코드도 알아야한다. 이러한 중복은 다음과 같이 완화될 수 있다.

  • 내부 API에서 생기는 중복
    • 언어나 기술에 중립적인 형식으로 내부 API를 정의할 수 있는 도구를 찾아라 (ex : swagger 등)
    • 모든 API 정의를 하나의 중앙 저장소에 넣어두고 여러 팀이 공유할 수 있으면 좋다.
  • 외부 API에서 생기는 중복
    • 외부 API는 엄밀한 문서화가 되어있는 경우가 많다.
  • 데이터 저장소와의 중복
    • 영속성 프레임워크 등을 통해 데이터 스키마로부터 객체를 바로 생성해낼 수 있도록 한다.
    • 또는 JS 객체처럼 키-값 데이터 구조에 밀어넣는 것도 좋다. (이때는 간단한 데이터 검증 도구는 필요하다)

6) 개발자 간의 중복

같은 기능을 여러 개발자가 각자 개발할 수도 있다. 이러한 것을 발견하려면 소통과 코드리뷰가 중요하다.

재사용하기 쉽게 만들어라

기존의 것을 찾아내고, 재사용하기 쉬운 환경을 조성해야한다. 재사용이 어려우면 중복의 위험을 감수해야한다.


Topic 10 - 직교성

컴퓨터 과학에서 직교성은 결합도 줄이기를 의미한다. 하나가 바뀌어도 나머지에 어떤 영향도 주지 않으면 서로 직교한다고 할 수 있다. 잘 설계가 되어있다면 데이터베이스 코드가 바뀌어도 사용자 인터페이스는 바뀌지 않을 것이다.

직교성의 장점

관련 없는 것들 간에 서로 영향이 없도록 하라

컴포넌트들이 각기 격리되어있으면 어느 하나를 바꿀 때 나머지를 걱정하지 않아도 된다. 직교성은 다음 두가지 큰 장점이 있다.

  • 생산성 향상
    • 변화를 국소화해서 개발 시간과 테스트 시간을 줄일 수 있다.
    • 직교성은 재사용하기도 쉽다
    • 직교적인 컴포넌트들을 결합하면 생산성 향상을 얻을 수 있다. 하나의 컴포넌트로 다양한 일을 할 수 있기 때문이다.
  • 리스크 감소
    • 직교성은 감염된 코드를 격리시켜준다.
    • 변경이 발생해도 시스템이 잘 깨지지 않는다.
    • 각각의 컴포넌트들을 테스트하기가 쉬워진다
    • 특정 제품, 플랫폼에 덜 종속적이 된다.

설계

설계가 직교적인지 확인하는 쉬운 방법은 ‘특정 기능에 대한 요구사항이 대폭 변경되었을때 몇개의 모듈이 영향을 받는가?’를 자문하는 것이다. 정답은 하나여야한다.

또한 현실 세계의 변화와 설계 사이의 결합도를 얼마나 줄였는지도 확인해야한다. 자신의 힘으로 제어할 수 없는 속성에 의존하면 안된다.

  • ex) 전화번호를 고객 식별자로 사용할 경우 -> 법이 변경되면 식별자를 모두 바꿔야한다.

툴킷과 라이브러리

같은 팀이 작성한 것이라도 라이브러리의 도입이 코드에 수용해서는 안될 변화를 가져오고, 직교성을 해친다면 도입하면 안된다.

EJB의 데코레이터 패턴이 좋은 라이브러리 예시이다.

코딩

코딩을 할 때 직교성을 유지할 수 있는 몇가지 기법이 있다.

  • 코드의 결합도를 줄여라
    • 불필요한 것은 다른 모듈에 보여주지말고, 다른 모듈의 구현에 의존하지 않는 코드를 작성하라
    • 데메테르 법칙을 따르려 노력해보자
    • 객체의 상태를 바꿔야하면, 객체가 직접 상태를 바꾸도록 해보자. (상태를 변경하는 메서드를 만들라는 뜻인듯…?)
  • 전역 데이터를 피하라
    • 전역 데이터를 읽기 전용으로 사용한다해도 전역 데이터가 변경되면 사용하는 모든 모듈에 영향이 간다
    • 필요한 컨텍스트를 모듈에 전달하는 식으로 해야 유지보수가 쉽다.
    • 싱글턴 객체를 일종의 전역 데이터로 남용하여 불필요한 결합을 만들 수 있다.
  • 유사한 함수를 피하라
    • 유사해보이는 함수를 여러개 구현해야할 수 있다. (ex : 시작과 끝은 같지만 중간이 다른 함수)
    • 이러한 함수는 전략 패턴을 사용하여 더 낫게 구현할 수 없을지 확인하라

기회가 있을 때마다 리팩터링하여 직교성을 개선하기 위해 노력하라.

테스트

직교적으로 설계된 시스템은 테스트하기가 더 쉽다. 통합 테스트보다 단위 테스트가 훨씬 쉽기 때문이다.

단위테스트를 작성하는 행위 자체가 직교성을 테스트할 수 있는 기회이다. 단위 테스트를 작성할 때 많은 모듈을 불러와야하면 결합도가 높다는 뜻이다.

버그 수정을 할 때에도, 하나의 모듈만 고치면 되는지 전체를 다 고쳐야하는지 점검해보아라.

더 알아보기

함수형 프로그래밍에서도 결합이 발생할 수 있는데, 한 함수의 결과를 다른 함수의 입력으로 사용하는 경우이다.

  • 이때는 함수 결과를 일반적인 포맷으로 하면 완화할 수 있을거 같음
  • 타입스크립트 사용도 도움이 된다고 함

Topic 11 - 가역성

프로젝트에서 정답은 하나가 아니다. 중요한 결정이 추후 변경될 수 있고, 큰 비용을 치뤄야하는 경우가 발생한다.

가역성

이 책에서 추천하는 방법들 (DRY 원칙, 결합도 줄이기 등)을 따른다면 중요하면서도 되돌릴 수 없는 결정의 수를 가능한한 줄일 수 있다.

최종 결정이란 없다

유연한 아키텍처

코드 뿐만이 아니라 아키텍처, 배포, 외부 제품과의 통합 영역도 유연하게 유지하여야한다. 아키텍처는 항상 변화하며 할 수 있는 것은 바꾸기 쉽게 만드는 것 뿐이다. 외부의 API를 추상화 계층 뒤로 숨기고, 코드를 여러 컴포넌트로 쪼개라.

유행을 좇지 마라.

어떤 미래가 펼쳐질지는 알 수 없으며, 코드 스스로 유연하게 대처 가능하도록 해야한다.


Topic 12 - 예광탄

프로젝트를 진행할때 마치 예광탄처럼 즉각적인 피드백을 얻는 것은 매우 중요하다.

프로젝트를 진행할때 우리는 미지의 것을 마주한다. 애매모호한 요구사항, 처음보는 라이브러리, 기술 등. 이럴때 전형적인 반응은 시스템을 극도로 명세화하는 것이다. 환경을 제약하고, 요구사항을 일일히 항목으로 만들어서 명세서를 만든 후 맞기를 기도한다.

하지만 실용주의 프로그래머는 소프트웨어 판 예광탄을 선호한다.

어둠 속에서 빛을 내는 코드

예광탄이 효과적인 이유는 일반 탄환과 동일한 환경 및 제약 조건에서 발사되기 때문이다. 코딩에서 동일한 효과를 얻으려면 우리를 요구사항으로부터 최종 시스템의 일부 측면까지 빨리, 눈에 보이게 반복적으로 도달하게 해줄 무언가를 찾아야한다.

시스템을 정의하는 중요한 요구사항을 찾아라. 의문이 드는 부분이나 가장 위험이 커 보이는 곳을 찾아라. 이런 부분의 코드를 가장 먼저 작성하도록 개발 우선순위를 정하라.

목표물을 찾기 위해 예광탄을 써라.

예시들

  • 프로젝트를 세팅할때 hello world 를 작성한 후 컴파일, 실행 해보는 것
  • 프로젝트 전체 아키텍처를 거치는 간단한 로직을 먼저 구현해보는 것

예광탄 코드는 여러 장점이 있다.

  • 사용자가 뭔가 작동하는 것을 일찍부터 보게 된다.
    • 사용자는 자신의 시스템에 진전이 있음을 보게 되어 기쁠 것이고, 점점 직접 기여하기 시작할 것이다.
    • 각 반복 주기마다 얼마나 목표에 가까워졌는지도 알려줄 것이다.
  • 개발자가 들어가서 일할 수 있는 구조를 얻는다
    • 무에서 유를 만드는 것은 어렵다. 따라서 일단 애플리케이션의 모든 요소간 상호 작용을 만들면, 이후 추가하는 것은 쉽고 일관성도 촉진된다.
  • 통합 작업을 수행할 기반이 생긴다.
    • 한번 시스템의 모든 요소를 연결하고 나면 새로 작성한 코드를 시스템에 붙이기가 쉬워진다.
  • 보여줄 것이 생긴다.
  • 진행상황에 대해 더 정확하게 감을 잡을 수 있다.

예광탄 코드 대 프로토타이핑

프토로타입은 최종 시스템의 어떤 특정한 측면을 탐사해보는 것이 목표이다. 따라서 프로토타입 방식을 따른다면 실험 이후 모두 버려야한다. 그리고 프로토타입을 통해 얻은 교훈으로 코드를 새로 작성한다.

프로토타입은 일종의 예시를 사용자에게 제시하고, 이후 최종 목표환경을 위한 코드로 다시 작성한다.

예광탄 코드는 애플리케이션이 전체적으로 어떻게 연결되는지 알려주고, 사용자에게는 애플리케이션의 요소들이 어떻게 상호작용하는지 보여주고 개발자에게는 아키텍쳐의 골격을 제시한다.

정리하면 프로토타입은 나중에 버리는 코드를 만들고, 예광탄 코드는 기능은 별로 없지만 완결된 코드이며 최종 시스템 골격 중 일부가 된다. 프로토타입은 예광탄을 발사하기 전에 먼저 수행하는 정찰 같은 것이다.


Topic 13 - 프로토타입과 포스트잇

프로토타입은 반드시 코드로 만들 필요는 없다. 그림이나 인터페이스 빌더 등을 통해 만들어 볼 수 있다.

프로토타입은 제한된 몇가지 질문에 답하기 위한 것이기에 세부 사항을 무시하고 실제 제품보다 매우 적은 비용으로 만들 수 있다. 만약 세부 사항을 포기하지 못하는 상황이라면 프로토타입을 만들고 있는 것이 맞는지 자문하라. 이럴때는 예광탄 코드가 적합할 수 있다.

프로토타입 대상

프로토타입으로 조사할 대상은 위험을 수반하는 모든 것이다. 이전에 해본 적 없는 것, 최종 시스템에 매우 중요한 것, 의심이 가는 것 등이 모두 대상이다.

  • 아키텍처
  • 기존 시스템에 추가할 새로운 기능
  • 외부 데이터의 구조 혹은 내용
  • 외부에서 가져온 도구나 컴포넌트
  • 성능 문제
  • 사용자 인터페이스 설계

프로토타입의 가치는 프로토타입 자체가 아니라 이를 통해 배우는 교훈에 있다.

프로토타이핑으로 학습하라

프로토타입을 어떻게 사용할 것인가?

프로토타입을 만들 때 무시해도 좋은 세부사항은 무엇일까?

  • 정확성 : 적절히 가짜 데이터 사용 가능
  • 완전성 : 제한된 방식으로만 동작해도 됨
  • 안정성 : 오류 검사를 무시해도 됨
  • 스타일 : 주석이나 문서가 많지 않아야함

Topic 14 - 도메인 언어

프로그래밍 언어로 사고방식이 제한될 수 있다. 따라서 도메인의 어휘를 사용하여 프로그래밍 하는 방식으로 좀 더 그 도메인에 가깝게 설계할 수 있다

문제 도메인에 가깝게 프로그래밍하라.

예시 :

  • 내부 도메인 언어 : RSpec, 피닉스라우터
    • 컴파일하고 실행할 수 있는 코드로, 실제 코드 안에 들어감
    • 호스트언어의 기능을 쓸 수 있다는 장점이 있지만, 호스트 언어의 문법과 의미론을 따라야한다
  • 외부 도메인 언어 : 큐컴버, 앤서블
    • 다른 언어나 데이터 구조로 변환하여 실행함
    • 호스트 언어에따른 제약은 없지만, 파서를 만들기에 리소스가 소비된다.

하지만 우리가 도메인 언어로 코드를 짠다해도 기획자는 읽지 않는다. 스스로 무엇을 원하는지 정확히 모르기 때문이다. 이때는 실제로 동작하는 것을 보여주면 스스로 원하는 것을 말할 것이다.

도메인언어를 사용하며 절약되는 시간보다 더 많은 시간을 쏟지마라. 기본적으로는 통용되는 yaml, json, csv 같은 외부 언어를 사용하라. 다만 외부언어 도입은 사용자가 직접 도메인 언어로 코드를 작성하는 경우에만 추천한다.


Topic 15 - 추정

얼마나 정확해야 충분히 정확한가?

모든 답은 추정치이다. 따라서 질문자의 원하는 것이 정확한 답을 요구하는지 큰 그림을 요구하는지 먼저 파악해야한다.

사용하는 단위에 따라서 답의 정확도 해석이 달라질 수 있다. 예를들어 130일이 걸린다고 하면 정확히 130일이라는 느낌이 들지만, 대략 6달이라고하면 5~6개월로 해석된다. 아래 단위를 추천한다. (ex : 125일이 걸릴 거 같으면 대략 6달이라고 말하자)

  • 1 ~ 15일 -> 일
  • 3~6주 -> 주
  • 8 ~ 20주 -> 달
  • 20주 이상 -> 추정치를 말하기 전에 다시 한번 생각해보자

추정치는 어디에서 나오는가?

모든 추정치는 문제의 모델에 기반한다. 그런데 모델을 작성하기 전에 그 일을 해본 사람에게 물어봐라. 다른 사람의 경험을 바탕으로 성공적인 추정치를 낼 수 있다.

무엇을 묻고있는지 이해하라

어떤 추정을 하건 상대방이 무엇을 묻고 있는지 먼저 이해해야한다. 도메인에 존재하는 조건에 대해서도 알아야한다. 상대방이 말해줄 때도 있지만 말하지 않더라도 미리 조건을 생각해보는 습관이 있어야한다.

시스템의 모델을 만들어라

의뢰인의 요청을 이해한 후에는 간단하게 모델을 만들어보라. 모델을 만듦으로써 표현에 드러나지 않은 패턴이나 프로세스를 발견할 수 있고, 초기 질문과는 다른 제안을 할 수도 있다.

모델이 정교해질수록 추정은 정확해지지만, 정확히 비례하진 않는다. 따라서 적절한 수준을 골라야한다.

모델을 컴포넌트로 나눠라

다 만든 모델을 컴포넌트로 분해하여 서로 어떻게 상호작용하는지 수식으로 기술해야한다. 각 노드 별로 전체 시스템에 얼마나 기여하는지 매개변수를 찾을 수 있을 것이다.

각 매개 변수에 값을 할당하라

가장 큰 영향을 미치는 매개변수를 찾아서 이 매개변수의 값을 최대한 정확하게 산출해내라. ex) 대기열시스템에서는 실제 시간당 요청수를 측정하거나, 비슷한 시스템을 찾아봐라

답을 계산하라

주요 매개변수들의 값을 변경시켜가며 여러 번 계산해보고 어느 것이 가장 잘 들어맞는지 찾아내라. 이때 너무 정확한 값이 나오면 아마 잘못계산한 것이다.

여러분의 추정 실력을 기록하라

계산한 추정치를 기록하고 나중에 얼마나 잘 들어맞는지 평가해봐라.

프로젝트 일정 추정하기

PERT 기법

아무 문제 없다면 10시간, 현실적으로는 18시간, 날씨가 나쁘면 30시간 정도 걸릴거 같습니다.

낙관적 추정치와 가장 가능성 높은 추정치, 비관적 추정치를 갖는다. 과업은 의존성에 따라 네트워크 형태로 배열한 후, 간단한 통계 기법으로 전체 프로젝트의 예상 최소 및 최대 소요 시간을 계산한다.

단순히 값을 부풀리는 것보다, 범위로 추정하는 것이 오류를 피할 수 있는 좋은 방법이다. PERT 속의 통계가 범위로 표현한 불확실성을 분산시켜주고, 전체 프로젝트에 대하여 더 나은 추정치를 얻을 수 있다.

코끼리 먹기

기능을 매우 작은 단위로 나누어 다음과 같은 방식으로 개발하면 추정치는 점점 더 정확해질 것이다.

  • 요구사항 확인하기
  • 위험 분석 후 위험도가 높은 부분을 우선 하기
  • 설계, 구현, 통합
  • 사용자와 함께 검증하기

한 iteration이 끝났을때 리뷰를 거쳐 총 몇번의 iteration이 필요할 지, 각 iteration 마다 뭘할지 추측할 수 있을 것이다.

이 방법은 경영진에게 인기는 없지만 더 정확한 일정을 전달할 수 있다.

This post is licensed under CC BY 4.0 by the author.

실용주의프로그래머 1장 실용주의 철학

실용주의프로그래머 3장 기본도구

Comments powered by Disqus.

diff --git "a/posts/\354\213\244\354\232\251\354\243\274\354\235\230\355\224\204\353\241\234\352\267\270\353\236\230\353\250\270-3\354\236\245-\352\270\260\353\263\270\353\217\204\352\265\254/index.html" "b/posts/\354\213\244\354\232\251\354\243\274\354\235\230\355\224\204\353\241\234\352\267\270\353\236\230\353\250\270-3\354\236\245-\352\270\260\353\263\270\353\217\204\352\265\254/index.html" new file mode 100644 index 000000000..c4ec3c406 --- /dev/null +++ "b/posts/\354\213\244\354\232\251\354\243\274\354\235\230\355\224\204\353\241\234\352\267\270\353\236\230\353\250\270-3\354\236\245-\352\270\260\353\263\270\353\217\204\352\265\254/index.html" @@ -0,0 +1,9 @@ + 실용주의프로그래머 3장 기본도구 | 디피의 개발일지
Posts 실용주의프로그래머 3장 기본도구
Post
Cancel

실용주의프로그래머 3장 기본도구

Topic 16 - 일반 텍스트의 힘

일반 텍스트란?

인쇄 가능한 문자로 이루어지고, 정보를 전달하기에 적합한 형식을 갖추어야한다. 또한 사람이 이해할 수 있어야한다.

  • 일반 텍스트
    1
    +2
    +
    우유
    +커피
    +
  • 일반 텍스트가 아님
    1
    +2
    +
    hlj;uijn bfjxrrctvh jkni’pio6p7gu;vh bjxrdi5rgvhj
    +Field19=467abe
    +

형식은 HTML, JSON, YAML 등 다양하다.

일반 텍스트는 데이터를 그 자체로만 이해할 수 있다. 이진포맷은 데이터를 읽는데 필요한 문맥과 데이터가 분리되어있어, 데이터를 읽는 문맥(애플리케이션 등)이 없으면 데이터는 무의미하다.

텍스트의 힘

  • 지원 중단에 대한 보험
    • 일반 텍스트는 이진형식과 달리 그 데이터를 읽는 문맥(시스템)이 없어져도 파싱이 가능하다.
  • 기존 도구의 활용
    • 새로운 도구가 나오더라도 텍스트형식은 항상 지원한다
  • 더 쉬운 테스트
    • 시스템 테스트에 사용할 합성 데이터를 일반 텍스트로 표현하면 특별한 도구 없이도 테스트 데이터를 편집할 수 있다.

최소 공통분모

데이터를 서로 교환하는 시스템 간에 일반 텍스트는 하나의 표준이 된다.


Topic 17 - 셸 가지고 놀기

셸 프롬프트를 통해 모든 도구를 불러다 쓸 수 있다. 도구를 조합하여 자동화 시스템을 만들 수도 있으며, 복잡한 명령어 수행도 가능하다. GUI 환경에서는 제공된 기능만 사용가능하지만, 셸에서는 원하는 기능을 만들 수 있다.

명령어 셸의 힘을 사용하라.

셸에 익숙해지면 생산성은 급상승한다.

평소 GUI 에서 수동으로 하는 작업이 있다면 셸로 자동화 해보자


Topic 18 - 파워 에디팅

에디터에 유창해지면 작업속도를 빠르게 할 수 있을 뿐 아니라, 에디터 사용법을 생각하지 않고도 작업할 수 있게 된다. 그러면 생각이 자유롭게 흘러갈 것이고 이는 프로그래밍에 큰 도움이 될 것이다.

유창해지기

에디터를 사용하면서 무언가 같은 일을 반복하는 경우가 있을 것이다. 이 작업을 발견하면 더 나은 방법(단축키)가 있는지 찾아봐라. 찾았다면 그것을 의식적으로 사용해봐라. 시간이 지나면 무의식적으로 사용할 수 있게 된다.


Topic 19 - 버전 관리

VCS은 시스템의 거대한 실행 취소 키와 같은 것으로, 정상적으로 컴파일 되는 지난 버전으로 돌려놓을 수 있도록 해준다. 이외에도 공동 작업, 배포 파이프라인, 이슈 추적, 팀 상호작용까지 아우를 수 있다.


Topic 20 - 디버깅

디버깅에 관련된 몇몇 문제들을 살펴보고 버그를 찾아내는 일반적인 전략 몇가지를 알아보자

디버깅의 심리

비난 대신 문제를 해결하라

버그가 누구의 잘못인지는 중요하지 않다. 다만 해결해야할 사람은 나라는 걸 명심하자

디버깅 사고방식

당황하지 마라

  • ‘그건 불가능해’ 란 생각을 하지마라. 실제로 일어났기 때문이다.
  • 근시안에 빠지지마라. 표면에 드러난 증상만 고치지 말고, 실제 원인을 찾으려고 노력하라

실마리 찾기

  • 작업중인 코드가 경고 없이 깨끗하게 빌드되는지 확인하라.
  • 관련 자료를 모두 모아라.
    • 실제 버그인지, 버그라면 자세한 현상이 무엇인지 자료를 요구하라
    • ex) 실제 시연되는 영상 등

디버깅 전략

버그 재현하기

버그를 고치는 가장 좋은 방법은 버그를 재현할 수 있게 하는 것이다. 버그를 명령하나로 재현할 수 있으면 더 좋다.

코드를 고치기 전 실패하는 테스트부터

버그가 발생하는 상황을 다른 것들로부터 분리하면 버그를 고칠 힌트를 얻을 수도 있다.

미지의 세계에 온 프로그래머

그놈의 오류 메세지 좀 읽어라

프로그램이 죽었다면 빨간색 예외 메시지를 무시하지마라

프로그램이 죽는 것이 아니라 결과가 이상한 상황이면 어떨까? 디버거를 붙인 다음 실패하는 테스트를 이용하여 문제를 재현하라. 그리고 먼저 디버거에 잘못된 값이 나타나는 지부터 확인하라.

이진 분할

거대한 스택 트레이스가 눈앞에 있을때, 이중에 정확히 어떤 부분에서 문제가 되는지 찾기 위해서는 중간부터 이진 분할 기법으로 찾을 수 있다.

입력값이 따라 버그가 발생하는 경우에도 정확히 어떤 입력값이 범인인지 이진분할을 통해 찾을 수 있다.

어떤 릴리스에서 버그가 발생하는지 찾고 싶은 경우에도 문제가 없던 릴리스와 현재 버전 사이에서 이진분할을 시도할 수 있다.

로깅과 트레이싱

트레이싱 구문은 ‘여기까지 도달’이나 ‘x값 = 2’ 같은 정보를 화면이나 파일에 출력하는 작은 진단용 메시지이다. 트레이싱은 여러 프로세스가 동시에 작동하는 경우, 특히 시간이 중요한 시스템에서는 중요한 정보가 된다.

트레이싱 구문으로 남기는 메세지는 규칙적이어야하며 일관된 형식이어야한다.

고무 오리

누군가(고무 오리도 괜찮다)에게 문제를 설명함으로써 원인을 찾을 수도 있다. 차근차근 코드를 설명함으로써 당연히 여기고 넘어갔을 부분을 명시적으로 이야기하게 되고, 그러면 문제가 보일 수 있다.

소거법

OS, 외부 라이브러리, 외부 제품에서 오류를 찾지 마라. 대개 외부 제품을 잘못 사용하고 있다고 생각하는 것이 더 낫다. 설사 외부 제품에 문제가 있다하더라도 우리 애플리케이션 코드에 문제가 없다는 걸 확인은 해야한다.

만약 하나만 변경했는데 시스템의 작동이 멈춘다면, 관련이 없어보여도 변경된 그 하나에 책임이 있다.

때로는 외부 제품이 새로운 버전으로 바뀌면서 버그가 생길 수도 있다. 따라서 이럴때는 새 조건하에서 시스템을 다시 테스트해봐야한다.

놀라운 구석

당연하다고 생각되는, 버그가 없을거라 생각되는 부분에서도 버그가 생길 수 있다. 버그가 발생한 데이터로, 맥락으로 다시 그 코드를 테스트하라. 이 경계 조건하에서 증명하라.

가정하지 말라. 증명하라.

이러한 놀라운 버그를 발견했을때는 단순히 고치는 것을 넘어 어떻게하면 미리 잡을 수 있었을지를 생각해보라. 그리고 버그를 고치는 김에 동일한 버그가 있을 법한 다른 코드도 확인하고, 지금 고쳐라.

버그가 누군가의 잘못된 가정으로 발생했다면 이 문제에대해 전체 팀과 함께 토론하라. 한 사람이 오해했다면 다른 사람도 그럴 수 있다.

디버깅 체크리스트

  • 보고된 문제가 내재하는 버그의 직접적 결과인가 아니면 단순히 증상일 뿐인가?
  • 버그가 정말로 여러분이 사용하는 프레임워크에 있나? OS에? 아니면 여러분 코드에 있나?
  • 이 문제를 동료에게 상세히 설명한다면 어떻게 말하겠는가?
  • 의심가는 코드가 단위 테스트를 통과한다면 테스트는 충분히 갖춰진 것인가? 이 데이터로 테스트를 돌리면 무신일이 생가는가?
  • 이 버그를 야기한 조건이 시스템의 다른 곳에도 존재하는가?

Topic 21 텍스트 처리

텍스트에 특화되어 처리해주는 도구가 있다 ex) sed, awk, 파이썬, 루비 등

이러한 텍스트 처리 도구는 중요한 기반 기술이며, 이러한 도구를 사용하면 유틸리티를 쉽게 만들거나, 아이디어를 프로토타입화 해볼 수 있다.

텍스트 처리 언어를 익혀라


Topic 22 엔지니어링 일지

회의에서 메모하거나, 작업 내용을 쓸때, 디버깅하다가 변수의 값을 적을때, 무엇을 어디 두었는지, 엉뚱한 생각을 기록할때 등 사용한다.

일지를 쓰면 좋은 점이 있다

  • 기억보다 믿을만하다
  • 진행 중인 작업과 직접적 관계가 없는 발상을 일단 쌓아놓을 수 있다.
  • 무엇가를 쓰면서 환기를 시킬 수 있다.
This post is licensed under CC BY 4.0 by the author.

실용주의프로그래머 2장 실용주의 접근법

실용주의프로그래머 4장 실용주의 편집증

Comments powered by Disqus.

diff --git "a/posts/\354\213\244\354\232\251\354\243\274\354\235\230\355\224\204\353\241\234\352\267\270\353\236\230\353\250\270-4\354\236\245-\354\213\244\354\232\251\354\243\274\354\235\230-\355\216\270\354\247\221\354\246\235/index.html" "b/posts/\354\213\244\354\232\251\354\243\274\354\235\230\355\224\204\353\241\234\352\267\270\353\236\230\353\250\270-4\354\236\245-\354\213\244\354\232\251\354\243\274\354\235\230-\355\216\270\354\247\221\354\246\235/index.html" new file mode 100644 index 000000000..10236c2b6 --- /dev/null +++ "b/posts/\354\213\244\354\232\251\354\243\274\354\235\230\355\224\204\353\241\234\352\267\270\353\236\230\353\250\270-4\354\236\245-\354\213\244\354\232\251\354\243\274\354\235\230-\355\216\270\354\247\221\354\246\235/index.html" @@ -0,0 +1,7 @@ + 실용주의프로그래머 4장 실용주의 편집증 | 디피의 개발일지
Posts 실용주의프로그래머 4장 실용주의 편집증
Post
Cancel

실용주의프로그래머 4장 실용주의 편집증

여러분은 완벽한 소프트웨어를 만들 수 없다

실용주의 프로그래머는 자신의 실수에 대비한 방어책을 마련한다.

  • Topic 23 : 코드의 공급자와 사용자는 권리와 책임에 대해 동의해야한다
  • Topic 24 : 버그상황에서 헤어나오는 도중에 어떤 손상도 입히지 않도록 보장해야한다.
  • Topic 25 : 확인을 쉽게하는 방법. 가정(assumption)을 적극적으로 검증하는 코드 작성
  • Topic 26 : 여러 시스템 리소스를 다룰때 실수 하지 않는 법
  • Topic 27 : 언제나 작은 단계를 고수해야한다.

Topic 23 : 계약에 의한 설계

DBC(Design By Contract, 계약에 의한 설계)

정확한 프로그램이란 자신이 하는 일을 적지도 많지도 않게 그만큼만 수행하는 프로그램이다. 이 주장을 문서화하고 검증하는 것이 DBC 이다.

선행조건

  • 루틴이 호출되기 전에 참이어야하는 조건. 이 선행조건이 위반된 경우 루틴이 호출되어서는 안된다. 후행조건
  • 루틴이 자기가 할 것이라고 보장하는 것. 루틴은 종료되어야하며 무한 반복은 안된다. 클래스 불변식
  • 호출자의 입장에서 이 조건이 언제나 참인 것을 클래스가 보장한다. 루틴 도중에는 참이 아닐 수 있지만, 루틴 종료 후에는 불변식이 참이 되어야한다.

만약 호출자가 모든 선행조건을 충족하면, 루틴은 종료 시 모든 후행조건과 불변식이 참이 되는 것을 보장한다. 따라서 만약 호출자나 루틴 중 어느 한쪽이라도 계약을 지키지 못하면 예외가 발생한다. 그렇기에 선행 조건을 이용해서 사용자 입력값을 검증한다거나 해서는 안된다.

호출자, 루틴 모두 입력값을 검증하지 않음. 호출자는 그냥 호출하고, 루틴은 실행 도중 예외를 발생시킴. 선행 조건은 호출자가 루틴을 부른 뒤, 루틴 자체로 들어가기 전에 보이지 않는 곳에서 검사됨.

클로져, 엘릭서 등이 이러한 DBC를 잘 지원한다.

클래스 불변식과 함수형 언어

이 용어가 의미하는 일반적인 개념은 상태이다. 함수형 언어에서는 보통 상태를 함수에 넘긴 후 바뀐 상태를 결과로 받는다.

DBC는 방어적 프로그래밍보다 더 효율적이고 DRY하다. 방어적 프로그램은 아무도 데이터를 검증하지 않는 상황에 대비하여 모든 사람이 데이터를 검증한다.

DBC 구현

코드를 작성하기 전에 유효한 입력범위가 무엇인지, 경계조건이 무엇인지, 루틴이 뭘 전달한다고 약속하는지, 무엇을 약속하지 않는지 나열하는 것만으로도 더 나은 코드를 작성할 수 있다. 코드에서 DBC를 지원하지 않으면 이정도가 최선이다. DBC는 설계 기법이고, 자동 검사가 없더라도 계약을 코드에 주석이나 단위 테스트로 넣어둘 수 있다

단정문

컴파일러가 계약을 검사하도록 하면 큰 도움이 된다. 몇몇 언어에서는 단정문으로 부분적으로 흉내낼 수 있다. 다만 단정문은 객체지향에서는 재정의 할때마다 일일이 작성하여야하고, 어떤 환경에서는 단정문에서 발생하는 예외를 무시하기에 완전한 방법은 아니다.

DBC와 일찍 멈추기

단정문이나 DBC 방식을 사용하여 선행조건, 후행조건, 불변식을 검증하면 더 일찍 멈추고 문제에 대한 정확한 정보를 얻을 수 있다.

의미론적 불변식

시스템에서 영원히 변경되지 않는 요구사항. 비즈니스 정책처럼 유동적으로 변하는 것에 영향을 받으면 안된다.

ex)

  • 같은 거래를 두번 처리하지 않는다
  • 오류 발생 시 소비자의 입장을 우선하라

Topic 24 : 죽은 프로그램은 거짓말을 하지 않는다.

프로그램에서 잘못된 것이 있을때 라이브러리나 프레임워크 루틴에서 먼저 잡아낼 수 있다. 빈 데이터를 받았거나, switch-case 문에서 default가 실행되었거나 하는 일이 발생할 수 있다. 이런 있을 수 없는 일이 발생했을때 우리는 그 사실을 알아야한다.

모든 오류는 정보를 준다. 오류 메시지 좀 읽어라.

잡은 후 놓아주는건 물고기뿐

어떤 개발자는 예외를 잡은 후 로그 좀 찍고 다시 예외를 올려보낸다. 하지만 실용주의 프로그래머는 굳이 잡지 않는다.

  • 애플리케이션 코드가 오류 처리 코드 사이에 묻히지 않는다.
  • 메서드에서 새로운 예외가 추가되었을때 코드를 수정하지 않아도 된다.

일찍 작동을 멈춰라

망치지 말고 멈춰라

문제를 빨리 발견하면 가능한 빨리 시스템을 멈출 수 있다.

얼랭과 엘릭서는 “방어적 프로그램은 시간낭비고, 그냥 멈추는 게 낫다”라는 철학을 가지고 있다. 프로그램이 실패하면 멈추고, 이런 실패는 슈퍼바이저가 관리한다. 슈퍼바이저는 코드를 실행하고, 실패 시 처리할 책임이 있다. 슈퍼파이저가 실패하면 슈퍼바이저의 슈퍼바이저가 처리한다. 이러한 슈퍼바이저 트리 기법은 효과적이어서 고가영성, 결함 감내 시스템에서 얼랭이나 엘릭서를 채택하는 요인이 된다.

어떤 환경에서는 멈추는게 적절하지 않을 수 있다. 그러나 있을 수 없는 일이 발생했다면 프로그램은 더이상 유효하지 않다고 할 수 있기에 가능한 빨리 종료되는 것이 좋을 수 있다.

일반적으로 죽은 프로그램이 끼치는 피해는 이상한 상태의 프로그램이 끼치는 피해보다 적다.


Topic 25 : 단정적 프로그래밍

단정문으로 불가능한 상황을 예방하라.

코딩할때 절대 일어나지 않을 일은 없다. 그런 생각이 든다면, 그것을 확인하는 코드를 추가하라. 다만, 오류처리를 해야하는 곳에서 단정을 대신 사용하지는 말라. 단정은 결코 일어나면 안되는 것들을 검사한다.

ex) 아래와 간은 코드는 작성해서는 안된다.

1
+2
+3
+
puts("Y나 N을 입력하세요")
+ch = gets[0]
+assert((ch == 'Y') || (ch == 'N')) 
+

단정이 실패한다고 바로 프로세스를 종료시킬 필요는 없다. 예외를 잡고 오류처리 루틴을 실행할 수도 있다. 다만, 단정을 실패하게 한 잘못된 정보를 사용하지는 말라.

단정 기능을 켜둬라

테스트는 모든 버그를 발견할 수 없고, 일어날 수 없는 일은 일어날 수 있다. 따라서 단정기능을 실제 환경에서 끄는 것은 문제가 될 수 있다.

성능상 이슈가 있다면 정말 문제가 되는 단정문만 꺼라.


Topic 26 : 리소스 사용의 균형

리소스 사용을 위한 간단한 팁들이 있다.

자신이 시작한 것은 자신이 끝내라

리소스 사용을 시작한 곳에서 리소스 할당을 해제하여야한다.

지역적으로 행동하라

잘 모르겠으면 스코프를 줄여라.

중첩할당

  • 리소스를 할당한 순서의 역순으로 해제하라. 그래야 한 리소스가 다른 리소스를 참조하는 경우에도 참조를 망가트리지 않는다
  • 코드의 여러 곳에서 같은 구성의 리소스를 참조하면 항상 같은 순서로 할당하라. 그래야 교착상태에 빠지지 않는다.
    • ex) resource1, resource2가 필요할때 항상 1 -> 2로 할당하라.

균형잡기와 예외

예외를 처리할때 리소스 해제는 다루기 어려울 수 있다. 이때는 두가지 방법을 사용할 수 있다.

  1. 변수 스코프를 사용한다. ex) c++, 러스트의 스택변수
  2. try-catch 블록에서 finally 절 사용

Topic 27 : 헤드라이트를 앞서가지 말라

우리는 너무 먼 미래를 내다볼 수 없고 정면에서 벗어난 곳일수록 더 어둡다

작은 단계들을 밟아라. 언제나.

언제나 신중하게 작은 단계들을 밟아라. 더 진행하기 전에 피드백을 확인하고 조정하라. 너무 큰 단계나 작업은 하지 않게 될 것이다.

ex) 피드백이란?

  • REPL의 결과
  • 단위 테스트
  • 사용자 데모 및 사용자와의 대화

너무 큰 작업이란 예언을 하는 모든 작업을 뜻한다. 불확실한 미래에 대비한 설계를 하기보단 언제나 교체 가능한 코드를 작성하여 대비하여라.


후기

이번 장은 코딩할 때 발생할 수 있는 이슈들에 대비하기 위한 팁들을 소개하는 장이었다.

일어날 수 없는 일이란 없고, 이에 대비하기 위한 단정문에 대한 얘기가 많이 나왔다. 개인적으로 단정문을 사용한 적은 없어서 내용이 잘 이해가 되지 않았다. 평소에는 if 문을 통해 조건을 확인하고 거짓이면 함수를 종료하는 식으로 하였는데, 이 방법과 단정문을 사용한 방법에 어떤 차이가 있는지 확실히 이해가 되지는 않았다.

다만 몇가지 얻어갈 것은 있었다.

  1. DBC의 개념은 함수를 설계할때 도움이 될거 같다. 선행조건, 후행조건, 불변식을 지키며 계약 이상의 것을 하지않는 함수라는 개념으로 예측 가능한 함수를 설계가능할거 같다.
  2. 문제가 발생하면 가능한 빨리 멈추는 것이 좋다라는 내용도 적용하기 좋다.
  3. 예외 발생 시 catch 한 후 로그찍고 다시 그대로 올려보내는 것보다, 잡지 않고 올려보내거나 이왕 잡았으면 적절히 처리하여 다른 예외를 발생 시키는 것이 좋아보인다. (이 부분은 책에서 말하는 바와 다를 수 있다)
  4. 리소스 사용에 관한 팁들도 도움이 될거 같다.
  5. 작은 작업을 한 후 피드백(테스트든 사용자든)을 받는 과정을 반복하여야하고, 너무 큰 작업을 한번에 하지 말라는 팁도 도움이 될거 같다.
This post is licensed under CC BY 4.0 by the author.

실용주의프로그래머 3장 기본도구

실용주의프로그래머 5장 구부러지거나 부러지거나

Comments powered by Disqus.

diff --git "a/posts/\354\213\244\354\232\251\354\243\274\354\235\230\355\224\204\353\241\234\352\267\270\353\236\230\353\250\270-5\354\236\245-\352\265\254\353\266\200\353\237\254\354\247\200\352\261\260\353\202\230-\353\266\200\353\237\254\354\247\200\352\261\260\353\202\230/index.html" "b/posts/\354\213\244\354\232\251\354\243\274\354\235\230\355\224\204\353\241\234\352\267\270\353\236\230\353\250\270-5\354\236\245-\352\265\254\353\266\200\353\237\254\354\247\200\352\261\260\353\202\230-\353\266\200\353\237\254\354\247\200\352\261\260\353\202\230/index.html" new file mode 100644 index 000000000..c81b6cfb5 --- /dev/null +++ "b/posts/\354\213\244\354\232\251\354\243\274\354\235\230\355\224\204\353\241\234\352\267\270\353\236\230\353\250\270-5\354\236\245-\352\265\254\353\266\200\353\237\254\354\247\200\352\261\260\353\202\230-\353\266\200\353\237\254\354\247\200\352\261\260\353\202\230/index.html" @@ -0,0 +1,69 @@ + 실용주의프로그래머 5장 구부러지거나 부러지거나 | 디피의 개발일지
Posts 실용주의프로그래머 5장 구부러지거나 부러지거나
Post
Cancel

실용주의프로그래머 5장 구부러지거나 부러지거나

이번 장에서는 되돌릴 수 있는 의사결정을 내리는 구체적인 방법을 설명한다.

  • topic 28 : 결합도 줄이기
  • topic 29 : 실세계를 갖고 저글링하기. 이벤트에 반응하는 네 가지 서로 다른 전략
  • topic 30 : 변환 프로그래밍. 함수 파이프라인
  • topic 31 : 상속세. 유연하고 바꾸기 쉬운 코드를 만들 수 있는 대안
  • topic 32 : 설정. 세부사항을 코드 밖으로 옮기는 방법

Topic 28 : 결합도 줄이기

코드에서 나타나는 결합의 증상

  • 관계없는 모듈이나 라이브러리 간의 희한한 의존 관계
  • 한 모듈의 간단한 수정이 이와 관계없는 모듈을 통해 시스템 전역으로 퍼져나가거나 시스템의 다른 곳에서 무언가를 꺠뜨리는 경우
  • 개발자가 수정하는 부분이 시스템에 어떤 영향을 미칠지 몰라 코드의 수정을 두려워하는 경우
  • 변경 사항에 누가 영향을 받는지 파악하고 있는 사람이 없어서 결국 모든 사람이 참석해야하는 회의

열차 사고

1
+2
+3
+4
+5
+
public void applyDiscount(customer, order_id, discount) {
+	totals = customer.orders.find(order_id).getTotals();
+	totals.grandTotal = totals.grandTotal - discount;
+	totals.discount = discount
+}
+

이 코드를 지원하기 위해 앞으로 바꾸면 안되는 것들이 너무 많다. 기차의 객차가 연결되어있듯이 메서드나 속성들이 모두 연결되어있다. 이런 코드를 열차 코드라고 부른다.

만약 할인율을 변경해야할때는 어떻게 해야할까? totals 객체의 필드는 어디서든 변경할 수 있다. 따라서 다른 곳에서 필드 값을 바꾸는 부분이 있다면 모두 변경해야한다.

이것은 책임의 문제이다. totals 객체가 합계를 관리하는 책임을 져야하는데 그렇게 하고 있지 않다. 누구나 질의하고 갱신할 수 있는 다수의 필드를 가진 컨테이너일 뿐이기에 그렇다.

이를 고치려면 다음 원칙을 적용해야한다.

묻지말고 말하라

다른 객체의 내부 상태에 따라 판단을 내리고 그 객체를 갱신해서는 안된다. 즉, 할인 처리를 totals 객체에 위임해야한다.

1
+2
+3
+
public void applyDiscount(customer, order_id, discount) {
+	customer.orders.find(order_id).getTotals().applyDiscount(discount);
+}
+

더 개선하면 customer 객체에서 주문 컬렉션을 가져오지 말아야한다.

1
+2
+3
+
public void applyDiscount(customer, order_id, discount) {
+	customer.findOrder(order_id).getTotals().applyDiscount(discount);
+}
+

추가로 order 객체가 totals 객체를 가졌다는 걸 굳이 드러낼 필요도 없다.

1
+2
+3
+
public void applyDiscount(customer, order_id, discount) {
+	customer.findOrder(order_id).applyDiscount(discount);
+}
+

여기서 더 숨길 수도 있지만, 여기서는 customerorder가 최상위 개념이다. 이정도는 드러내도 괜찮을 것이다.

데메테르 법칙

LoD(데메테르 법칙) : 어떤 클래스 C에 정의된 메서드는 다음 목록에 속하는 것만 사용할 수 있다.

  • C의 다른 인스턴스 메서드
  • 메서드의 매개변수
  • 스택이나 힙에 자신에 생성하는 객체의 메서드
  • 전역 변수

다음 말이 좀 더 쉬울 수 있다.

메서드 호출을 엮지 말라

무언가를 접근할 때 .을 딱하나만 사용하도록 노력하라. 다만 엮는 것들이 절대로 바뀌지 않을 것 같다면 괜찮다. (언어에서 기본적으로 포함된 기능들. 외부 라이브러리는 아님)

연쇄와 파이프라인

함수에서 함수로 데이터를 넘겨가며 데이터를 변환하는 것은 열차사고와는 다르다. 숨겨진 구현 세부 사항에 의존하지 않기 때문이다. 하지만 결합이 생기지 않는 것은 아닌데, 한 함수의 결과물이 다음 함수의 입력 형식이어야하기 때문이다. 하지만 이러한 결합은 열차사고보단 문제가 되는 경우가 적다.

글로벌화의 해악

전역 데이터는 애플리케이션 컴포넌트 간의 결합을 만들어낸다. 전역 데이터는 시스템 전체에 영향을 줄 수 있다. 변경될 때 바꿔야할 곳을 전부 바꿨는지 확인하기 어려울 수 있다.

전역 데이터를 피하라.

싱글턴도 전역 데이터이다.

외부로 노출된 인스턴스 변수가 많은 싱글턴은 여전히 전역 데이터이다. 메서드 안으로 데이터를 숨기면 한층 낫긴하지만 설정데이터를 단 한 벌만 가지고 있는 것에는 변함이 없다.

외부 리소스도 전역 데이터다

수정 가능한 외부 리소스(DB, API 등)는 모두 전역 데이터다. 이러한 리소스들을 코드로 모두 감싸는 것이 좋다.

전역적이어야 할 만큼 중요하다면 API로 감싸라.

결국은 모두 ETC

직접적으로 아는 것만 다루는 부끄럼쟁이 코드를 계속 유지하라. 그러면 결합도를 크게 낮출 수 있다.


Topic 29 : 실세계를 갖고 저글링하기

이번 장은 반응적인 애플리케이션을 작성하는 법을 다룬다.

이벤트

이벤트는 무언가 정보가 있다는 것을 의미한다. 정보는 외부에서 올 수도 있고, 내부에서 생길 수도 있다. 어디에서 온 것이든 애플리케이션이 이런 이벤트에 반응하고 하는 일을 조절하도록 만들면, 실세계에서 더 잘 작동하는 애플리케이션을 만들 수 있다.

이러한 애플리케이션을 만드는 다음 네가지 전략이 있다.

  • 유한 상태 기계
  • 감시자 패턴
  • 게시 - 구독
  • 반응형 프로그래밍과 스트림

유한 상태기계

FSM은 작성해야하는 부분은 적지만, 한번 작성하면 이벤트를 어떻게 처리할지 한눈에 알기 쉽다.

감시자 패턴

감시자 패턴은 이벤트를 발생시키는 쪽인 감시대상과, 이벤트에 관심이 있는 감시자로 이루어진다.

감시자는 자신이 관심 있는 이벤트를 감시대상으로 등록한다. 보통은 호출될 함수의 참조도 같이 넘긴다. 추후 해당 이벤트가 발생 시, 등록된 감시자 목록을 보며 함수들을 일일이 호출한다.

감시자 패턴은 수십년간 잘 작동해왔지만 한가지 문제점이 있다. 바로 모든 감시자가 감시 대상에 등록해야하는 결합이 생긴다는 것이다. 또 감시 대상이 콜백을 직접 호출하기에 성능 병목이 생길 수 있다. 이러한 문제점은 게시-구독으로 해결한다.

게시 - 구독

감시자 패턴을 일반화한 것으로 감시자 모델의 결합도를 높이는 문제와 성능 문제를 해결한다.

게시-구독 모델은 게시자구독자가 있고, 채널로 연결된다. 채널은 코드 밖에 있으며, 게시자가 채널로 이벤트를 보내고, 구독자는 채널을 통해 이벤트를 받는다. 이는 비동기적으로 이루어진다.

이러한 게시-구독 모델은 대부분의 클라우드 서비스가 제공하기에 직접 구현할 필요는 없다. 또한 추가적인 결합이 없어 기존 코드를 수정하지 않고 이벤트 처리코드를 추가하거나 교체할 수 있다.

단점은 너무 많이 사용하는 시스템에서는 현재 어떤 일이 벌어지는지 파악하기가 어렵다는 것이다. 게시자가 이벤트를 보내고 어떤 구독자가 처리하는지 확인하기 어렵다.

반응형 프로그래밍과 스트림 그리고 이벤트

반응형 프로그래밍 : 하나의 값이 바뀌면, 그 값을 사용하는 다른 값이 반응하는 것

  • ex) 엑셀, 리액트, 뷰

스트림 : 이벤트를 일반적인 자료구조처럼 다룰 수 있게 해줌. 이벤트의 리스트. 이벤트를 처리하고, 조합하고, 골라내는 등 일반적인 자료구조처럼 다룰 수 있음

  • 비동기적으로 작동할 수도 있다.

반응형 이벤트의 표준은 https://reactivex.io/ 에서 정의했다. 이 사이트에서는 언어에 무관한 원칙들을 정의하고 몇가지 공통 구현사항을 문서화 했다. 이를 구현한 것으로는 자바스크립트 RxJS 등이 있다.

후기)

스트림 부분은 잘 이해가 안갔음. 사용해본적이 없어서 정확히 어떤 것에서 큰 장점이 있는지 모르겠음. 한번 예제라도 건드려 보는게 좋을거 같다

어디에나 이벤트가 있다

이벤트는 모든 곳에 있다. 하지만 이벤트가 어디서 발생하든 이벤트를 중심으로 공들여 만든 코드는 일직선으로 수행되는 코드보다 더 잘 반응하고 결합도가 낮다.


Topic 30 : 변환 프로그래밍

모든 프로그램은 데이터를 변환한다. 코드에만 집중하여 핵심을 놓치지말고, 프로그램이란 입력을 출력으로 바꾸는 것이라고 생각하라. 이렇게하면 프로그램 구조는 명확해지고, 결합도 대폭 줄어들 것이다.

프로그래밍은 코드에 관한 것이지만, 프로그램은 데이터에 관한 것이다.

변환 찾기

요구사항에서 입력과 출력이 무엇인지 찾으면 전체 프로그램을 나타내는 함수가 정해짐. 이후에는 입력을 출력으로 바꾸는 단계를 차례대로 찾으면 됨. (top-down 방식)

변환 모델의 좋은점

객체지향프로그래밍을 하다보면 데이터를 숨기고, 객체 안에 캡슐화해야한다고 생각한다. 이런 객체들이 서로 대화하며 서로의 상태를 변경하는데, 이런 방식은 결합을 많이 만들어내고, 이는 이후 유지보수가 어려운 코드를 만들게한다.

상태를 쌓아놓지말고 전달하라.

변환 모델은 데이터를 전체 시스템에 흩어놓는 대신, 데이터를 거대한 강으로 생각한다. 데이터는 기능과 동등해져서 파이프라인은 코드 -> 데이터 -> 코드 -> 데이터의 연속이다. 데이터는 특정한 함수들과 묶이지 않는다. 대신 우리 애플리케이션이 입력을 출력으로 바꾸어 나가는 진행 상황을 데이터로 자유롭게 표현하여 결합을 크게 줄일 수 있다. 어떤 함수든 매개변수가 다른 함수의 출력결과와 맞기만 하면 어디서나 재사용할 수 있다.

ex) 보통 객체지향에서는 두번째처럼하는데, 두번째는 앞단계에서 반환하는 객체에 다음 함수가 구현되어있어야한다. 만약 새로운 요구사항이 생겼을때, 두번째는 해당 객체에서 함수를 변경해야하는데 다른 코드에 영향이 갈까 두려움이 생길 것이다. 첫번째 방식은 그냥 한단계를 추가하면 된다.

  • 데이터 변환
    1
    +2
    +3
    +
    const content = File.read(fileName);
    +const lines = findMatchingLines(content, pattern);
    +const result = truncateLines(lines);
    +
  • 메서드 연결
    1
    +2
    +3
    +
    const result = contentOf(fileName)
    +              .findMatchingLines(pattern)
    +              .truncateLines();
    +

오류 처리는 어떻게 하나?

공통적으로 연쇄 변환 도중 변환 사이에 값을 날것으로 넘기지 않는 관례가 있다. Wrapper 역할을 하는 자료구조로 값을 싸서 넘긴다.

이런 개념을 이용하여 오류 검사를 변환 안에서 하거나, 변환 바깥에서 할 수 있다.


Topic 31 : 상속세

코드를 공유하기 위해 상속을 쓸때의 문제

상속도 일종의 결합이다. 만약 최상위 클래스만 바꾸고 싶을때도 하위 클래스에서 어떻게 사용하고 있는지에따라 코드가 망가질 수 있다. 최상위 클래스의 담당자가 자기는 한 클래스의 변수명을 변경하였는데, 코드 전체가 망가지면 당황할 것이다.

타입을 정의하기 위해 상속을 쓸때의 문제

상속을 타입을 정의하기 위해 사용하는 경우도 있다. 이때 클래스 계층도 같은 것을 사용할 수 있다. 하지만 클래스 사이의 미묘한 차이를 위해 계층을 계속 더하다보면 클래스 계층도는 매우 복잡해진다.

다중 상속을 지원하지 않는 것도 문제가 될 수 있는데, 어떤 클래스를 제대로 모델링하려면 다중 상속이 필요해질 수 있기 때문이다.

더 나은 대안

인터페이스

자바의 인터페이스 같은 것을 사용할 수 있다. 인터페이스는 단순한 선언이고, 아무런 코드도 만들지 않으며 단순히 선언된 메서드를 구현해야한다고 지시할 뿐이다. 또한 이들을 타입으로 사용할 수 있고, 해당 인터페이스를 구현한 클래스라면 무엇이든 그 타입과 호환된다.

다형성은 인터페이스로 표현하는 것이 좋다.

1
+2
+3
+4
+
public interface Locatable {
+	Coordinate getLocation();
+	boolean locationIsValid();
+}
+

위임

상속을 사용하게 되면 딱 2개의 메서드만 필요해도 부모의 20개의 메서드가 한번에 딸려온다. 클래스가 자신의 인터페이스를 제어할 수 없게 되는 것이다.

대신에 위임을 사용하면 클래스는 필요한 것만 노출할 수 있다.

서비스에 위임하라. Has-A 가 Is-A보다 낫다.

믹스인, 트레이트, 카테고리, 프로토콜 확장 등

언어마다 이름은 다르지만, 클래스나 객체에 상속을 사용하지 않고 새로운 기능을 추가하여 확장하고 싶을 떄 사용하는 기능이다.

1
+2
+3
+4
+5
+6
+7
+
mixin CommonFinders {
+	def find(id) {...}
+	def findAll() {...}
+}
+
+class AccountRecord extends BasicRecord with CommonFinders
+class OrderRecord extends BasicRecord with CommonFinders
+

믹스인으로 기능을 공유하라

예를들어 검증을 적용할 수 있는 여러 계층이 있을때, 각 상황에 맞는 전문화된 클래스를 믹스인을 사용하여 만들 수 있다.

1
+2
+3
+
class AccountForCustomer extends Account with AccountValidations, AccountCustomerValidations
+
+class AccountForAdmin extends Account  with AccountValidations, AccountAdminValidations
+

상속이 답인 경우는 드물다

  • 인터페이스
  • 위임
  • 믹스인과 트레이트

상황에 따라 상속보다 더 나은 방법이 있을 것이다. 타입 정보를 교환하고 싶은건지, 기능을 더하고 싶은건지, 메서드를 공유하고 싶은건지에 따라 다르다. 우리의 목표는 의도를 가장 잘 드러내는 기법을 사용하는 것이다. 그리고 정글 전체를 끌어들이지 않도록 조심하다


Topic 32 : 설정

프로그램 출시 이후 변경될 것이라 생각되는 값들은 프로그램 외부에서 관리하라. 이렇게하면 프로그램을 조정할 수 있게 된다.

외부 설정으로 애플리케이션을 조정할 수 있게하라.

소스 코드 본체 바깥에 표현할 수 있는 것을 찾아라.

정적 설정

대부분의 프레임워크에서는 설정을 일반 파일이나 데이터베이스 테이블로 관리한다. 어떤 형태로 사용하든 설정을 자료구조 형태로 불러온다. 보통 처음 애플리케이션이 실행 될 때 읽어올 것이다.

보통 이 설정을 전역에서 접근할 수 있도록 하는데, 그것보다는 API 뒤로 숨기는 것이 좋다. 그러면 설정을 표현하는 세부 사항으로부터 코드를 떼어놓을 수 있다

서비스형 설정

설정을 외부에서 관리하긴하지만, 일반 파일이나 데이터베이스가 아니라 서비스 API 뒤에서 관리하는 방식이다. 여기에는 몇가지 장점이 있다.

  • 여러 애플리케이션이 설정 정보를 공유할 수 있다.
  • 여러 인스턴스에 걸쳐서 전체 설정을 한번에 바꿀 수 있다.
  • 설정 데이터를 전용 UI로 관리할 수 있다.
  • 설정 데이터를 동적으로 계속 바꿀 수 있다. -> 새로이 빌드-배포가 필요없다.

설정 정보를 바꾸기 위해 코드 빌드가 필요없는 방식이다.

지나치게 하지는 말라

지나치게 모든 변수를 설정할 수 있게하지는 말라.

여담

실무를 경험해본 결과 서비스형 설정이 확실히 좋았다. 이러한 값들은 빌드가 필요없이 바로 참조가 되어 이슈가 발생했을 경우 곧바로 대응을 할 수가 있기 때문이다.

물론 스프링에서 properties 파일과 같은 정적 설정도 장점이 있다. 설정 정보의 성격에 따라 적절히 분리해서 관리하면 될 거 같다.

  • 정적 설정 : 자주 변경되지 않지만 애플리케이션에 거쳐 많이 사용되거나 외부로 뺄 필요가 있어보이는 값들. 급하게 변경하지 않아도 이슈가 없는 값들
  • 서비스형 설정 : 자주 변경될거라 예상되는 값들. 급하게 변경이 발생할 수 있는 값들.

후기

이번 장은 전체적으로 결합도를 줄이는 것에 집중한 장이다.

topic 28 : 책임을 분명히하고, 자신이 직접적으로 아는 것만 다루는 방식을 통해 결합도를 줄일 수 있다. 전역 변수도 일종의 결합인데, 전역변수 변경 시 필요한 부분을 모두 바꾸었는지 확인하기 어렵기 때문이다.

이 토픽에서는 데메테르 법칙에 대해서도 얘기하였는데, 쉽게 풀이한 “무언가에 접근할 때 .을 두번 이상 사용하지 마라”라는 팁은 이해하기가 쉬웠다.

topic 29 : 이벤트를 구현하는 방식에 네가지 방식(유한 상태 기계, 감시자 패턴, 게시 - 구독, 반응형 프로그래밍과 스트림)에 대해 설명하였다. 유한상태기계를 작성하면 이벤트를 어떻게 다뤄야하는지 알기 쉽다는 팁이 도움될거 같다.

topic 30 : 프로그램은 결국 데이터를 변환하는 것이고, 데이터를 중심으로 구조를 생각하면 어떻게 파이프라인을 구성해야할지 한눈에 보인다.

topic 31 : 상속은 결합을 만들어내기에 사용을 지양해야한다는 파트이다. 상속 대신 인터페이스, 위임, 믹스인 등을 통해 기능을 추가하는 것이 좋다고 한다. 개인적으로 동감하는 파트이고, 상속은 프레임워크에서 제공하는 것 외에는 사용을 자제하는 것이 좋다고 생각한다.

topic 32 : 설정을 소스 본체 바깥에 저장하는 방식을 설명한다. 개인적으로 서비스형 설정이 변화에 대응이 쉬워 더 선호한다.

This post is licensed under CC BY 4.0 by the author.

실용주의프로그래머 4장 실용주의 편집증

실용주의프로그래머 6장 동시성

Comments powered by Disqus.

diff --git "a/posts/\354\213\244\354\232\251\354\243\274\354\235\230\355\224\204\353\241\234\352\267\270\353\236\230\353\250\270-6\354\236\245-\353\217\231\354\213\234\354\204\261/index.html" "b/posts/\354\213\244\354\232\251\354\243\274\354\235\230\355\224\204\353\241\234\352\267\270\353\236\230\353\250\270-6\354\236\245-\353\217\231\354\213\234\354\204\261/index.html" new file mode 100644 index 000000000..97f17c55d --- /dev/null +++ "b/posts/\354\213\244\354\232\251\354\243\274\354\235\230\355\224\204\353\241\234\352\267\270\353\236\230\353\250\270-6\354\236\245-\353\217\231\354\213\234\354\204\261/index.html" @@ -0,0 +1 @@ + 실용주의프로그래머 6장 동시성 | 디피의 개발일지
Posts 실용주의프로그래머 6장 동시성
Post
Cancel

실용주의프로그래머 6장 동시성

  • 동시성 : 둘 이상의 코드 조작이 실행될 때 동시에 실행 중인 것처럼 행동하는 것. 소프트웨어 동작
    • 코드의 다른 부분으로 실행을 전환할 수 있는 환경에서 코드를 구동해야함. ex) 스레드, 프로세스
  • 병렬성 : 실제로 동시에 실행되는 것. 하드웨어 동작
    • 두가지 일을 동시에 할 수 있는 하드웨어가 필요함. ex) CPU내 코어, 네트워크로 연결된 컴퓨터 등

모든 일에 동시성이 있다.

시스템이 커지면 동시성은 필수다. 어떤 일을 할 때, 한번에 하나씩 순차적으로 수행 하면 시스템이 매우 느려질 것이기 때문이다.

따라서 코드 간의 결합 이외에도, 시간적 결합을 깨는 것도 중요하다.

Topic 33 : 시간적 결합 깨트리기

코드를 짜다보면 순차적 사고를 하기 마련이다. 하지만 유연성과 동시성을 확보하기 위해 시간이나 순서에 의존하는 시간적 결합을 끊어야한다. 그럼 시간 관련 의존성을 줄일 수 있고, 더 빠르고 안정적인 시스템을 만들 수 있다.

동시성 찾기

동시에 일어나도 되는 일과 반드시 순서대로 일어나야하는 일을 구분하기 위해, 활동 다이어그램 등을 사용하여 작업 흐름을 모델화 할 수 있다.

동시 작업의 기회

활동 다이어그램을 통해 동시에 작업할 수 있는 부분들이 있다. 이 중에서 데이터베이스 접근, 외부 서비스 요청 등 우리 코드가 아닌 곳에서 시간이 걸리는 일을 할 때, 우리 코드는 다른 작업을 할 수 있을 것이다.

병렬 작업의 기회

큰 작업을 독립적인 부분 작업으로 나누어 여러 프로세서에서 동시에 일을 처리할 수 있다.


Topic 34 : 공유 상태는 틀린 상태

공유 상태는 틀린 상태다

상태가 공유되면 동시성을 구현할 때 프로그램 오류가 발생할 수 있다. 따라서 어떤 상태가 있다면 이를 원자적으로 갱신하여야한다. 여기에는 다음 방법들이 있다.

  • 세마포어
    • 세마포어를 소유하고 있을 때만 조회/갱신을 할 수 있도록 함.
    • 단점 : 누군가가 세마포어를 얻는걸 잊어버릴 수 있음.
  • 리소스를 트랜잭션으로 관리하라
    • 리소스 제어를 중앙으로 집중하여, 조회와 동시에 업데이트를 할 수 있도록 한다.

여러 리소스와 트랜잭션

어떤 작업을 수행할 때, 여러 리소스가 동시에 필요하면 공유 상태 관리는 더 복잡해진다. 하지만 이때도 여러 리소스를 하나의 리소스로 보고, 이 리소스에 접근하는 코드를 모듈로 옮긴 후, 클라이언트는 그 리소스를 달라고 요청하면 그림이 간단해진다. 클라이언트가 여러 리소스를 직접 호출하는 것보다 훨씬 간단하다.

트랜잭션이 없는 갱신

수정 가능한 리소스를 공유하는 애플리케이션 코드 어디에서나 동시성 문제가 발생할 수 있다. 여기에는 한가지 팁이 있다.

불규칙한 실패는 동시성 문제인 경우가 많다.


Topic 35 : 액터와 프로세스

액터

자신만의 비공개 지역상태를 가진 독립적인 가상 처리 장치. 우편함을 하나씩 보유하고 있고, 메세지를 받으면 차례대로 처리한다. 메세지를 처리할 때 액터는 다른 액터를 생성하거나, 메세지를 보낼 수 있고, 새로운 상태를 생성할 수 있다.

액터는 언제나 동시성을 띤다

  • 액터를 관리하는 것은 없다.
  • 시스템이 저장하는 상태는 메시지와 각 액터 내의 지역 상태일 뿐이다.
  • 모든 메시지는 일방향이고, 메시지 내에서 다른 메시지를 보내는 것만 가능하다.
  • 한번에 하나의 메시지만 처리한다.

위 특징들로 액터들은 아무것도 공유하지 않고 비동기적으로 동시에 실행된다. 따라서 동시성을 다루는 코드도 필요없다.

공유 상태 없는 동시성을 위하여 액터를 사용하라


Topic 36 : 칠판

칠판은 일종의 자유방임주의적 동시성으로, 독립된 프로세스, 에이전트 등이 수집한 사실을 붙이고 누군가는 떼어내기도 한다. (ex : 린다, JavaSpace, T Spaces 등)

복잡한 규칙과 과정을 준수해야하는 대출 시스템에서 사용할 수도 있는데, 어떤 사실이 칠판에 올라가면 규칙 엔진이 돌아가서 적절한 규칙을 발동시키면 된다.

메시지 시스템과 칠판의 유사성

카프카와 같은 메시징 시스템은 단순히 메시지를 보내는 것 이상으로 이벤트 로그를 통한 영속성지원, 패턴 매칭으로 메시지 조회를 지원한다. 따라서 메시징 시스템을 칠판으로 사용할 수도 있다.

하지만 간단하지 않다.

아키텍처에서 액터, 칠판, 마이크로서비스를 사용하면 애플리케이션에서 생길 수 있는 동시성 문제를 예방할 수 있다. 하지만, 이런 접근 방식은 많은 동작이 간접적으로 일어나서 분석이 어렵다.

이에 대한 기법으로, 처리를 시작할 때 고유한 ‘추적 아이디 (Trace Id)’를 붙일 수 있다.

This post is licensed under CC BY 4.0 by the author.

실용주의프로그래머 5장 구부러지거나 부러지거나

실용주의프로그래머 7장 코딩하는 동안

Comments powered by Disqus.

diff --git "a/posts/\354\213\244\354\232\251\354\243\274\354\235\230\355\224\204\353\241\234\352\267\270\353\236\230\353\250\270-7\354\236\245-\354\275\224\353\224\251\355\225\230\353\212\224-\353\217\231\354\225\210/index.html" "b/posts/\354\213\244\354\232\251\354\243\274\354\235\230\355\224\204\353\241\234\352\267\270\353\236\230\353\250\270-7\354\236\245-\354\275\224\353\224\251\355\225\230\353\212\224-\353\217\231\354\225\210/index.html" new file mode 100644 index 000000000..fd2823d84 --- /dev/null +++ "b/posts/\354\213\244\354\232\251\354\243\274\354\235\230\355\224\204\353\241\234\352\267\270\353\236\230\353\250\270-7\354\236\245-\354\275\224\353\224\251\355\225\230\353\212\224-\353\217\231\354\225\210/index.html" @@ -0,0 +1,9 @@ + 실용주의프로그래머 7장 코딩하는 동안 | 디피의 개발일지
Posts 실용주의프로그래머 7장 코딩하는 동안
Post
Cancel

실용주의프로그래머 7장 코딩하는 동안

코딩은 설계내용을 단순히 코드로 바꾸는 과정이 아니다. 이번 장에서는 프로그램을 정확하게 동작하게 하려면 어떻게 해야하는지 다룬다. 운전할 때와 마찬가지로 코딩도 반복적인 일이지만 정신을 늘 기민하게 유지하여야한다.

  • topic 37 : 본능과 무의식적인 생각을 더 잘 활용할 수 있다
  • topic 38 : 우연에 맡기는 프로그래밍을 피하는 방법
  • topic 39 : 알고리즘의 속도
  • topic 40 : 리팩터링
  • topic 41 : 테스트로 코딩하기
  • topic 42 : 속성 기반 테스트를 통해 고정관념에서 벗어난 테스트하기
  • topic 43 : 읽고 분석하기 쉬운 코드를 쓰는 법
  • topic 44 : 이름 짓기

Topic 37 : 파충류의 뇌에 귀 기울이기

경험이 늘어날수록 무의식적인 지식이 쌓여져가고, 본능적으로 싸늘한 느낌을 느낄 때가 있다. 이럴 때는 그런 반응을 인지하고, 왜 그런 느낌이 드는지를 알아내야한다. 여기서는 그런 느낌이 드는 몇가지 흔한 상황을 살펴보자.

백지의 공포

어떤 일을 시작할때 두려움을 느끼는 경우가 많다. 이 원인은 두가지가 있다.

  1. 무의식적으로 어떤 작업을 시작할때 문제점이 있을거 같다고 본능이 말해주는 경우이다. 이때는 시간을 주어 생각해보면 무엇이 문제인지 알수 있다.
  2. 단순히 실수할까봐 두려운 걸 수도 있다.

자신과 싸우기

어떤 날은 코드를 짜는 것이 매우 힘들 수 있다. 이때는 억지로 계속 작성하지말고, 무언가 문제가 있는지 생각해보자. 구조나 설계가 틀렸는가? 엉뚱한 문제가 아닐까?

파충류와 이야기하는 법

여러분 내면의 파충류에게 귀 기울여라

일단 하고 있는 일을 멈춰라. 약간의 시간을 두고 뇌가 정리를 할 수 있게 해라. 밥을 먹거나 산책을 해도 된다. 그러면 해결책이 떠오를 때가 있다.

만약 이래도 안되면 문제를 표면으로 끄집어 내라. 작성하는 코드에 대해 그림을 그려보거나 동료에게 설명해봐라. 고무오리도 좋다.

이렇게하여도 해결이 안된다면 뇌에게 우리가 하는 일이 문제가 없다는 걸 알려줘야한다. 프로토타이핑의 시간이다.

  1. 프로토타이핑 중이라고 포스트잇에 써라
  2. 기존 코드가 방해된다면 잠시 치워두어라
  3. 프로토타이핑은 원래 실패한다고 상기하고, 성공하더라도 원래 버리는 것이라고 생각하라.
  4. 텅 빈 에디터 화면에 하고자하는 것을 주석으로 적어라
  5. 코딩을 시작하라

이렇게하면 불안감은 사라지고 빨리 해치우고 싶다는 생각이 들 것이다. 그래도 안된다면 다시 첫단계(휴식)부터 시작하라.

여러분의 코드뿐이 아니다.

다른 사람의 코드를 읽으면서 중요해보이는 곳에 메모를 하고, 실험을 할 수 있다. 이상한 부분이 있다면 적어놓고 패턴을 찾아보아라. 만약 그렇게 코드를 작성한 이유를 찾는다면 코드를 이해하기가 훨씬 쉬워진다. 다른 사람의 패턴을 여러분이 사용할 수도 있다.

코드뿐이 아니다.

설계, 요구사항 등 코드 외적인 것에서도 직감이 발동할 수 있다. 이 직감에 귀를 기울이고 적극적으로 생각하라


Topic 38 : 우연에 맡기는 프로그래밍

우연에 맡기는 프로그래밍 하기

몇번 돌려보고 문제가 없는거 같아 코딩을 계속하다보면 어느순간 문제가 생기는 경우가 있다. 이경우 원인을 파악하기 힘든데, 왜 코드가 잘 돌아갔는지 몰랐기 때문이다. 이처럼 우연에 맡겨 코딩을 하는 경우가 있다

  • 구현에서 생기는 우연
    • 우연히 현재 존재하는 에러나 예외적인 경우에 의존하여 코드를 짜는 경우가 있다
      • ex) 외부 라이브러리를 사용하는데 문서화되지않은, 의도하지 않은 동작을 사용. 다음 버전에서 고쳐질 수 있음.
    • 다른 루틴을 호출할 때는 문서화된 동작에만 의존하라. 그럴 수 없다면 추측을 문서로 상세히 남겨라
  • 비슷하다고 괜찮을 리는 없다
    • 항상 시간이 1시간 차이나는 경우, 모든 개발자가 각자 시간을 다룰때 1시간을 더하거나 빼는 식으로 코딩을 할 수 있다. 하지만 이런 코드가 퍼져나가면 더이상 수습이 어렵다
  • 유령 패턴
    • 우연히 발생한 패턴을 기반으로 코드를 짤 수 있다.
    • 가정하지말고 증명하라
  • 상황에서 생기는 우연
    • 현재 환경에서 우연히 잘 동작하는 코드일 수 있다. ex) 네트워크 환경이 안좋은 경우, 서버의 시간이 부정확한 경우
    • 우연에 맡기는 프로그래밍을 하지 말라
  • 암묵적인 가정
    • 가정을 했으면 이를 문서화하라. 가정은 개발자마다 다르며 사실에 근거하지 않은 가정은 재앙의 근원이 된다.

의도적으로 프로그래밍하기

  • 언제나 지금 무엇을 하고 있는지 의식하라
  • 경험이 적은 프로그래머에게 코드를 설명할 수 있는가?
  • 자신도 잘 모르는 코드를 만들지 말라
  • 간단하게라도 계획을 세워라
  • 가정에 의존하지 마라.
  • 가정을 기록으로 남겨라
  • 가정을 테스트하라.
  • 우선순위를 정하라
  • 기존 코드가 앞으로 짤 코드를 지배하도록 하지 마라.

Topic 39 : 알고리즘의 속도

실전에서의 알고리즘 속도

입력값이 얼마나 큰 값이 들어올지 스스로 물어봐야한다. 숫자가 외부 요인에 따라 달라지면, 시간/메모리를 다시 계산하라

입력 데이터가 작을땐 선형적이어도, 크면 스래싱이 발생하면서 수행시간이 폭증할 수 있다. 이론적으로 추정을 이미 했더라도 실무적으로 문제가 생길 수 있다

여러분의 추정을 테스트하라

하지만 언제나 가장 빠른 알고리즘이 최고가 아니다. 복잡한 알고리즘을 사용한만큼 디버깅, 유지보수의 시간이 늘어날 수 있다.

성급한 최적화를 조심하고, 그 알고리즘이 정말로 병목인지 먼저 확인하라


Topic 40 : 리팩터링

밖으로 드러나는 동작은 그대로 유지한 채 내부 구조를 변경함으로써 이미 존재하는 코드를 재구성하는 체계적 기법

  • 마틴 파울러

리팩터링은 언제 하는가?

무언가를 더 많이 알게 되었을때, 더이상 코드가 잘 맞지 않을때, 무엇이든 잘못되었다는 생각이 들 때 수행한다.

  • 중복
  • 직교적이지 않은 설계
  • 더이상 유효하지 않은 지식
  • 사용 사례
  • 성능
  • 테스트 통과 : 테스트가 통과하여 이상 없음을 발견하였을때 코드를 정리하기 좋은 타이밍이다.

현실적인 문제

일정의 압박은 리팩터링을 하지 않는 변명이 될 수 있다. 하지만 리팩터링을 하지 않으면 추후 더 큰 문제가 될 수 있다. (마치 종양처럼)

일찍 리팩터링하고, 자주 리팩터링하라

문제가 작을때 코딩하는 동안 함께 진행하는 편이 더 쉽다.

리팩터링은 어떻게 하는가?

리팩터링의 본질은 재설계이다. 하지만 거대한 코드를 헤집어 놓으면 더 안좋은 상황이 생길 수 있다. 리팩터링은 천천히 신중하게 진행해야하는 작업이다. 관련하여 마틴 파울러의 조언이 몇가지 있다

  1. 리팩터링과 기능 추가를 동시에 하지 말라
  2. 리팩터링을 하기 전에 든든한 테스트가 있는지 확인하라. 가능한 자주 테스트를 돌려라.
  3. 단계를 작게 나누어서 신중하게 작업하라. 한 단계가 끝날때마다 테스트를 돌리는 것이 좋다.

Topic 41 : 테스트로 코딩하기

테스트는 버그를 찾기 위한 것이 아니다

테스트의 이득은 테스트를 실행할 때가 아니라, 테스트를 생각해내고 작성할 때 생긴다

테스트가 코딩을 주도한다

테스트 작성에 대해 생각하는 것은 코드의 작성자가 아니라 사용자인 것처럼 메서드를 외부의 시선에서 볼 수 있게 해준다.

테스트가 코드의 첫번째 사용자다

다른 코드와 긴밀하게 결합된 메서드는 테스트하기가 어렵다. 메서드를 실행하기도 전에 환경 구성을 한참해야하기 때문이다. 따라서 테스트하기 좋게 만들면 결합도도 내려간다.

또 무언가를 테스트하려면 그것을 이해해야한다. 코딩을 시작하기 전에 경계조건 테스트를 생각한다면 함수를 단순하게 만드는 패턴을 찾을 수 있을 것이다. 테스트해야하는 오류조건을 먼저 생각하면 그에 맞게 함수구조를 잡을 것이다

테스트 주도 개발

  1. 추가하고 싶은 작은 기능을 하나 결정한다
  2. 그 기능이 구현되었을때 통과할 테스트를 하나 작성한다
  3. 테스트를 실행한다. 방금 추가한 테스트는 실패해야한다.
  4. 실패하는 테스트를 통과하는 최소한의 코드를 작성한다. 이후 테스트가 전부 통과하는지 확인한다
  5. 코드를 리팩터링한다. 방금 작성한 테스트나 함수를 개선할 수 있을지 살펴본다. 개선 후에도 잘 테스트를 통과하는지 확인한다.

TDD는 이를 매우 짧은 주기로 반복하여 끊임없는 테스트 작성과 통과를 반복한다. 이는 테스트를 막 배운 사람에게는 효과가 크다. 하지만 이 TDD의 노예가 된 사람들이 있다.

  • 테스트 커버리지 100% 채우기위해 과도한 시간을 투자한다
  • 많은 수의 중복 테스트가 생긴다.
  • 밑에서부터 시작하여 위로 올라가는 방식으로 설계를 한다.

TDD를 실천하되, 너무 중독되지는 말자

TDD : 목표가 어디인지 알아야한다

TDD는 전체 문제를 작은 단계로 나누어 접근하도록 해주지만, 반대로 전체 문제를 보지 않고 작은 문제에 매몰 되게도 할 수 있다.

테스트를 진행할 때마다 목적지를 항상 떠올려야한다

단위테스트

소프트웨어를 컴포넌트로 나누어 조립하여 구성하는 방식은 자주 쓰인다. 하지만 이때 각 컴포넌트는 연결되기 전에 철저하게 테스트 되어야한다.

이때 각 컴포넌트는 단위 테스트를 거칠 수 있다. 단위 테스트를 통과한다면 조립하더라도 각 개별 부분은 정상 동작한다고 믿을 수 있다.

이때 단위테스트로 계약을 지키는지, 코드로 표현된 계약이 우리가 생각한 것과 일치하는지 확인할 수 있다. 다양한 종류의 케이스와 경계조건 케이스를 가지고 테스트 해볼 수 있다.

임시 테스트

임시테스트는 console.log() 나 디버거 등을 직접 실행해보면서 입력하는 코드 조각일 수 있다. 디버깅이 종료되면 이러한 임시테스트는 정식 테스트의 형태로 만들어 놔야한다.

테스트 접점 만들기

배포한 후에도 테스트할 일이 자주 생긴다. 이때 디버깅을 쉽게하기 위해 다음 방법을 사용할 수 있다

  • 로그 : 일관적이고 파싱하기 쉬워야한다
  • 단축키 조합 : 특정 단축키, URL로 접근 시 디버거 창이 뜨도록 만들 수 있음
  • 기능 스위치 : 특정 사용자에게는 더 많은 진단 메세지를 남기기

테스트 문화

우리가 테스트 안하면 사용자가 테스트하게 된다. 선택지는 세개다

  • 테스트 먼저 : 최상의 선택으로, TDD가 포함된다.
  • 테스트와 코드를 함께 : 테스트를 먼저 쓰기가 어려울 경우 사용가능하다.
  • 테스트하지 않음

제대로 된 테스트 문화를 가졌다면 모든 테스트가 항상 통과해야한다. 실패해도 무시하는 테스트가 하나 생기면 점점 더 많이 무시하게 된다


Topic 42 : 속성 기반 테스트

우리가 작성한 코드를 스스로 테스트하다보면, 잘못된 가정하에서 테스트를 할 수 있다. 하지만 그렇다고 다른 사람에게 테스트를 맡기면, 테스트를 생각할때 나오는 이점이 없어진다.

이 문제를 해결하기 위해서는 컴퓨너에게 테스트를 맡기는 방법이 있다.

계약, 불변식, 속성

속성 = 계약 + 불변식

  • 계약 : 선행조건에 맞추어 입력을 넣으면, 출력이 후행 조건에 맞음을 보장해줌
  • 불변식 : 함수 실행 전후로 계속 참이 되는 조건 (ex : 정렬 함수에서 리스트의 원소의 개수는 항상 같음)

코드에서 속성을 찾아내서 테스트 자동화에 사용할 수 있는데, 이걸 속성 기반 테스트라고 한다.

속성 기반 테스트로 가정을 검증하라.

파이썬에서는 Hypothesis 라이브러리와 pytest로 이런 테스트가 가능하다.

테스트 데이터 생성

대부분의 속성 기반 테스트에서는 데이터를 어떻게 생성할지 지정하는 작은 언어가 있다

1
+2
+
@given(some.integers())
+@given(some.integers(min_value=5, max_value=10).map(lambda x: x * 2))
+

속성 기반 테스트는 우리를 자주 놀래킨다

속성 기반 테스트는 입력을 생성하는 규칙과 출력을 검증하는 단정문만 설정한 채 알아서 동작한다. 따라서 정확히 어떤 일이 일어날지 모른다.

만약 속성 기반 테스트가 실패했다면 테스트 함수가 어떤 매개변수를 사용했는지 알아낸다음 그 값을 이용하여 단위테스트를 추가하는 것이 좋다.

  • 속성 기반 테스트의 여러가지 다른 수행결과와 상관없이 문제가 발생하는 상황에 집중할 수 있게 해준다.
  • 속성 기반 테스트는 임의의 값을 생성하기에 다음번에 해당 값이 생성될 보장은 없기 때문에.

속성 기반 테스트는 설계에도 도움을 준다

단위 테스트는 메서드의 첫번째 사용자로서 코드에 대해 생각하게 해준다.

속성 기반 테스트는 코드를 불변식과 계약이라는 관점으로 바라보게 해준다. 이로써 경계조건은 줄어들고 데이터의 일관성을 해치는 함수는 도드라진다.

둘은 서로를 보완하며 서로 다른 문제를 해결해준다.


Topic 43 : 바깥에서는 안전에 주의하라

나머지 90%

코드를 완성한 이후 코드가 잘못될 수 있는 경우를 찾아봐라. 그리고 각 경우에 대한 단위 테스트를 추가해야한다

  • 잘못된 매개변수를 넘기는 경우
  • 리소스를 흘리거나, 리소스가 모자라는 경우

하지만 이렇게 내부 오류를 찾는것만으로는 불충분하다. 외부의 공격에도 대비해야한다

기본 보안 원칙

  1. 공격 표면을 최소화하라
    1. 공격자가 데이터를 입력/추출할 수 있거나, 서비스를 실행시킬 수 있는 모든 접근지점
    2. 코드가 복잡할수록 공격지점은 많아진다. 코드는 단순할수록 좋다
    3. 입력데이터를 신뢰하지마라
    4. 인증없는 서비스는 공격 매개체다
    5. 인증을 요구하는 서비스도 공격 매개체다.
      1. 인증 받은 사용자수를 최소로 유지하고, 유효하지 않은 유저, 서비스를 정리하라
    6. 출력데이터도 공격 매개체다. (ex : 오류메세지로 ‘다른 사용자가 사용중인 비밀번호’)
      1. 응답에 들어있는 데이터가 사용자의 권한에 적절한지 늘 확인하라.
    7. 디버깅 정보는 공격 매개체다
  2. 최소 권한 원칙
    1. 최소한의 권한을 꼭 필요한 시간만큼만 제일 짧게 부여하라
  3. 안전한 기본값
  4. 민감 정보를 암호화하라
  5. 보안 업데이트를 신속히 적용하라 

    상식 대 암호

암호학은 상식과 맞지 않을 수 있다.

암호화의 첫번째이자 가장 중요한 규칙은 절대 직접 만들지말라는 것이다. 암호의 세계에서는 사소한 오류가 전체 암호화를 무용지물로 만들 수 있다. 이미 만들어진 프레임워크를 사용하라. (자주 업데이트 되는)

로그인과 같은 인증서비스도 외부 서비스를 사용하면 외부에서 대신 고민해준다. 따라서 가능하면 해당 서비스를 사용하는 것이 좋을 수 있다


Topic 44 : 이름짓기

코드에서 하는 역할에 따라 이름을 지어야한다.

예시 1 : 상품 고객 변수 이름을 user 대신 customerbuyer로 하면 무언가를 구매하려는 사람으로 인식할 수 있게 된다.

예시 2 : 할인을 적용하는 메서드

1
+
public void deductPercent(double amount)
+

이 코드에는 아래 질문점이 생긴다

  • 왜 이 일을 하는가?
  • amount는 할인금액인가 할인율인가?

아래처럼 고칠 수 있다.

1
+
public void applyDiscount(Percentage discount)
+

문화를 존중하라

반복문의 i, j 는 사용하지말라는 얘기가 많지만 C 프로그래밍에서는 많이 사용되는 변수명이다. 물론 해당 관습이 없는 곳에서는 사용하지말라

camelCase나 snake_case도 해당 분야의 문화를 존중해야한다.

일관성

팀의 모든 사람이 각 단어의 뜻을 알고 일관성 있게 사용해야한다. 팀에서 특별한 의미가 있는 단어를 모은 사전을 만들 수도 있다.

이름 바꾸기는 더 어렵다

코드는 계속 변화하며 의미는 계속 변경된다. 그에 맞춰 부지런히 이름을 변경하지 않으면 잘못된 이름이 계속 사용될 수 있다.


후기

이번 장에서는 일반적으로 코딩을 하면서 발생할 수 있는 위험 신호(?)를 어떻게 잘 감지하고, 잘 활용할 수 있는지에 대해 다루었다

  • topic 37 : 파충류의 뇌에 귀 기울이기
    • 코딩을 하면서 무의식적으로 싸늘한 느낌이 들때 그 신호를 무시하지말고, 어떤 점에서 그 느낌이 드는지 생각해보고 테스트해봐라
    • 이유를 잘 모르겠으면 산책을 하거나, 고무오리 기법을 사용할 수도 있다.
  • Topic 38 : 우연에 맡기는 프로그래밍
    • 우연히 당시에만 잘 돌아가는 코드를 짤 수도 있다.
    • 어떤 가정을 할 때, 그 가정이 합당한지 확인하라. 다른 사람에게 설명할때 합리적인지 생각하라
  • Topic 39 : 알고리즘의 속도
    • 이론적 속도와 실제 환경에서의 속도가 다를 수 있다.
    • 성급한 최적화는 조심하되, 그 모듈이 정말 병목이라면 테스트해봐라
  • Topic 40 : 리팩터링
    • 최대한 일찍, 자주 리팩터링을 진행하라.
    • 리팩터링을 할때는 단계를 작게 나누고, 기능추가를 하지말라.
    • 든든한 테스트가 있다면 리팩터링을 할때 큰 도움이 된다
  • Topic 41 : 테스트로 코딩하기
    • 테스트의 가장 큰 이득은 테스트를 생각하고 작성할 때 생긴다.
    • 테스트는 메서드의 첫번째 사용자이다.
    • 테스트 하기 쉬운 코드를 작성하다보면 결합도가 낮은 코드가 만들어진다.
    • TDD는 좋지만 너무 중독되지는 말자. 항상 큰 문제를 우선하여 생각하라
    • 실패하는 테스트를 방치하지 말라. 실패해도 무시하는 테스트가 생기면 점점 많아진다.
  • Topic 42 : 속성 기반 테스트
    • (속성 = 계약 + 불변식)을 테스트하는 것으로, 어떤 가정을 테스트하기에 좋은 방식으로 보인다
  • Topic 43 : 바깥에서는 안전에 주의하라
    • 보안과 관련된 것은 직접 만들지말고 가능한 이미 검증된 서비스를 사용하라
    • 기본 보안 원칙을 지켜라
      • 공격표면 최소화, 최소권한원칙, 안전한 기본값, 민감정보 암호화, 보안업데이트
  • Topic 44 : 이름짓기
    • 코드에서 하는 역할에 따라 이름을 지어라.
    • 팀의 모든 사람이 같은 단어를 같은 뜻으로 사용하는 것이 좋다.
This post is licensed under CC BY 4.0 by the author.

실용주의프로그래머 6장 동시성

잘못된 DRY

Comments powered by Disqus.

diff --git "a/posts/\354\213\244\354\232\251\354\243\274\354\235\230\355\224\204\353\241\234\352\267\270\353\236\230\353\250\270-8\354\236\245-\355\224\204\353\241\234\354\240\235\355\212\270-\354\240\204\354\227\220/index.html" "b/posts/\354\213\244\354\232\251\354\243\274\354\235\230\355\224\204\353\241\234\352\267\270\353\236\230\353\250\270-8\354\236\245-\355\224\204\353\241\234\354\240\235\355\212\270-\354\240\204\354\227\220/index.html" new file mode 100644 index 000000000..c5a6c3922 --- /dev/null +++ "b/posts/\354\213\244\354\232\251\354\243\274\354\235\230\355\224\204\353\241\234\352\267\270\353\236\230\353\250\270-8\354\236\245-\355\224\204\353\241\234\354\240\235\355\212\270-\354\240\204\354\227\220/index.html" @@ -0,0 +1 @@ + 실용주의프로그래머 8장 프로젝트 전에 | 디피의 개발일지
Posts 실용주의프로그래머 8장 프로젝트 전에
Post
Cancel

실용주의프로그래머 8장 프로젝트 전에

Topic 45 : 요구사항의 구렁텅이

자신이 뭘 원하는지 정확히 아는 사람은 아무도 없다.

요구사항 미신

무엇을 다루든 정확한 명세란 것은 거의 불가능하다. 이때 프로그래머는 사람들이 자신이 무엇을 원하는지 깨닫도록 도와줄수 있다.

프로그래머는 사람들이 자신이 원하는 바를 꺠닫도록 돕는다.

상담 치료로서의 프로그래밍

신입 개발자의 실수는 요구사항을 받고 바로 해결책을 구현하는 것이다.

어떤 요구사항을 받았을때 여러 경우를 생각하고 되물어 확인하자 (사소하여 짜증날거 같은 질문이라해도)

  • 요구사항: 5만 원 이상인 모든 주문은 배송비가 무료이다
  • 질문 : 어떤 종류의 배송비가 무료인지? 국제 배송은? 5만원에 배송비 포함인지? 세금 포함인지? 나중에 기준이 바뀔지?

우리의 역할은 기획자의 말을 해석해서 그로 인한 영향을 다시 알려주는 것이다.

요구사항은 과정이다

요구사항은 피드백을 반복하며 알게 된다.

기획자는 우리의 피드백을 받고 자기 생각을 가다듬는다. 이때 피드백을 말로 표현하기 어려울 수도 있다. 그때는 프로토타입이나 목업으로 대화를 진행할 수 있다.

이러한 요구사항 가다듬기는 프로젝트가 끝날때까지도 이어진다. 따라서 프로젝트 전체를 요구사항 수집 단계로 봐야하고, 짧은 주기로 반복하여 피드백을 자주 받는 것이 좋다.

요구사항 문서화

요구사항 문서는 의뢰인을 위한 것이 아니다. 거대한 요구사항 문서를 만들어도 의뢰인은 읽지 않을 것이다. 요구사항 문서는 개발자를 위해서 세부사항을 명시한 것이다.

요구사항 문서화는 계획을 위해서도 좋다. 짧은 설명을 인덱스 카드 형식으로 하여 상태에따라 이리저리 옮길 수 있게 할 수 있다. 설명이 부족할 수도 있는데, 이것을 이용해서 개발자로 하여금 의뢰인에게 질문을 던지게 할 수 있다

지나치게 자세한 명세

요구사항을 만들 때는 추상적으로 작성하는 것이 좋다. 모호하게 적으라는 것이 아니라, 의미론적 불변식을 요구사항으로 잘 갈무리하고, 구체적인 작업방식은 정책으로 문서화해야한다.

요구사항은 아키텍처나 설계가 아니며 필요를 표현하는 것이다

딱 하나만 더

요구사항이 조금씩 늘어나는 것을 막는 방법은 피드백이다. 정기적으로 피드백을 주고받아 요구사항이 늘어날 떄의 영향을 의뢰인이 체험할 수 있도록 하라. 피드백을 주고 받아 추가되는 요구사항을 위해 무엇을 희생 가능할지 함께 고민할 수 있다

용어사전 관리하기

프로젝트에서 사용하는 용어를 모은 사전을 만들어라. 최종 사용자에서 말단 직원까지 프로젝트에 참가하는 모든 사람이 동일한 용어를 사용해야한다.


Topic 46 : 불가능한 퍼즐 풀기

풀기 어려운 문제는 상상 속이 아닌 실제 제약 조건을 알아내고 그 속에서 해법을 찾아야한다. 어떤 제약 조건은 절대적이라 따라야하지만, 어떤 조건은 단순한 지레짐작이다.

자유도

생각의 틀을 벗어나지 말고, 틀을 찾아라

모든 선입견을 의심하고 그것이 진짜 바꿀 수 없는 제약인지 가늠해봐야한다. 풀리지 않는 문제를 마주하면 모든 해결책을 생각해봐라. 아무리 쓸모없고 바보 같더라도 버리지 말고 잘 모아두고 왜 쓸모없다고 생각했는지 설명해봐라

자신만의 방법에서 빠져나와라

어려운 문제를 마주하고 그것이 풀리지 않는다면 잠시 다른 일을 하라. 무의식적으로 문제가 해결될 수도 있다.

간단히 표현하면 딴짓을 한 사람이 의식적으로 노력한 사람보다 복잡한 해결과제를 더 잘 해냈다.

  • 사이콜로지 투데이

만약 손을 놓기 싫다면 다른 사람과 이야기 해봐라

  • 왜 이 문제를 풀고 있는가?
  • 문제를 풀어서 얻는 것은 무엇인가?
  • 풀려고 하는 문제가 특수한 경우인가? 특수한 경우를 없앨 수는 없나?
  • 관련된 문제 중 풀 수 있는 더 간단한 문제는 없나?

행운은 준비된 사람에게 찾아온다.

무의식적으로 문제를 해결하기 위해서는 원료가 많아야한다. 엔지니어링 일지처럼 일상적인 작업을 할 때 무엇은 잘되고 무엇은 안되는지 피드백을 줄 수 있다.


Topic 47 : 함께 일하기

페어 프로그래밍

사람들은 모두 다르기에 다른 배경을 가지고 문제를 푸는데도 다른 접근방법을 사용한다. 주어진 문제에 집중하는 정도나 주의력도 제각각이다. 입력을 담당한 개발자는 문법이나 코딩 스타일에 집중할 수 있고, 다른 개발자는 문제를 더 높은 수준에서 생각할 수 있다.

뇌의 용량은 한정되어있고, 문법이나 코딩스타일에 맞추는 일은 뇌 용량을 많이 소비한다. 두번째 사람이 전체적인 문제를 생각하는 뇌를 맡는다. 또 두번째 사람의 존재로 인해 안좋은 습관이나 꼼수를 덜 쓰게 된다

몹 프로그래밍

페어프로그래밍을 넘어 더 많은 사람이 참가할 수도 있다. 이때는 의뢰인처럼 개발팀의 일원이 아닌 사람도 참가할 수 있다

무엇을 해야할까?

처음에는 어색할 수 있지만 조금씩 시도해볼 수 있다. 이렇게 일하는 동안에는 기술적인 면 외에 인간적인 측면도 신경써야한다.

  • 누가 똑똑한지 겨루지마라. 각자 뛰어난 부분이 있다
  • 소규모로 시작해라
  • 코드만 비판하고 사람을 비판하지 마라. ‘넌 틀렸어’보다 ‘이 부분을 한 번 볼까요?’가 좋다
  • 다른 사람의 관점을 듣고 이해하려고 노력하라.
  • 자주 회고를 하라.

너무 무턱대고 시도하지는 말고, 사례 보고서를 찾아보고 연구하라. 장점과 걱정되는 점에 대해 감을 잡아라.

코드에 혼자 들어가지는 말라.


Topic 48 : 애자일의 핵심

애자일은 명사가 아니다. 애자일은 무언가를 하는 방식이다.

애자일은 일하는 방식이지 우리 자신이 아니다. 애자일 선언에서 언급된 애자일의 가치는 아래와 같다.

우리는 소프트웨어를 개발하고, 또 다른 사람의 개발을 도와주면서 소프트웨어 개발의 더 나은 방법들을 찾아가고 있다. 이 과정에서 우리는 다음과 같은 가치들을 찾아냈다.

  • 공정과 도구보다 개인과 상호작용
  • 포괄적인 문서보다 작동하는 소프트웨어
  • 계약 협상보다 고객과의 협력
  • 계획을 따르기보다 변화에 대응하기

왼쪽도 가치가 있지만 오른쪽에 더 높은 가치를 둔다

애자일 선언은 고정불변의 문서라기보다는 만들어가는 과정에 대한 제안이다.

애자일 프로세스라는 것은 있을 수 없다

애자일, 즉 기민함은 변화에 대응하는 것이다. 소프트웨어를 개발할 때 따라야하는 단 한 가지 계획이란 없다. 애자인 선언의 네가지 가치중 세가지가 이처럼 피드백을 수집하고 그에 대응하라는 얘기다.

이 가치들은 무엇을 하라는 것이 아니라, 할일을 결정할 때 무엇을 추구할지 알려주는 것이다. 그 결정은 상황에 따라 다르다.

피드백 고리

피드백고리를 만들어라. 이름 짓기 같은 작은 고리라도 피드백고리를 반복하는 것이 중요하다. 매번 작은 단계를 진행하고, 평가하고, 고칠 것이 있으면 고쳐라

이 과정이 설계를 주도한다. 좋은 설계는 바꾸기 쉬운 결과물을 만들기 때문에 애자일이 잘 작동하려면 좋은 설계를 만들어야한다.


후기

프로젝트 전, 진행 중에 필요한 팁들을 알려준 장이다.

  • Topic 45 : 요구사항이라는 것은 기획자가 단순히 던져주는 것이 아니라, 개발자가 함께 만들어가는 것이라고 알게 해주었다.
    • 요구사항을 받았으면 바로 구현하기보단, 요구사항을 구현했을때의 영향을 생각하여 되물을 수 있다
    • 요구사항은 피드백을 반복하며 구체화된다
  • Topic 46 : 풀기 어려운 문제를 맞이하였을 때, 어떤 부분에서 막히는지 확인하라. 그리고 그 부분이 진짜로 제약조건인지 확인하라
    • 문제의 해결책을 의논할 때는 바보 같더라도 모두 모아라. 선입견에 빠지지말고 왜 바보같은지 합리적인 이유가 있는지 확인하라
  • Topic 47 : 페어프로그래밍, 몹 프로그래밍을 통해 코드를 작성하는 사람과 전체를 보는 사람의 뇌를 분리시켜 코드의 품질을 올릴 수 있다
  • Topic 48 : 에자일 방법론 형식에 매몰되지 말라. 피드백 고리를 만들고 기민하게 변화하는 것이 에자일의 가치이다.
This post is licensed under CC BY 4.0 by the author.

잘못된 DRY

실용주의프로그래머 9장 실용주의 프로젝트

Comments powered by Disqus.

diff --git "a/posts/\354\213\244\354\232\251\354\243\274\354\235\230\355\224\204\353\241\234\352\267\270\353\236\230\353\250\270-9\354\236\245-\354\213\244\354\232\251\354\243\274\354\235\230-\355\224\204\353\241\234\354\240\235\355\212\270/index.html" "b/posts/\354\213\244\354\232\251\354\243\274\354\235\230\355\224\204\353\241\234\352\267\270\353\236\230\353\250\270-9\354\236\245-\354\213\244\354\232\251\354\243\274\354\235\230-\355\224\204\353\241\234\354\240\235\355\212\270/index.html" new file mode 100644 index 000000000..05ef5da5b --- /dev/null +++ "b/posts/\354\213\244\354\232\251\354\243\274\354\235\230\355\224\204\353\241\234\352\267\270\353\236\230\353\250\270-9\354\236\245-\354\213\244\354\232\251\354\243\274\354\235\230-\355\224\204\353\241\234\354\240\235\355\212\270/index.html" @@ -0,0 +1 @@ + 실용주의프로그래머 9장 실용주의 프로젝트 | 디피의 개발일지
Posts 실용주의프로그래머 9장 실용주의 프로젝트
Post
Cancel

실용주의프로그래머 9장 실용주의 프로젝트

  • Topic 49 : 프로젝트 참여인원이 복수가 되었을때 실용적으로 각 부분을 위임하는 법
  • Topic 50 : 팀에게 잘 맞는 방법론을 사용하고 있는가
  • Topic 51 : 안정적인 소프트웨어를 지속적으로 생산해내는 방법
  • Topic 52 : 사용자를 기쁘게하는 방법
  • Topic 53 : 자신의 작업에 자부심을 가져라

Topic 49 : 실용주의 팀

실용주의 팀은 작아야한다. 구성원은 대략 10~12명이고, 구성의 변경이 적어야한다. 서로를 신뢰하고 의존할 수 있어야한다.

작고 안정적인 팀을 유지하라

원하는 바를 실현할 수 있는 환경에 실용주의 개발자 그룹이 만들어진다면, 자신들에게 맞는 역학을 금방 만들고 또 다듬어 낼 것이다

깨진 창문을 없애라

자질구레하게 계속되는 문제를 고치는데 열정을 유지하기 힘들다

팀 전체가 깨진 창문을 용납해서는 안된다. 반드시 제품의 품질에 책임을 져야한다.

삶은 개구리

팀은 개인보다 더 삶은 개구리가 되기 쉽다. 모든 사람이 적극적으로 환경 변화를 감시하도록 권장하라. 범위의 확장, 일정 단축, 추가기능 등 기존에 인지하던 것과 다른 것들을 늘 깨어서 의식해야한다.

변화를 거부하지 않아도 된다. 단지 그 변화를 인식하고는 있어라

여러분의 지식 포트폴리오를 계획하라.

성공을 원하는 팀이면 자신들의 지식과 기술에 투자해야한다. 이때는 계획을 세워서 해야하는데, 시간이 남으면 하겠다는 안하겠다는 말과 같다. 계획을 세울 때 기능개발로만 몽땅 채우지말라. 대신에 아래 것들을 고려하라

  • 구형 시스템 유지보수
  • 프로세스 회고와 개선
  • 새로운 기술 탐험 : 다들 쓰니까 쓴다는 이유보단 팀에 잘 맞는 것이 뭘지 조사하라
  • 학습 및 기술 갈고 닦기

팀의 존재를 소통하라

팀이라는 것 역시 더 큰 조직의 일부이다. 외부사람들에게 과묵한 프로젝트팀이야말로 최악이다. 나머지 팀들과 의사소통하라.

반복하지 말라

팀원 간의 중복된 일을 없애기는 어렵다. 좋은 의사소통을 통해 이 문제를 피할 수 있다. 팀 동료에게 질문을 하고 거의 즉각적으로 답을 받을 수 있어야한다. 질문을 하기위해 회의를 기다려야하면 의사소통이 어려워진다.

DRY를 지키려면 서로 관심을 유지하라

팀 예광탄

시스템의 끝에서 끝까지 전체에 걸쳐있는 단일 기능을 개발하라. 이 작업을 맡은 모든 구성원이 함께 일하는 것이 편안하고 익숙해야한다.

자동화

일상적으로 반복되는 일을 자동화하라. 수작업으로 하는 것보다 훨씬 일관적이고 정확하다

멈춰야 할 때를 알라

각 팀원이 자신의 방식대로 빛나게 하라. 프로젝트의 가치를 만들어 내기에 딱 좋을 만큼의 구조를 제공하라


Topic 50 : 코코넛만으로는 부족하다

화물 숭배에 빠지지 말라

맥락의 중요성

특정한 개발 방법이나 프레임워크를 굳이 사용하는 이유가 무엇인가? 유명해서인가? 아니면 현재 프로젝트 또는 팀에 잘 맞아서인가?

유행하는 것이 아니라 실제로 잘 맞는 것을 사용하라

유명한 팀들이 성공한 방식을 따라하기보다는 맥락을 보고 잘 맞는 것을 사용하라.

잘 맞는 방법론이더라도 다 정확히 따라야할 필요는 없다. 좋은 것만 취하고 안맞는 것은 버려라

진짜 목표

진짜 목표는 “에자일을 한다”, “린을 한다” 가 아니라 사용자에게 작동하는 소프트웨어를 제공하는 것이다.

CICD가 불가능한 목표라고 생각하는 팀은 많다. 하지만 모든 목표가 그렇듯 계속 올바른 방향을 바라보는 것이 중요하다. 제품 출시 주기를 조금씩 줄여나가라. 최종적으로는 필요할때마다, 사업적으로 의미 있을때마다 배포하는 것이 목표이다.

사용자에게 필요할 때 제공하라

이런 지속적 개발을 위해선 feature 브랜치가 아니라 main 또는 trunk 브랜치에서 개발해야한다. 그리고 사용자에게 시범적 기능을 제공할 때는 ‘기능 스위치’ 같은 기법을 활용하라


Topic 51 : 실용주의 시작 도구

생각없이 행할 수 있는 중요한 작업의 수가 늘어남에 따라 문명은 발전한다

  • 엘프리드 노스 화이트헤드

프로젝트에서 거듭 발생하는 일상적인 작업은 모두 자동화해야한다. 수작업은 운에 맡기는 것이고, 반복 가능성도 보장 받지 못하다. 그 작업의 절차를 여러 사람이 서로 다르게 해석할 여지가 있으면 더욱 그렇다

여기에는 아래와 같은 프로젝트를 견고하게 만들어주는 도구들이 있다.

  • 버전관리
    • 프로젝트를 빌드하는데 필요한 모든 것을 버전 관리하에 두어야한다. 이렇게하면 빌드 장비를 일시적으로 쓰고 버릴 수 있다.
    • 배포 설정도 버전관리 하에 있으므로 서비스 릴리즈도 자동으로 처리된다.
    • 버전 관리 시스템으로 빌드, 테스트, 릴리스를 운용하라.
  • 회귀 테스트
    • 일찍, 자주, 자동으로 테스트하라.
    • 테스트는 코드가 완성되었다는 확신을 줄 수도 있다.
    • 테스트 환경은 실제 환경과 최대한 비슷하게 설정해야한다. 두 환경의 차이에서 버그가 생긴다.
    • 테스트의 몇가지 주요 유형
      • 단위테스트
      • 통합테스트 : 시스템에서 버그가 제일 많이 나오는 것이 모듈을 통합하는 부분이다.
      • 유효성 평가 및 검증 : 시스템의 기능적 요구사항을 충족하는지 테스트해야한다. 잘못된 문제를 푸는 것은 아닌지, 최종 사용자의 접근 방식이 개발자의 테스트 데이터와 어떻게 다른지에 대해 관심을 가져라.
      • 성능 테스트
      • 테스트를 테스트하라 : 버그를 심어놓고 테스트가 정상적으로 동작하는지 확인하라
      • 철저한 테스트 : 코드 커버리지만 올리지 말고 상태 조합을 테스트하라
      • 속성 기반 테스트 : 컴퓨터가 상태를 생성하도록 하여 예상치 못한 상태를 테스트하여라
    • 버그는 한번만 잡아라
      • 인간 테스터가 버그를 찾았다면 더이상 인간 테스터가 그 버그를 만나선 안된다.
      • 해당 버그를 자동화 테스트에 포함시켜라
  • 전체 자동화
    • 수작업 절차를 사용하지 말라.
    • ‘하나만’ 이라는 생각으로 수작업 절차를 추가하면 커다란 창문을 깨는 것이다

Topic 52 : 사용자를 기쁘게 하라

소프트웨어 프로젝트 자체가 아니라, 사용자가 실제로 기대하는 것에 진짜 의미가 있다. 소프트웨어는 이런 목적을 달성하기위한 수단일 뿐이다.

이런 숨겨져있는 프로젝트 배후의 가치에 대해 사용자가 기대하는 바의 일부가 드러났다면, 이런 기대를 어떻게 충족시킬 수 있을지 고민하라

  • 모든 팀 구성원이 이러한 기대를 이해해야한다
  • 결정을 내릴때 어떤 것이 기대를 충족시킬 수 있는지 생각하라
  • 이런 기대를 염두에 두고 요구사항을 비판적으로 분석하라. 요구사항을 바꾸는 것도 제안하라
  • 프로젝트 진행 중에도 계속 사용자의 기대에 대해 생각하라

사용자를 기쁘게하라. 그저 코드만 내놓지 말라


Topic 53 : 오만과 편견

실용주의 프로그래머는 책임을 회피하지 않는다. 자신의 코드만 좋게보지말고 동료들의 코드를 깎아 내리지 말라. 경계심 때문에 자신의 코드를 방어하지 말라. 같은 맥락에서 다른 사람의 코드를 존중해야 한다.

자신이 만든 코드에 서명을 할 수 있어야하고, 품질을 보증할 수 있어야한다.


맺은말

소프트웨어는 의도치 않게 디스토피아를 만들어낼 수 있다. 코드를 작성할 때마다 두가지 질문을 던져라

  1. 사용자를 보호했는가?
  2. 나라면 이것을 쓸까?

어떤 창의적인 발상은 윤리적 행위의 경계선을 달리기 시작할 때가 있다. 자신이 그런 프로젝트에 참가했다면, 그 프로젝트의 후원자와 같은 책임이 있다.


후기

이번 장은 프로젝트 전체적인 관점에서의 실용주의 팁들을 던진 장이다. 말단 개발자인 나로서는 실천하기 어려운 것들도 많지만, 몇가지는 머리 속에 새겨뒀다가 제안을 할 수 있을 거 같다.

  • Topic 49 : 팀을 실용적으로 운용하는 방법에 대해 알려주었다
    • 작고 안정적인 팀이 서로를 신뢰할 수 있어야한다.
    • 깨진 창문을 용납하지 말라
    • 변화를 인식하라
    • 팀 내부에서는 편안하고 빠른 소통이 이뤄질 수 있도록하고, 팀 외부 사람과도 소통을 잘하라
    • 예광탄, 자동화를 통해 팀 생산력을 올릴 수 있다
  • Topic 50 : 어떤 방법론, 프레임워크에 매몰되지말고 팀에 잘맞는 것을 취사선택하라. 프로젝트의 진짜 목표에 집중하라
  • Topic 51 : 버전관리, 회귀 테스트, 자동화라는 도구를 통해 프로젝트를 단단하게 만들 수 있다
  • Topic 52 : 언제나 사용자의 기대를 충족하는 것을 1순위로 생각하라
  • Topic 53 : 자신의 작성한 코드의 품질을 보증할 수 있어야한다.

topic 50, 52, 53은 태도에 대한 얘기로 본다. 이부분은 머릿속에 기억하여 항상 실천하려는 자세를 취할 수 있다.

topic 49, 51 에서도 기억하면 팀 또는 프로젝트에 개선할 부분을 더 잘 찾을 수 있을 거 같다.

This post is licensed under CC BY 4.0 by the author.

실용주의프로그래머 8장 프로젝트 전에

실용주의프로그래머 - 전체 후기

Comments powered by Disqus.

diff --git "a/posts/\354\213\244\354\232\251\354\243\274\354\235\230\355\224\204\353\241\234\352\267\270\353\236\230\353\250\270-\354\240\204\354\262\264-\355\233\204\352\270\260/index.html" "b/posts/\354\213\244\354\232\251\354\243\274\354\235\230\355\224\204\353\241\234\352\267\270\353\236\230\353\250\270-\354\240\204\354\262\264-\355\233\204\352\270\260/index.html" new file mode 100644 index 000000000..1f034230a --- /dev/null +++ "b/posts/\354\213\244\354\232\251\354\243\274\354\235\230\355\224\204\353\241\234\352\267\270\353\236\230\353\250\270-\354\240\204\354\262\264-\355\233\204\352\270\260/index.html" @@ -0,0 +1 @@ + 실용주의프로그래머 - 전체 후기 | 디피의 개발일지
Posts 실용주의프로그래머 - 전체 후기
Post
Cancel

실용주의프로그래머 - 전체 후기

이 책은 프로젝트 과정 전반에 거쳐 소프트웨어 전문가답게 진행하는 방법에 대해 서술되어있다. 전반적으로 이해가 잘되도록 쓰여져 있고, 현인의 어록과도 같은 책이라 참고할만한 부분도 많이 있다.

하지만 조직문화나 권한의 한계, 물리적인 시간의 한계로 모든 팁을 다 따르기는 어렵다. 또한 이러한 내용은 완전히 습관이 되어야하는데, 긴 책 내용을 전부 기억하기는 현실적으로 어렵다. 따라서 현재 내 상황에서 실천 가능한 팁들을 간단히 정리하였다.

이 문서는 1차, 2차, … 로 비정기적으로 업데이트 해나갈 것이다. 다음 업데이트는 이전 차수 내용이 완전히 체화 되었을 때이다.

실천 가능한 팁 정리 - 1차

  • 팀 내 구성원끼리 서로를 믿을 수 있어야한다. 신뢰가 기반이 되어야 안전하게 아이디어를 교류할 수 있다.
  • 자신이 작성한 코드에 책임을 져라. 어설픈 변명 대신 대안을 내라. 그저 모르겠다는 대답은 하지마라
  • 깨진 창문을 방치하지마라. 당장 시간이 없다면 주석이나 메모라도 작성하여 인지는 하라
  • 어떤 변화를 만들고 싶으면 작은 성공을 먼저 보여줘라. (Topic 4)
  • 당장의 일에만 정신을 쏟지말고 큰 그림을 기억하라
  • 피드백 주기를 만들어라
    • 회귀 테스트, 의뢰인 피드백, 짧은 배포주기
    • 모호한 요구사항에 대해서도 여러번 질문을 하여 피드백받아 구체화하라
    • 너무 먼 미래를 내다보지 말고 피드백을 받아라
  • 지식 포트폴리오를 만들어라 : 주기적인 투자, 리스크 관리, 다각화, 리밸런싱
  • 문서는 애초에 포함하고 나중에 집어넣으려고 하지 말라 (ex : swagger가 좋은 예시)
  • 에디터를 잘 활용하라. 자주 수동으로 사용하는 동작을 단축키로 해결할 수 있는지 찾아봐라
  • 변경하기 쉬운 설계를 하라
    • DRY : 코드를 포함한 문서, 데이터, 주석, 개발자 간의 주석을 줄여라 (Topic 9)
    • 직교성 : 코드 사이의 결합도를 줄여라. 전역 데이터도 결합이다. 데메테르 법칙(.은 하나만)
      • 상속 대신 인터페이스, 위임, 믹스인을 사용하는 것이 결합도가 낮다.
    • 프로그램을 데이터 변환 모델이라고 생각하라
  • PERT 기법 : 최상, 일반, 최악의 시나리오별로 예상되는 추정치를 전달하는 방법
  • 엔지니어링 일지를 적어라 : 디버깅 기록이나 엉뚱한 생각이나 어떤 것이든 기억할만한 무엇이든 적어도 됨
  • 있을 수 없는 일이 발생했다면 최대한 빨리 멈춰라. 빨리 멈추는게 계속 작동하는 것보다 좋다
    • 있을 수 없는 일을 검사하기 위해 단정문을 사용할 수 있다. 단정문에 걸리면 최대한 빨리 멈춰라
  • 무언가 불안한 느낌이 들면, 왜 그런 느낌이 드는지 이유를 천천히 생각해보고, 테스트해봐라
  • 잘못된 가정으로 코딩을 하지마라. 가정을 세웠다면 그것을 증명하라
  • 속성 기반 테스트를 통해 예상치못한 상태를 검증할 수 있다.
  • 리팩터링은 일찍 자주 습관적으로 하라.
  • 테스트는 코드의 첫번째 사용자다. 테스트를 생각하는 것만으로도 큰 도움이 된다.
  • 이름을 지을땐 코드의 역할에 따라, 일관적으로 지어라.
  • 어려운 문제를 맞이했을때, 불필요한 제약조건을 세우진 않았는지 생각하라
  • 어려운 문제를 맞이했을때, 잠시 숨을 돌리거나 고무오리 기법을 사용하는 것도 좋다
  • 일상적으로 반복되는 작업을 자동화하라
  • 특정한 방법론에 얽매이지말고, 팀에 잘 맞는 방법론의 좋은 점만 취하여라
  • 사용자가 기대하는 것이 무엇인지 계속 생각하라. 그것이 진짜 목표이다.

  • 하나의 메서드, 컴포넌트를 작성한 이후에는 어떻게 테스트할지를 생각해보자
  • 중요한 메서드, 컴포넌트에는 테스트를 추가하자 (속성기반 테스트, 단위테스트 등)
  • 반복되는 작업이 보이면 자동화하자
This post is licensed under CC BY 4.0 by the author.

실용주의프로그래머 9장 실용주의 프로젝트

Esbuild to reduce build times by 90%

Comments powered by Disqus.

diff --git "a/posts/\354\213\244\355\226\211\354\273\250\355\205\215\354\212\244\355\212\270/index.html" "b/posts/\354\213\244\355\226\211\354\273\250\355\205\215\354\212\244\355\212\270/index.html" new file mode 100644 index 000000000..1eb9ca2f0 --- /dev/null +++ "b/posts/\354\213\244\355\226\211\354\273\250\355\205\215\354\212\244\355\212\270/index.html" @@ -0,0 +1,55 @@ + 자바스크립트 실행 컨텍스트 | 디피의 개발일지
Posts 자바스크립트 실행 컨텍스트
Post
Cancel

자바스크립트 실행 컨텍스트

실행컨텍스트

  • 코드의 실제 진행상황을 추적하는데 필요한 정보들을 모아둔 구조

  • 함수가 ()로 호출되면 실행컨텍스트가 생성됨.
    • 실행 컨텍스트에는 파라미터를 포함한 지역 메모리와 실행문이 있음.
  • 함수가 종료되면 실행 컨텍스트는 사라진다.


자바스크립트의 함수 객체

  • 서브루틴으로 실행할 수 있는 객체
  • 동작을 나타내는 실행코드가 있으면서, 일반 객체와 동일하게 동작할 수 있다.
    • 서브루틴 실행 : sum()
    • 일반 객체 : sum.a = 1;


콜스택

  • 현재 실행되고 있는 실행 컨텍스트를 추적하기 위한 구조체
  • 함수가 실행되면 실행순서에 따라 차례대로 콜 스택에 쌓이고 종료되면 Pop된다.
  • 함수가 실행될 때마다 실행컨텍스트가 생성되는데, 자바스크립트 엔진이 현재 실행중인 함수의 실행컨텍스트를 추적하기 위해선 콜스택의 top을 사용한다.


스코프

  • 현재 접근할 수 있는 변수들의 범위
  • 현재 실행중인 실행 컨텍스트에서 변수를 찾을 수 없다면, 상위 스코프의 실행 컨텍스트로 탐색 범위를 옮긴다.
    • 이를 스코프 체인이라고 함.
  • 서로 독립된 스코프에서 생성된 함수는 스코프체인이 일어나지 않는다.
    • 즉, 스코프체인은 상위 스코프의 실행 컨텍스트을 탐색하는 것을 말한다.
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+
// 스코프 체인이 일어나지 않음
+function sum() {
+    return a + b;
+}
+function calc(a, b, expr) {
+    return expr();
+}
+calc(1,3, sum); // 에러
+
+// 스코프 체인이 일어남
+function calc(a, b) {
+    const sum = () => {
+        return a+ b;
+    }
+    return sum();
+}
+calc(1,3) // 4
+


클로저

  • 함수가 함수를 반환할 때, 반환되는 함수는 자신을 둘러싼 메모리 환경을 가지고 반환하는 것
    • 함수의 호출이 아닌 정의된 위치에 결정되는 스코프이며, 이를 렉시컬 스코프라고 한다.
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+
function outer() {
+    var n = 0;
+    function increase() {
+        n += 1;
+    }
+    return increase;
+}
+var newFn = outer();
+newFn();   // n이 1 증가하여 1이 됨.
+newFn();   // n이 1 증가하여 2가 됨
+
  • 상위 실행 컨텍스트로 스코프 체인을 이어가기 전, 클로저에 변수가 있는지 먼저 확인한다.
  • 클로저 변수는 함수가 호출이 되어야만 접근이 가능하기 때문에 정보은닉에 활용된다.
  • 함수 호출간 공유 메모리로도 활용이 가능하다.
    • 일반적으로 함수가 종료되면 메모리 환경이 사라져, 각 호출간 연결고리가 없다.
    • 위 예제에서 newFn의 호출마다 이전 호출의 결과가 누적되어 n이 하나씩 증가하는 것을 말함.


비동기 자바스크립트

  • 자바스크립트 엔진은 기본적으로 싱글스레드이다.
  • 개발자들은 브라우저 API를 활용하여 비동기 프로그래밍을 할 수 있음.
  • 동작
    1. 브라우저 API는 콜백을 실행조건이 되면 스레드 큐에 등록시킨다.
      • 실행조건 : 네트워크 요청 종료, 타이머 종료
    2. 엔진이 콜백을 실행시킬 준비가 되면, 스레드 큐에서 콜 스택으로 콜백함수를 넘겨준다.
      • 준비 : 콜스택이 비어있음 + 전역 실행 콘텍스트에서 실행할 코드가 없음
  • 이렇게 조건을 계속 체크하고, 콜백함수를 큐에서 스택으로 옮겨주는 걸 이벤트 루프라고 한다.


출처

https://forward.nhn.com/2021/sessions/17

This post is licensed under CC BY 4.0 by the author.

코테대비 SQL 문법

DB 페이징 기법

Comments powered by Disqus.

diff --git "a/posts/\354\225\214\352\263\240\353\246\254\354\246\230-\352\260\234\354\232\224/index.html" "b/posts/\354\225\214\352\263\240\353\246\254\354\246\230-\352\260\234\354\232\224/index.html" new file mode 100644 index 000000000..c61391e8c --- /dev/null +++ "b/posts/\354\225\214\352\263\240\353\246\254\354\246\230-\352\260\234\354\232\224/index.html" @@ -0,0 +1,51 @@ + 알고리즘 개요 | 디피의 개발일지
Posts 알고리즘 개요
Post
Cancel

알고리즘 개요

DP

복잡한 문제를 간단한 여러 하위문제로 나누어 푸는 방법

두가지 구현방식이 존재함

  • top-down
    • 여러개의 하위문제로 나누고, 하위문제를 푼 다음, 그것들을 결합하여 최종적으로 최적해를 구한다.
    • 이때 하위문제로 나눌때 같은 하위문제를 가지고 있는 경우가 있다. 이때의 최적해를 저장해서 사용하여 같은 하위문제를 다시 풀지 않도록 하는 방법을 메모이제이션이라고 한다.
  • bottom-up
    • 하위문제들의 해로 상위문제의 최적해를 구한다.

피보나치 예시

top-down
+f (int n) {
+  if n == 0 : return 0
+  elif n == 1: return 1
+
+  if dp[n] has value : return dp[n]
+  else : dp[n] = f(n-2) + f(n-1)
+         return dp[n]
+}
+
bottom-up
+f (int n){
+  f[0] = 0
+  f[1] = 1
+  for (i = 2; i <= n; i++) {
+   f[i] = f[i-2] + f[i-1]
+  }
+  return f[n]
+}
+

접근방법

  1. 모든 답을 만들어보고 그 중 최적해의 점수를 반환하는 완전 탐색 알고리즘을 설계한다.
  2. 전체 답의 점수를 반환하는 것이 아니라, 앞으로 남은 선택들에 해당하는 점수만을 반환하도록 부분문제 정의를 변경한다.
  3. 재귀 호출의 입력 이전의 선택에 관련된 정보가 있다면 꼭 필요한 것만 남기고 줄인다.
  4. 입력이 배열이거나 문자열인 경우 가능하다면 적절한 변환을 통해 메모이제이션을 할 수 있도록 조정한다.
  5. 메모이제이션을 적용한다.



그리디

각 단계마다 지금 당장 가장 좋은 방법만을 선택해나가는 방법.

접근 방법

  1. 문제의 답을 만드는 과정을 여러 조각으로 나눈다.
  2. 각 조각마다 어떤 우선순위로 선택을 내려야할지 결정한다.
  3. 다음 두 속성이 적용되는지 확인해본다.
    1. 탐욕적 선택 속성 : 항상 각 단계에서 우리가 선택한 답을 포함하는 최적해가 존재하는가
    2. 최적 부분구조 : 각 단계에서 항상 최적의 선택만을 했을 때, 전체 최적해를 구할 수 있는가



Sorting algorithm

Bubble sort

인접한 두개의 데이터를 비교해가면서 정렬을 진행하는 방식. 매 회차마다 가장 큰 값이 맨 끝으로 이동된다.

Space ComplexityTime Complexity
O(1)O(n^2)


Selection Sort

정렬되지 않은 원소 중 가장 작은 값(혹은 가장 큰 값)을 고르고, 해당하는 위치로 교환해주는 방식

Space ComplexityTime Complexity
O(1)O(n^2)


Insertion Sort

i 번째를 정렬할 순서라고 가정하면, 0 부터 i-1 까지의 원소들은 정렬되어있다는 가정하에, i 번째 원소와 i-1 번째 원소부터 0 번째 원소까지 비교하면서 i 번째 원소가 비교하는 원소보다 클 경우 서로의 위치를 바꾸고, 작을 경우 위치를 바꾸지 않고 다음 순서의 원소와 비교하면서 정렬해준다.

Space ComplexityTime Complexity
O(1)O(n^2)


Merge Sort

더이상 나누어지지 않을 때 까지 반 씩(1/2) 분할하다가 더 이상 나누어지지 않은 경우 반환한다. 이 때 반환한 값끼리 combine하면서, 비교가 이뤄지며, 비교 결과를 기반으로 정렬되어 임시 배열에 저장된다. 그리고 이 임시 배열에 저장된 순서를 합쳐진 값으로 반환한다.

Space ComplexityTime Complexity
O(n)O(nlogn)


Heap Sort

binary heap 자료구조를 이용한 정렬방법

두가지 방법

  1. 정렬의 대상인 데이터들을 힙에 넣었다가 꺼내며 정렬
  2. 기존의 배열을 힙으로 만든 다음에 꺼내어 정렬

heap에 데이터를 저장하는 시간은 O(logn)이고, 삭제 시간은 O(log n)이다.

Space ComplexityTime Complexity
O(1)O(nlogn)


Quick sort

가장 빠르지만, 최악의 경우 O(n^2)의 시간복잡도를 가짐. 하지만 constant factor가 작아서 속도가 빠르다.

분할 정복전략을 사용.

Divide 과정에서 pivot이라는 개념이 사용된다. 오름차순으로 정렬한다고 하면, 이 pivot 을 기준으로 좌측은 pivot 보다 작은 값이 위치하고, 우측은 큰 값이 위치하도록 partition된다. 이렇게 나뉜 좌, 우측 각각의 배열을 다시 재귀적으로 Quick Sort 를 시키면 또 partition 과정이 적용된다.이 때 한 가지 주의할 점은 partition 과정에서 pivot 으로 설정된 값은 다음 재귀과정에 포함시키지 않아야 한다. 이미 partition 과정에서 정렬된 자신의 위치를 찾았기 때문이다.

Worse case

pivot이 항상 가장 작은값 또는 가장 큰 값으로 선택되면, 한쪽으로 치우친 분할이 일어나게되고, O(n) 깊이를 갖게 된다.

Balanced-partitioning

배열 내의 원소 중 임의의 원소를 pivot으로 설정하면 입력에 관계없이 일정한 수준의 성능을 얻을 수 있다고 함.

partitioning

가장 마지막 원소를 pivot 으로 설정했다고 가정하자.

이 pivot 은 움직이지 말자. 첫번째 원소부터 비교하는데 만약 그 값이 pivot 보다 작다면 그대로 두고, 크다면 맨 마지막에서 그 앞의 원소와 자리를 바꿔준다. 즉 pivot value 의 index 가 k 라면 k-1 번째와 바꿔주는 것이다.

이것을 모든 원소에 대해 실행하고 마지막 과정에서 작은 값들이 채워지는 인덱스를 가리키고 있는 값에 1 을 더한 index 값과 pivot 값을 바꿔준다. 즉, 최종적으로 결정될 pivot 의 인덱스를 i 라고 했을 때, 0 부터 i-1 까지는 pivot 보다 작은 값이 될 것이고 i+1 부터 k 까지는 pivot 값보다 큰 값이 될 것이다.

Space ComplexityTime Complexity
O(log(n))O(nlogn)


Non-comparisons Sorting algorithm

Counting sort

범위와 타입이 정해져있는 입력에서, 각 값이 몇 번 나왔는지 세어 정렬하는 방법이다.

점수와 같이 0~100 으로 구성되는 좁은 범위에 존재하는 데이터들을 정렬할 때 유용하게 사용할 수 있다.

Space ComplexityTime Complexity
O(n)O(n)

Radix Sort

입력의 데이터의 길이가 동일할때 사용하는 방법.

하나의 기수마다 하나의 버킷을 생성하여, 분류를 한 뒤에, 버킷 안에서 또 정렬을 하는 방식이다.

두가지 방식으로 나뉨

  • LSD(Least Significant Digit) : 일의 자리부터 정렬. 주로 사용됨. 중간 정렬 결과를 확인할 수 없음. 끝가지 되야지만 정렬이 됨.
  • MSD(Most Significant Digit) : 가장 큰 자리부터 정렬. 중간 정렬결과를 확인할 수 있음.

MSD는 중간정렬를 확인할 수 있으나, 정렬이 완료됐는지 확인하는 과정이 필요하고 여기에 메모리가 더 소요된다. 따라서 일반적으로 LSD를 사용

Space ComplexityTime Complexity
O(n)O(n)



에라토스테네스의 체

만일 100 이하의 소수를 모두 찾고 싶다면, 1 부터 100 까지의 자연수를 모두 나열한 후, 먼저 소수도 합성수도 아닌 1을 지우고, 2외의 2의 배수들을 다 지우고, 3외의 3의 배수들을 다 지우는 과정을 100제곱근인 10이하의 소수들에 대해서만 반복하면, 이때 남은 수들이 구하고자 하는 소수들이다.

Space ComplexityTime Complexity
O(n)O(loglogn)
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+
	int num1, num2;
+	int arr[MAX] = { 0,1 };
+
+	scanf("%d %d", &num1, &num2);
+
+	for (int i = 2; i <= num2; i++)
+	{
+		for (int j = 2; i * j <= num2; j++)
+			arr[i * j] = 1;
+	}
+
+	for (int i = num1; i <= num2; i++)
+	{
+		if (!arr[i])
+			printf("%d\n", i);
+	}
+



출처

https://github.com/JaeYeopHan/Interview_Question_for_Beginner/tree/master/Algorithm

This post is licensed under CC BY 4.0 by the author.

singleton

javascript 이벤트루프

Comments powered by Disqus.

diff --git "a/posts/\354\225\214\352\263\240\353\246\254\354\246\230-\355\214\201-\354\240\225\353\246\254/index.html" "b/posts/\354\225\214\352\263\240\353\246\254\354\246\230-\355\214\201-\354\240\225\353\246\254/index.html" new file mode 100644 index 000000000..c4be40676 --- /dev/null +++ "b/posts/\354\225\214\352\263\240\353\246\254\354\246\230-\355\214\201-\354\240\225\353\246\254/index.html" @@ -0,0 +1 @@ + 알고리즘 팁(WIP) | 디피의 개발일지
Posts 알고리즘 팁(WIP)
Post
Cancel

알고리즘 팁(WIP)

구조 관련

정렬된 배열의 앞 뒤로 새로운 요소를 삽입 해야되는 경우

  • 필요한 길이의 2배길이의 배열을 선언한 다음, (start, to)로 인덱스를 트래킹해가면서 넣는다.
  • 이때 start, to의 초깃값은 2배 길이의 배열의 딱 중간이어야함(그래야 최솟값만 들어가거나, 최댓값만 들어갈때도 배열이 터지지 않음)

소수판별

  • floor(sqrt(n)) 까지만 검사해도 됨.

라이브러리 관련

string

  • < > == + 등의 연산자 사용가능
  • .substr(index, size)
    • 첫번째에는 시작 인덱스, 두번째에는 사이즈를 넣음
    • O(n)의 시간복잡도를 가지므로 조심해서 사용
  • .replace(index, size, str)
    • 첫번째에는 시작인덱스, 두번째에는 사이즈, 세번째에는 변경할 문자열을 넣음
    • 첫번째와 두번째로 정해진 범위를 세번째의 문자열로 교체한다.
  • .compare(string)
    • 매개변수로 들어온 string과 같으면 0을 반환하고, 다르면 0이 아닌 수를 반환한다.
    • 호출하는 string의 값이 매개변수보다 사전순으로 앞에 있을땐 음수, 사전순으로 뒤에 있을땐 양수를 반환
  • .find(str, index = 0)
    • 호출하는 string과 매개변수로 들어오는 string 중에 일치하는게 있는지 확인
    • 만약 일치하는게 있으면, 일치하는 부분의 첫번째 순서를 반환한다.
    • 두번째는 어디서부터 찾을지이며, 디폴트는 0이다.
  • .append(str2, n, m)
    • 뒤에 str2의 n index부터 m개의 문자를 이어 붙임
  • .append(n, char)
    • 뒤에 n개의 char를 이어붙임
  • .insert(n, str2)
    • n번째 index 뒤에 str2를 이어붙임
  • .erase(n, m)
    • n~m번째 문자를 지움
  • 변환 관련
    • to_string(num) : 숫자를 문자열로 바꿔줌
    • stoi(), stof(), stol(), stod() : 문자열을 숫자로 바꿔줌
This post is licensed under CC BY 4.0 by the author.

Object Oriented Programming

함수형 프로그래밍

Comments powered by Disqus.

diff --git "a/posts/\354\235\221\354\247\221\353\217\204/index.html" "b/posts/\354\235\221\354\247\221\353\217\204/index.html" new file mode 100644 index 000000000..28a08977b --- /dev/null +++ "b/posts/\354\235\221\354\247\221\353\217\204/index.html" @@ -0,0 +1 @@ + 응집도와 결합도 | 디피의 개발일지
Posts 응집도와 결합도
Post
Cancel

응집도와 결합도

응집도

모듈 내부의 기능적인 집중 정도. 높을 수록 좋다.


우연적 응집도 < 논리적 응집도 < 시간적 응집도 < 절차적 응집도 < 교환적 응집도 < 순차적 응집도 < 기능적 응집도


기능적 응집도(Functional Cohesion) : 모듈 내부의 모든 기능이 단일한 목적을 위해 수행되는 경우

순차적 응집도(Sequential Cohesion) : 모듈 내에서 한 활동으로 부터 나온 출력값을 다른 활동이 사용할 경우

교환적 응집도(Communication Cohesion) : 동일한 입력과 출력을 사용하여 다른 기능을 수행하는 활동들이 모여있을 경우

절차적 응집도(Procedural Cohesion) : 모듈이 다수의 관련 기능을 가질 떄 모듈 안의 구성요소들이 그 기능을 순차적으로 수행할 경우

시잔적 응집도(Temporal Cohesion) : 연관된 기능이라기 보단 특정 시간에 처리되어야 하는 활동들을 한 모듈에서 처리할 경우

논리적 응집도(Logical Cohesion) : 유사한 성격을 갖거나 특정 형태로 분류되는 처리 요소들이 한 모듈에서 처리되는 경우

우연적 응집도(Coincidental Cohesion) : 모듈 내부의 각 구성요소들이 연관이 없을 경우 -> 서로 영향을 안준다. 즉 뭉쳐는 있는데 딱히 이유는 없다.


결합도

결합도는 모듈과 모듈간의 상호 의존 정도. 낮을 수록 좋음


자료 결합도 < 스탬프 결합도 < 제어 결합도 < 외부 결합도 < 공통 결합도 < 내용 결합도


자료 결합도(Data Coupling) : 모듈간의 인터페이스 전달되는 파라미터를 통해서만 모듈간의 상호 작용이 일어나는 경우. 깔끔한 Call by value

스탬프 결합도(Stamp Coupling) : 모듈간의 인터페이스로 배열이나 오브젝트, 스트럭쳐등이 전달되는 경우.

제어 결합도(Control Coupling) : 단순히 처리를 해야할 대상인 값만 전달되는게 아니라 어떻게 처리를 해야 한다는 제어 요소(DCD, Flag등)이 전달되는 경우.

외부 결합도(External Coupling) : 어떤 모듈에서 반환한 값을 다른 모듈에서 참조해서 사용하는 경우

공통 결합도(Common Coupling) : 파라미터가 아닌 모듈 밖에 선언되어 있는 전역 변수를 참조하고 전역변수를 갱신하는 식으로 상호작용하는 경우

내용 결합도(Content Coupling) : 다른 모듈 내부에 있는 변수나 기능을 다른 모듈에서 사용 하는 경우


출처

https://raisonde.tistory.com/entry/%EA%B2%B0%ED%95%A9%EB%8F%84Coupling%EA%B3%BC-%EC%9D%91%EC%A7%91%EB%8F%84Cohension

This post is licensed under CC BY 4.0 by the author.

스키마

반정규화

Comments powered by Disqus.

diff --git "a/posts/\354\236\230\353\252\273\353\220\234-DRY/index.html" "b/posts/\354\236\230\353\252\273\353\220\234-DRY/index.html" new file mode 100644 index 000000000..bd62cadb3 --- /dev/null +++ "b/posts/\354\236\230\353\252\273\353\220\234-DRY/index.html" @@ -0,0 +1 @@ + 잘못된 DRY | 디피의 개발일지
Posts 잘못된 DRY
Post
Cancel

잘못된 DRY

https://velog.io/@eunbinn/dry-the-common-source-of-bad-abstractions

  • DRY 원칙을 너무 지키다보니 잘못된 추상화를 하는 경우가 있다
  • 바로 서로 성격이 다른 코드이지만, 비슷한 패턴을 가진다는 이유로 같은 코드로 묶는 경우이다.
  • ex) 네비게이션에 Article, Menu, Buy 버튼이 있을때, Article, MenuBuy 버튼은 서로 성격이 다르지만 같은 팩터리 메서드로 묶는다면, 추후 Buy 버튼만 변경될 우려가 있다.
  • 따라서 서로 다른 성격의 코드는 다르게 묶거나, 다양한 형태에 대응 가능하도록 하는 것이 좋다.
This post is licensed under CC BY 4.0 by the author.

실용주의프로그래머 7장 코딩하는 동안

실용주의프로그래머 8장 프로젝트 전에

Comments powered by Disqus.

diff --git "a/posts/\354\242\213\354\235\200\354\275\224\353\223\234\353\236\200/index.html" "b/posts/\354\242\213\354\235\200\354\275\224\353\223\234\353\236\200/index.html" new file mode 100644 index 000000000..53daad59a --- /dev/null +++ "b/posts/\354\242\213\354\235\200\354\275\224\353\223\234\353\236\200/index.html" @@ -0,0 +1,315 @@ + 좋은 코드란 무엇인가? | 디피의 개발일지
Posts 좋은 코드란 무엇인가?
Post
Cancel

좋은 코드란 무엇인가?

좋은 코드란 무엇일까?

일반적으로 좋은 코드라고 하면 다음 세가지를 얘기한다.

  • 읽기 좋은 코드
  • 테스트가 용이한 코드
  • 중복이 없는 코드

그럼 왜 위 세가지가 갖춰진 코드를 좋은 코드라 하는 걸까? 그리고 어떻게 이 세가지를 지키며 코드를 작성할수 있을까?

왜 좋지 않은 코드가 생산되는가?

우리는 항상 좋은 코드를 작성하기 위해 노력한다.

하지만 언제나 이것을 놓치는 시점이 존재하기에 좋지 않은 코드가 나오게 된다.

그럼 어떠다가 놓치게 되는 것일까?

우리가 마주치는 상황

  • 이미 운영중인 서비스의 CS 대응하며 수정
  • 변경된 요구사항, 인터페이스를 반영
  • 어제 또는 방금 작성한 코드 수정

우리는 언제나 코드를 수정하고, 다시 만든다. 그러는 과정에서 다음과 같은 상황이 발생하게 된다.

쓰이지 않는 코드

수정을 반복하면서 쓰이지 않는 코드가 발생하게 된다. 하지만 우리는 보통 이 코드를 지우지 않고 놔둔다. 바로 어디서 쓰일지 모른다는 불안감 때문이다.

이 불안감은 어디서 오는 것일까?

  1. 거리

    코드가 동작하는 곳과 코드가 정의된 곳 사이의 물리적 거리가 멀면 파악하기 힘들어진다. 보고 있는 코드가 일정 범위에서만 사용된다면, 그 범위만 확인 후 사용하지 않는 코드라는 것을 확신할 수 있을것이다.

  2. 순수하지 않은 함수

    함수 외부의 어떤 값을 기반으로 동작하는 함수는 그 side effect를 제어하기 어렵다. 함수의 입출력만 확인하여 함수 내부를 수정할 수 없다면 수정하기 까다롭다.

응급처치한 코드

중복되어서 하나의 모듈로 따로 빼두었지만, 쓰이는 곳에 따라 다르게 동작하도록 로직을 추가해야할 때를 마주친다.

이때 보통 side effect가 두려워서 함수에 입력을 추가하든가, 옵션 값을 추가하여 내부에서 억지로 처리한다. 이때 이 코드는 좋지 않은 코드가 된다.

이런 응급처치가 여러번 반복되면 아무도 알아볼수없는 함수가 탄생한다.

기술부채

물론 응급처치가 필요할때가 존재한다. 급하게 문제를 해결해야할때는 응급처치가 필요하다. 하지만, 언제나 이것을 부채처럼 여기고 수정해야한다.

좋지 않은 코드 줄이기

좋은 코드란 좋지 않은 코드가 없는 코드를 말한다. 따라서 좋지 않은 코드를 줄이고 격리하자. 이렇게 코드를 점진적으로 개선해나가야 한다.

추출이 아닌 추상화

추출

별다른 기준없이 중복 코드를 단순히 밖으로 빼내는 것

추상화

대상의 중요 요점들을 재해석해서, 의존성이 있는 것들끼리 묶어 밖으로 빼낸 것

의존성을 드러내기 위한 추상화

단순히 중복된 코드를 추출하는 것만으로는 재사용하기가 어렵다. 재사용을 위해 또다른 비용을 소모하게 될 뿐이다.

함수를 분리할때는 그 함수의 역할을 인지하고 하나의 역할만 하도록 정의해야한다. 즉, 의존성을 드러내어 의존성이 있는 것들끼리만 묶어서 함수의 추상화를 해야한다.

EX : before
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+
const Spagetti = () => {
+  const [page, setPage] = useState(1);
+  const [totalPage, setTotalPage] = useState(10);
+  const [data, setData] = useState(null);
+  const [isLoading, setLoading] = useState(false);
+
+  const handlePage = () => {};
+  const fetchData = async () => {
+    fetch(page)
+  };
+
+  useEffect(() => {
+    // data fetch
+  }, [])
+
+  return (
+    <Component> ... </Componnet>
+  )
+}
+
  • pagination을 관리하는 state - page, totalpage 와 data의 상태를 관리하는 state - data, isLoading 이 다른 곳에서 동일하게 사용된다고 가정해보자.
  • 그대로 추출하면 다음과 같이 된다.
EX : after - bad
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+
function useSpagettiData(initialPage: number, fetchFunction: () => Promise<Data>) {
+  const [page, setPage] = useState(1);
+  const [totalPage, setTotalPage] = useState(10);
+  const [data, setData] = useState(null);
+  const [isLoading, setLoading] = useState(false);
+
+  const fetchData = async () => {
+    return await fetch(page);
+  };
+
+  const handlePage = () => { ... };
+
+  useEffect(() => {
+    // data fetch
+  }, [])
+
+  return {
+    page, data, isLoading, isError, handlePage, ...
+  }
+}
+
+const Spagetti = () => {
+  const { data } = useSpagettiData(1, fetchData)
+
+  return ( ... );
+}
+
  • 단순히 추출을 한 결과이다.
  • 만약 코드에 변경이 생겨서 data의 초깃값을 설정해줘야한다면? api 응답값에 따라 total page가 달라지는 경우가 있다면? 아마 세번째 인자로 무언가를 추가하여 내부에서 분기를 추가하고, 로직을 재구성할 것이다.
  • 즉 한 함수안에 의존성이 서로 없는 부분이 섞여, 한 부분과 상관이 없는 부분이 변경되어도 함수 전체가 변경되어야한다.
  • 그럴 경우 스파게티 코드가 탄생하게 된다.
EX : after - good
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+
// pagination을 관리하는 hooks
+function usePagination(initialPage: number) {
+  const [page, setPage] = useState(initialPage);
+  const [totalPage, setTotalPage] = useState(10);
+
+  const handlePage = () => {};
+
+  return { page, totalPage, handlePage };
+}
+
+// data fetch를 관리하는 hooks
+function useFetch<T>(fetchFunction: () => Promise<T>, intialValue = null) {
+  const [data, setData] = useState(intialValue);
+  const [isLoading, setLoading] = useState(false);
+
+  useEffect(() => {
+    // data fetch
+  }, [])
+
+  return { data, isLoading, isError };
+}
+
+const Spagetti = () => {
+  const { page, totalPage, handlePage } = usePagination(1);
+  const { data } = useFetch(() => fetch(page), intialDadtdad);
+
+  return ( ... );
+}
+
  • 함수에 수정이 생겨도 의존성이 있는 부분끼리만 묶여있기에, 서로의 영역을 침범하지 않는다.

Where

그럼 어디에 분리할 것인가?

단순히 컴포넌트를 렌더링하는 함수 컴포넌트일 경우 어느정도 레벨로 공유될지에 따라 정의되는 위치가 달라진다. 어떤 함수는 도메인과 강하게 결합되어있어 특정 페이지에서만 재사용되고, 어떤 함수는 의존 관계없이 어디에서든 사용할 수 있다.

목적에 따라 맞는 위치를 정의해주는 것 또한 중요하다

삭제하기 쉬운 코드와 삭제하기 어려운 코드의 분리

아무리 깔끔하게 코드를 작성한다 하더라도, 요구사항에 맞추다보면 어쩔 수 없이 복잡한 코드가 작성된다. 사용하고 있는 라이브러리의 문제일수도 있고, 여러 브라우저에 대응하다 탄생한 코드일 수 있다.

이러한 좋지 않지만, 어쩔 수 없는 코드는 제대로 관리할 수 있도록 격리해줘야한다.

이때 주석과 함께 격리해두면 기존 코드의 흐름을 끊지 않고, 복잡한 코드를 이해하는데 도움을 줄 수 있다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+
/**
+ * @Note event type이 언제든 서버에서 추가될 수 있다. 하드코딩되어 있는 event name에 추가할 수 있도록 한다.
+ */
+const targetEventNameRegex =
+  /click_|impression_|push_|scroll_|swipe_|background_/g;
+/**
+ * @Note eventName에 이전에 선택된 eventType_이 prefix로 붙어서 전달되어 이를 subtract 해준다.
+ */
+useEffect(() => {
+  const refinedEventName = eventName?.replace(targetEventNameRegex, "");
+  setValue(getName("eventName"), `${eventType}_${refinedEventName ?? ""}`);
+}, []);
+

일관성있는 코드

최소한의 가독성을 보장하는 방법은 일관성있는 코드를 작성하는 것이다.

코드에 일관성이 지켜지면 예측이 가능해진다. 예측이 가능하다는 것은 어느 곳에 어떤 코드가 위치하는지 예상할 수 있다는 것이다. 이것이 프로젝트 팀원 간의 그라운드 룰이 필요한 이유이다.

Naming

변수 및 함수의 네이밍에 규칙을 만들자.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
// Case 1.
+// prefix: handle
+// target: button
+// action: click
+const handleButtonClick = () => {};
+
+// Case 2.
+// prefix: on
+// action: click
+// target: button
+const onClickButton = () => {};
+

Directory

우선 디렉터리 구조는 아키텍쳐가 아니다. (아키텍처 !== 디렉터리 구조)

일관된 디렉터리 구조는 전체적인 구조를 파악하는데 큰 도움을 주고, 컴포넌트간의 관계를 파악하는데에도 도움을 준다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+
src
+├── api
+├── components
+├── hooks
+├── model
+├── pages
+│   ├── contract
+│   └── docs
+└── utils
+
  • components 는 컴포넌트를 모아둔 것, pages는 route path를 드러낸 곳이라는 것을 예측할 수 있다.
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+
src
+├── @shared
+│   ├── components
+│   ├── hooks
+│   ├── models
+│   └── utils
+├── contract
+│   ├── components
+│   ├── hooks
+│   ├── models
+│   ├── utils
+│   └── index.ts
+├── docs
+│   ├── components
+│   ├── hooks
+│   ├── models
+│   ├── utils
+│   └── index.ts
+└── App.tsx
+

첫번째 구조 : 코드가 하는 역할에 따라 디렉터리를 구분함. 어플리케이션이 커지면, 코드가 정의된 곳과 사용되는 곳이 멀어지는 문제점이 발생함.

두번째 구조 : 도메인 영역에 따라 디렉터리를 구분한 Featured Based 구조. 전반적으로 사용되는 것만 @shared에 넣고, 나머지는 연관이 있는 것끼리만 묶어놔서, 어플리케이션이 커져도 응집도 있게 모여있다.

참고자료

지역성의 원칙을 고려한 패키지구조

요약 : 프로그래머의 머리를 캐시라고 한다면, temporal locality, spatial locality에 의해 연관이 있는 기능끼리 모아두어 관리하면, 필요한 부분만 머리속에 넣어두어 사용할 수 있으므로, 빠르게 처리 가능하다.

확장성있는 코드

확장이 어려운 코드는 내부에서 많은 변경이 발생하며, 이것은 코드를 읽기 어렵게 만든다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+
const Input = styled.input`
+  // styling
+`;
+const Input = ({
+  type,
+  value,
+  onChange,
+}) => {
+  return (
+    <Input type={type} value={value} onChange={onChange}>
+  )
+}
+

위 컴포넌트는 확장에 닫혀있다. 즉, 새로운 이벤트 핸들러나 값을 전달하기 어려운 구조이다.

따라서 아래와 같이 확장이 가능하도록 만들어야한다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+
const Input = styled.input`
+  // styling
+`;
+
+const Input = (props: HTMLAttributes<HTMLInputElement>) => {
+  return (
+    <Input {...props}>
+  )
+}
+

여기에 아래와 같이 validator에 대한 처리를 추가하면 좀 더 의미있어진다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+
const Input = styled.input<{ isValid?: boolean }>`
+  // styling
+  // error styling
+`;
+interface Props extends HTMLAttributes<HTMLInputElement> {
+  isValid?: boolean;
+}
+const Input = ({ isValid, ...props }: Props) => {
+  return (
+    <Input {...props} isValid={isValid}>
+  )
+}
+

출처

https://jbee.io/etc/what-is-good-code/

This post is licensed under CC BY 4.0 by the author.

css 선택자

React v18 주요 변경점

Comments powered by Disqus.

diff --git "a/posts/\354\275\224\355\205\214-\353\214\200\353\271\204-sql-\353\254\270\353\262\225/index.html" "b/posts/\354\275\224\355\205\214-\353\214\200\353\271\204-sql-\353\254\270\353\262\225/index.html" new file mode 100644 index 000000000..99aec5369 --- /dev/null +++ "b/posts/\354\275\224\355\205\214-\353\214\200\353\271\204-sql-\353\254\270\353\262\225/index.html" @@ -0,0 +1,123 @@ + 코테대비 SQL 문법 | 디피의 개발일지
Posts 코테대비 SQL 문법
Post
Cancel

코테대비 SQL 문법

정렬

1
+2
+3
+4
+5
+6
+7
+
SELECT * 
+FROM ANIMAL_INS 
+ORDER BY ANIMAL_ID ASC
+
+ORDER BY ANIMAL_ID DESC
+
+ORDER BY NAME ASC, DATETIME DESC
+

조건

1
+2
+3
+4
+5
+6
+7
+
SELECT ANIMAL_ID, NAME 
+FROM ANIMAL_INS
+WHERE INTAKE_CONDITION="Sick"
+
+WHERE INTAKE_CONDITION<>"Aged"
+
+WHERE (A.SEX_UPON_INTAKE<>"Spayed Female" AND A.SEX_UPON_INTAKE<>"Neutered Male")AND (B.SEX_UPON_OUTCOME = "Spayed Female" OR B.SEX_UPON_OUTCOME = "Neutered Male")
+

제한

1
+2
+
ORDER BY DATETIME ASC
+LIMIT 1
+

SUM, MAX, MIN

SELECT MAX(DATETIME)
+FROM ANIMAL_INS;
+
+SELECT MIN(DATETIME)
+FROM ANIMAL_INS;
+
+SELECT COUNT(*)
+FROM ANIMAL_INS;
+
+SELECT COUNT(DISTINCT NAME)
+FROM ANIMAL_INS;
+

그룹화

SELECT ANIMAL_TYPE, COUNT(*)
+FROM ANIMAL_INS
+GROUP BY ANIMAL_TYPE
+ORDER BY ANIMAL_TYPE ASC
+
+
+SELECT NAME, COUNT(NAME)
+FROM ANIMAL_INS
+GROUP BY NAME
+HAVING COUNT(NAME)>=2
+ORDER BY NAME ASC
+
+SELECT HOUR(DATETIME) AS HOUR, COUNT(*)
+FROM ANIMAL_OUTS
+GROUP BY HOUR(DATETIME)
+HAVING HOUR>=9 AND HOUR<20
+ORDER BY HOUR ASC
+

IS NULL

1
+2
+3
+4
+5
+6
+7
+8
+
SELECT ANIMAL_ID
+FROM ANIMAL_INS
+WHERE NAME IS NULL
+
+WHERE NAME IS NOT NULL
+
+SELECT ANIMAL_TYPE, IF(NAME IS NULL, "No name", NAME) AS NAME, SEX_UPON_INTAKE
+FROM ANIMAL_INS
+

JOIN

OUTER-JOIN_더알아보기

  • SELECT * FROM A a [JOIN 문] B b ON a.key=b.key 는 동일
  • 어디를 남기느냐에 따라 LEFT JOIN / FULL OUTER JOIN / RIGHT OUTER JOIN
  • 겹치는 걸 안남기면, 안남는 부분의 키가 NULL
    • FULL 일 경우 둘 중 하나가 NULL

String, Date

WHERE A.NAME IN ("Lucy", "Ella", "Pickle", "Rogan", "Sabrina", "Mitty")
+
+SELECT ANIMAL_ID, NAME, DATE_FORMAT(DATETIME, "%Y-%m-%d") as 날짜
+
+SELECT DATE('2021-08-21 12:34:56') as 날짜, 
+       MONTH('2021-08-21 12:34:56') as 월, 
+       DAY('2021-08-21 12:34:56') as 일, 
+       HOUR('2021-08-21 12:34:56') as 시간, 
+       MINUTE('2021-08-21 12:34:56') as 분, 
+       SECOND('2021-08-21 12:34:56') as 초;
+

LIKE

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+
--A로 시작하는 문자를 찾기--
+SELECT 컬럼명 FROM 테이블 WHERE 컬럼명 LIKE 'A%'
+
+--A로 끝나는 문자 찾기--
+SELECT 컬럼명 FROM 테이블 WHERE 컬럼명 LIKE '%A'
+
+--A를 포함하는 문자 찾기--
+SELECT 컬럼명 FROM 테이블 WHERE 컬럼명 LIKE '%A%'
+
+--A로 시작하는 두글자 문자 찾기--
+SELECT 컬럼명 FROM 테이블 WHERE 컬럼명 LIKE 'A_'
+
+--첫번째 문자가 'A''가 아닌 모든 문자열 찾기--
+SELECT 컬럼명 FROM 테이블 WHERE 컬럼명 LIKE '[^A]'
+
+--첫번째 문자가 'A'또는'B'또는'C'인 문자열 찾기--
+SELECT 컬럼명 FROM 테이블 WHERE 컬럼명 LIKE '[ABC]'
+SELECT 컬럼명 FROM 테이블 WHERE 컬럼명 LIKE '[A-C]'
+
This post is licensed under CC BY 4.0 by the author.

CSS 최적화

자바스크립트 실행 컨텍스트

Comments powered by Disqus.

diff --git "a/posts/\355\201\264\353\236\230\354\212\244\355\230\225-\354\273\264\355\217\254\353\204\214\355\212\270/index.html" "b/posts/\355\201\264\353\236\230\354\212\244\355\230\225-\354\273\264\355\217\254\353\204\214\355\212\270/index.html" new file mode 100644 index 000000000..3a7467864 --- /dev/null +++ "b/posts/\355\201\264\353\236\230\354\212\244\355\230\225-\354\273\264\355\217\254\353\204\214\355\212\270/index.html" @@ -0,0 +1,149 @@ + 클래스형 컴포넌트 vs 함수형 컴포넌트 | 디피의 개발일지
Posts 클래스형 컴포넌트 vs 함수형 컴포넌트
Post
Cancel

클래스형 컴포넌트 vs 함수형 컴포넌트

클래스형 컴포넌트

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+
import React, { Component } from 'react';
+
+class Hello extends Component {
+  static defaultProps = {
+      name: '이름없음'
+  };
+   
+  render() {
+    const { color, name, isSpecial } = this.props;
+    return (
+      <div style=>
+        {isSpecial && <b>*</b>}
+        안녕하세요 {name}
+      </div>
+    );
+  }
+}
+
+export default Hello;
+
  • class 키워드로 시작

  • Component를 상속 받음

  • render() 함수로 JSX 반환

  • props를 조회할 때 this 키워드 사용

  • defaultProps 설정 시 클래스 내부에 static 키워드와 함께 선언


    상태

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+
class Counter extends Component {
+  constructor(props) {
+    super(props);
+    this.state = {
+      counter: 0
+    };
+  }
+  handleIncrease = () => {
+    this.setState({
+      counter: this.state.counter + 1
+    });
+  };
+
+  handleDecrease = () => {
+    this.setState({
+      counter: this.state.counter - 1
+    });
+  };
+
+  render() {
+    return (
+      <div>
+        <h1>{this.state.counter}</h1>
+        <button onClick={this.handleIncrease}>+1</button>
+        <button onClick={this.handleDecrease}>-1</button>
+      </div>
+    );
+  }
+}
+
  • state로 상태를 관리
  • state를 선언할 때는 constructor 안에서 this.state 로 설정
  • 상태를 업데이트할 때는 this.setState 로 업데이트


Life Cycle

마운트 단계

  • constructor

    • 생성자 메서드. 컴포넌트가 만들어지면 가장 먼저 실행되는 메서드.
  • getDerivedStateFromProps

    1
    +2
    +3
    +4
    +5
    +6
    +7
    +
    static getDerivedStateFromProps(nextProps, prevState) {
    +    if (nextProps.color !== prevState.color) {
    +      return { color: nextProps.color };
    +    }
    +      
    +    return null;
    +}
    +
    • props로 받아온 것을 state에 넣어주고 싶을 때 사용함.
    • static 키워드가 필요하고, 함수 내부에서 this 객체에 접근할 수 없다
  • render
    • 컴포넌트 렌더 메서드
    • 부수효과를 일으키면 안됨. 서버통신/브라우저 쿠키 받아오기 등의 부수효과는 render 메서드 안에서는 하지 않는다. 부수효과가 필요하면 다른 생명주기 함수에서 사용.
  • componentDidMount
    • render의 첫 반환이 돔에 반영된 직후에 호출됨.


업데이트 단계

  • getDerivedStateFromProps
    • 컴포넌트의 props나 state가 바뀌었을때 호출됨.
  • shouldComponenetUpdate
    • 컴포넌트 최적화시 많이 사용된다.
    • 컴포넌트를 리렌더링할지 말지 결정함.
  • render
    • 컴포넌트 반환
  • getSnapshotBeforeUpdate
    • 렌더링 결과가 실제 돔에 반영되기 직전에 호출된다.
    • 이전 돔 요소의 상태값을 가져오기 좋음
  • componentDidUpdate
    • 업데이트된 컴포넌트가 실제 돔에 반영된 직후 호출된다.
    • 새로 반영된 돔의 상태값을 가장 빠르게 가져올 수 있는 메서드이다.


언마운트 단계

  • componentWillUnmount
    • 컴포넌트가 화면에서 사라지기 직전에 호출된다.
    • 등록했던 이벤트나 라이브러리 호출을 제거.


오류 발생시

  • componentDidCatch
    • render 함수에서 에러가 났을때, 에러가 발생할 수 있는 컴포넌트의 부모 컴포넌트에서 작성해야한다.
    • 사용자에게 에러화면을 보여주거나, 서버에 로깅할때 사용한다.


함수형 컴포넌트

함수형 컴포넌트는 자바스크립트 함수의 형태로 된 컴포넌트로, 기존에는 state생명주기함수를 사용할 수 없었지만, hook을 통해 가능해짐으로써 많이 사용하기 시작했다.

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+
import React, {useState} from 'react';
+
+function Hello({ color, name, isSpecial }) {
+  const [count, setCount] = useState(0);
+  return (
+    <div style=>
+      {isSpecial && <b>*</b>}
+      안녕하세요 {name}
+    </div>
+  );
+}
+
+export default Hello;
+
  • JSX를 return문을 사용해서 반환
  • hook을 통해 상태를 관리하고, 생명주기함수를 사용할 수 있다.


Hook 생명주기

useEffect hook을 통해 관리할 수있다.

1
+2
+3
+4
+5
+6
+
useEffect(() => {
+    
+    return () => {
+        // componentWillUnMount()
+    }
+}, [/* componentDidUpdate() */]) // dependencies를 안넣으면 리렌더링 될때마다 호출됨.
+

마운트 될 때

  • 컴포넌트가 실제 돔에 반영된 이후, 함수형 컴포넌트 내 모든 useEffect가 실행됨.

업데이트 될때

  • useEffect dependencies 에 넣은 stateprops가 업데이트 되었을때 useEffect 호출

언마운트 될 때

  • useEffect 내 return 문이 실행됨.

useLayoutEffect

  • useEffect는 컴포넌트가 render 와 paint 된 후 실행된다. 즉 실제 돔에 컴포넌트가 나타난 다음에 호출되어, useEffect 내에 dom에 영향을 주는 코드가 있을 경우 사용자 입장에서는 화면이 깜빡일 수 있다.
  • useLayoutEffect는 컴포넌트가 render 된 이후 실행되고, 그 이후에 paint 된다. 이 작업은 동기적으로 이루어지며, paint 되기 전에 실행되기에 dom을 조작하는 코드가 존재하더라도 사용자는 깜빡임을 경험하지 않는다. 이때 dom을 조작하는 코드에는 state 업데이트도 포함된다.
  • 내부의 코드가 모두 실행된 후 컴포넌트가 실제 돔에 반영되기에, 로직이 복잡할 경우 사용자가 레이아웃을 보는데까지 시간이 오래걸린다. 따라서 useEffect의 사용이 권장된다.


차이정리

클래스형 컴포넌트

  • this.state를 통해 상태를 관리한다.
  • 생명주기함수를 작성할 수 있다.

함수형 컴포넌트

  • hook을 통해 상태와 생명주기를 관리한다.
    • 생명주기 hook 비교
      • useEffect : componentDidMount, componentDidUpdate, componentWillUnmount(return문)
      • useLayoutEffect : shouldComponentUpdate



장단점 비교

함수형 컴포넌트

  • 클래스형 컴포넌트보다 메모리 자원을 덜 사용한다.
  • 코드가 간결하다.



출처

https://velog.io/@seong-dodo/React-%ED%81%B4%EB%9E%98%EC%8A%A4%ED%98%95-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-vs-%ED%95%A8%EC%88%98%ED%98%95-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8

https://devowen.com/298

https://born-dev.tistory.com/27

https://koras02.tistory.com/178?category=967574

This post is licensed under CC BY 4.0 by the author.

React에서 Key를 사용하는 이유

CSS 최적화

Comments powered by Disqus.

diff --git "a/posts/\355\216\230\354\235\264\354\247\225/index.html" "b/posts/\355\216\230\354\235\264\354\247\225/index.html" new file mode 100644 index 000000000..9709e36e7 --- /dev/null +++ "b/posts/\355\216\230\354\235\264\354\247\225/index.html" @@ -0,0 +1,10 @@ + DB 페이징 기법 | 디피의 개발일지
Posts DB 페이징 기법
Post
Cancel

DB 페이징 기법

페이징

DB 에서 페이징을 구현하는 방법


오프셋 페이징

limit과 offset을 활용하여 페이징을 구현하는 방법.

SELECT * FROM table
+ORDER BY timestamp
+OFFSET 10
+LIMIT 5
+


장점

  • 구현이 간단하다.

단점

  • Offset을 정말로 건너 뛰는 게 아니라, 한번은 읽는다. 따라서 만약 Offset이 100만이라면, 100만개의 데이터를 읽고 그다음 Limit으로 할당된 데이터를 읽는다.
  • 사용자가 다음 페이지로 넘어가기 전에 새로운 데이터가 입력된다면, 다음 페이지에서 중복데이터가 보일 수 있다.
  • 사용자가 다음 페이지로 넘어가기 전에, 현재 페이지의 데이터가 몇 개 삭제된다면, 다음페이지에서 상단 몇 개의 데이터는 누락된다.


커서 페이징

오프셋 페이징의 단점을 해결할 수 있는 페이징 기법.

클라이언트가 가져간 마지막 row의 다음 row들을 n개 요청하는 방법이다. id 또는 타임스탬프를 기준으로 정렬하고, LIMIT을 거는 방법을 사용한다.

SELECT id, title
+  FROM `post`
+  WHERE id < (Id Cursor : 996)
+  ORDER BY id DESC
+  LIMIT 5
+

이때 사용자 쪽에서는 두 번째 페이지 이상을 조회할 시 기준이 되는 값을 같이 보내줘야 한다.

스크린샷 2020-09-08 오후 9 51 23


장점

  • 새로 생성되거나 삭제되는 데이터가 많아도, 사용자에게 중복되거나 누락된 데이터를 보여주지 않는다.
  • 데이터가 많아도, 매우 빠르게 조회가 가능하다.

단점

  • 정렬할 컬럼에 중복된 값이 존재하면 안되고, 순차적이어야한다.
  • 무한 스크롤이 아닌 경우 구현이 어렵다.


페이지 스킵 구현법

  • 미리 몇개의 페이지를 가져오기
  • SKIP 된 만큼 LIMIT을 주고 마지막 10개 또는 20개만 반환하는 방식.


출처

https://wonyong-jang.github.io/database/2020/09/06/DB-Pagination.html

https://uxdesign.cc/why-facebook-says-cursor-pagination-is-the-greatest-d6b98d86b6c0

This post is licensed under CC BY 4.0 by the author.

자바스크립트 실행 컨텍스트

React-query 에서 cache를 사용하는 방법

Comments powered by Disqus.

diff --git "a/posts/\355\224\204\353\241\240\355\212\270-\354\225\204\355\202\244\355\205\215\354\263\220-\353\263\200\355\231\224/index.html" "b/posts/\355\224\204\353\241\240\355\212\270-\354\225\204\355\202\244\355\205\215\354\263\220-\353\263\200\355\231\224/index.html" new file mode 100644 index 000000000..ec32bde71 --- /dev/null +++ "b/posts/\355\224\204\353\241\240\355\212\270-\354\225\204\355\202\244\355\205\215\354\263\220-\353\263\200\355\231\224/index.html" @@ -0,0 +1 @@ + 프론트 아키텍쳐 흐름 | 디피의 개발일지
Posts 프론트 아키텍쳐 흐름
Post
Cancel

프론트 아키텍쳐 흐름

프론트 아키텍처 흐름

MVC

  • Model - View -Controller로 나눈 아키텍처
  • Model
    • 컨트롤러가 호출했을때, 요청에 맞는 역할을 수행한다. 비즈니스 로직을 구현하는 영역으로 응용프로그램에서 데이터를 처리하는 부분
    • 뷰나 컨트롤러에 대해서 어떤 정보도 알지 말아야한다.
  • Controller
    • 클라이언트의 요청을 받았을 때, 그 요청에 대해 실제 업무를 수행하는 모델컴포넌트를 호출한다. 또한 클라이언트가 보낸 데이터가 있다면, 모델에 전달하기 쉽게 데이터를 가공한다.
    • 모델이 업무를 마치면 그 결과를 뷰에게 전달한다.
    • 모델이나 뷰에 대해 알고 있어야 하며, 둘의 변경을 모니터링해야한다.
  • View

    • 컨트롤러로부터 받은 모델의 결과값을 가지고, 사용자에게 출력할 화면을 만드는 일을 한다.

    • 만들어진 화면을 컨트롤러로 다시 보내고, 컨트롤러는 이를 웹 브라우저에 전송하여 웹 브라우저가 출력하게 한다.

    • 모델과 컨트롤러에 대해 몰라야한다.

  • 단점 : jquery로 작업할 떄 반복되는 부분이 나타남

MVVM

  • Model - View - View Model 의 약자로, 프로그램의 비지니스 로직과 프레젠테이션 로직을 UI로 명확하게 분리하는 패턴.
  • MVC에서 jquery로 작업할 떄 나타나는 반복되는 부분을 줄이기 위해 등장

img1.daumcdn

  • 구성요소

    • Model : 데이터를 보관하고 있는 부분으로, 데이터를 불러오거나 업데이트하는 비즈니스 로직이 있음.

    • View Model : Model에 데이터를 요청하고 가공함. 비지니스 로직을 처리. View로부터 입력을 받아 적절한 처리를 함
      • MVC에서 Controller는 View에서 데이터를 요청할 때마다 기능을 반복적으로 수행했다면, View Model은 View와 데이터 바인딩이 되어있다. 따라서 Model에서 데이터가 변경되면 View에서도 자동으로 변경된다.
    • View : UI 담당. UI에 연관된 로직만 수행한다. 필요한 데이터는 View Model과 data binding을 통해 얻는다. 사용자의 입력을 VM으로 전달.
  • 특징

    • View와 ViewModel이 n : 1 관계이다. 하나의 View Model에 여러 View가 결합이 가능하여 View Model의 재사용성이 높다.
  • 장점

    • 데이터를 관리하는 로직과 UI로직을 깔끔하게 분리하여 유지보수에 용이해짐.

    • 코드의 재사용성을 개선

    • UI 디자이너와 개발자가 쉽게 협업할 수 있는 디자인 패턴

  • 단점

    • View Model의 설계가 쉽지 않다.
    • 상태 끌어올리기, Props drilling 문제가 있음.

Container- Presenter 패턴

  • 컴포넌트를 재사용하기 위해선, 컴포넌트에 비즈니스로직에 포함되어선 안됨.
  • 따라서 비즈니스 로직를 관장하는 컴포넌트를 container라고 하고, 화면만 그리는 컴포넌트를 presenter라고 나눔
  • 단점 : props drilling

FLUX 패턴과 Redux

  • props drilling을 해결. view에서 액션을 디스패치하면 store에서 데이터를 변경하고 다시 뷰로 전달하는 단방향 흐름을 갖는 패턴.

    image

  • 데이터 흐름 : Stores -> view -> action -> dispatcher-> Stores

    • 단방향 데이터 흐름을 가져 이해하기가 쉽다.
    • Redux 패턴 : Store가 하나이고, dispatcher 대신 리듀서를 사용한다.
      • 루트 리듀서 : 서브 리듀서를 관리하고, 액션을 서브 리듀서로 보냄. 서브 리듀서로부터 업데이트 된 상태 객체를 받고 store로 보냄.
      • 서브 리듀서 : 이전 상태를 변경하지 않고 복사해서 변경한다. 이후 상태 객체를 업데이트하고 루트 리듀서에게 돌려줌.
  • redux : flux 패턴을 이용한 구현체.

    • 컴포넌트 트리 외부에 store가 저장하여 이 store에서 상태를 저장함. 그리고 컴포넌트들은 이 store에 들어있는 상태를 사용하거나 업데이트함.
  • 장점 : props drilling을 해결. 거대한 view와 상태관리인 model을 나눔

  • 단점 : redux는 보일러플레이트가 많다.

observer-observable 패턴

  • props drilling을 해결. flux 처럼 거대한 view와 상태관리인 model을 나누는 관점은 동일
  • 복잡한 dispatch와 action을 배제하고, 값이 바뀌면 바뀐 값을 모두에게 전달한다는 개념.
  • 주로 RxJS 사용
  • MVI 패턴
    • flux 패턴의 dispatch, action, update의 인터페이스를 모두 observable을 이용한 스트림의 하나의 방식으로 만들어 비동기 문제 해결, 복잡함을 해소한 하나의 인터페이스로 만들었다.

MVI 아키텍쳐

Model - View - Intent

사용자가 다른 동작을 했는데, 실제로는 같은 데이터의 변화를 의미하는 경우가 있다. 예를들어, 키보드로 위 버튼을 누른 것과, 마우스로 + 버튼을 클릭한 것은 같은 데이터의 변화를 의미할 수 있다.

이떄 우리는 비즈니스 로직을 두 가지 레이어로 나눌 수 있다.

  1. 사용자가 View를 통해서 전달한 UI Event를 어떠한 데이터 변화를 하게 할지 전달하는 역할
  2. 전달받은 요청에 따라서 적절히 데이터를 변화하는 역할

여기서 1번을 Intent라 하고, 2번을 Model이라고 정의하면 MVI 모델이 생긴다.


MVI의 특징

MVI 아키텍쳐의 특징은, MVC, MVVM과 다르게 하나의 컴포넌트가 아니라 앱 전체에 적용된다는 점이다. 그래서 전체적으로 데이터의 방향성이 단방향으로 연결되며, 데이터가 전역적으로 구성되는 것이 특징이다. View는 모델에 의존적이지만, 비즈니스 로직은 View와의 의존성이 없기 때문에 화면변화에 유연하며, 별도로 테스트하기 쉽다. 또한 컴포넌트 간 데이터 통신에 의존하지 않기에 일관성있는 상태를 유지하기도 용이하다.

  • 데이터가 단방향으로 순환한다 -> 데이터의 흐름을 이해하고 디버깅하기 쉬워진다.
  • 비즈니스 로직이 View에 의존하지 않다 -> UI 변화 요구사항에 유연하게 대응할 수 있다.
  • View의 생명주기와 무관하게 일관성 있는 상태를 갖는다. -> 컴포넌트 생명주기에 따른 상태 동기화 문제를 해결한다.

이를 통해 View에서는 Model로부터 데이터를 조회하는 Query와 데이터를 변화시키는 Command를 언제든 조립해서 사용(CQRS 패턴)할 수 있다. 또한 View는 비즈니스 로직에 의존적이지만, View끼리는 느슨하게 결합하여 UI 요구사항 변화에 긴밀하게 대응할 수 있다.


현재 프론트 아키텍처의 방향성

context와 hook, props 상속

  • props drilling만 문제라면 props만 새로 뚫지 않도록 하여 해결하는 방법.
  • React의 기본 기능인 Context API를 사용

atomic 패턴 (recoil)

  • view-model은 분리하되 action-dispatch-reducer 와 같은 복잡한 구조 말고, 간단한 문법으로 동작하도록하자.
  • 컴포넌트 관리를 위한 방법론. View를 원자로 나누고 분자, 유기체, 템플릿, 페이지 순으로 결합하여 큰 단위의 View를 그리는 방법.

  • Recoil : Redux보다 간단한 문법으로 컴포넌트 외부에서 공통의 set, get을 할 수 있게 하면서 동시에 동기화할 수 있게 함.
    • Atoms : 작은 store 개념
    • Selector : 다른 atom이나 selector를 입력으로 받고, 상위의 atoms, selector가 업데이트되면 하위의 selector 함수도 다시 실행되며 컴포넌트들도 리렌더링 된다.

react-query

  • 서버를 하나의 model로 보고, react를 view로 보는 관점.


출처

https://yozm.wishket.com/magazine/detail/1663/

https://velog.io/@teo/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C%EC%97%90%EC%84%9C-MV-%EC%95%84%ED%82%A4%ED%85%8D%EC%B3%90%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80%EC%9A%94#mvvm-%EC%95%84%ED%82%A4%ED%85%8D%EC%B3%90—angular-react-vue

This post is licensed under CC BY 4.0 by the author.

스프링 게층구조

쿠키 인증 vs 세션 vs JWT

Comments powered by Disqus.

diff --git "a/posts/\355\225\250\354\210\230\355\230\225-\355\224\204\353\241\234\352\267\270\353\236\230\353\260\215/index.html" "b/posts/\355\225\250\354\210\230\355\230\225-\355\224\204\353\241\234\352\267\270\353\236\230\353\260\215/index.html" new file mode 100644 index 000000000..6fb3c4ead --- /dev/null +++ "b/posts/\355\225\250\354\210\230\355\230\225-\355\224\204\353\241\234\352\267\270\353\236\230\353\260\215/index.html" @@ -0,0 +1,92 @@ + 함수형 프로그래밍 | 디피의 개발일지
Posts 함수형 프로그래밍
Post
Cancel

함수형 프로그래밍

부수 효과가 없는 순수함수를 1급 객체로 간주하여 파라미터로 넘기거나 반환값으로 사용하는 것을 가능하도록 하여, 참조 투명성을 지키도록 하는 프로그래밍 패러다임

순수 함수형 프로그래밍만으로 개발을 하기에는 무리가 있다. 따라서 적절히 조절해가면서 개발을 해나가자.

요약

함수형 프로그래밍의 가장 큰 특징 두 가지

  • immutable data
  • first class citizen으로서의 function

immutable data

각 함수는 외부의 값을 변경하지 않고, side effect를 일으키지 않도록 하는 순수함수여야함.

first-class citizen

함수형 프로그래밍 패러다임에서는 함수일급객체로 간주됨. 일급객체(first-class citizen) 로서의 함수는 다음과 같은 특징을 가지고 있음

  • 변수나 데이터 구조 안에 함수를 담을 수 있어서, 함수의 파라미터로 전달할 수 있고, 함수의 반환값으로 사용할 수 있다.
  • 할당에 사용된 이름과 관계없이 고유한 구별이 가능하다.
  • 함수를 리터럴로 바로 정의할 수 있다.

좀 더 자세히

함수형 프로그래밍에 대한 이해

프로그래밍 패러다임

근래의 프로그래밍 패러다임은 크게 아래와 같이 구분할 수 있다.

  • 명령형 프로그래밍 : 어떻게 할 건지(how)를 설명하는 방식
    • 절차지향 : 수행되어야할 순차적인 처리과정을 포함하는 방식
    • 객체지향 : 객체들의 집합으로 프로그램의 상호작용을 표현
  • 선언형 프로그래밍 : 무엇을 할건지(what)를 설명하는 방식
    • 함수형 프로그래밍 : 순수함수를 조합하여 소프트웨어를 만드는 방식
    • 반응형 프로그래밍 : 모든 데이터를 스트림으로 보고, 이를 구독하는 곳에서 변화에 따라 알아서 처리하는 프로그램을 말한다. 이벤트 위주의 로직을 조합가능한 모듈로 코딩하는 방법
      • 이때 함수형 프로그래밍 패러다임을 차용하여 함수형 반응형 프로그래밍으로 개발을 할 수도 있다.

함수형 프로그래밍의 등장

명령형 프로그래밍을 기반으로 개발하던 소프트웨어는, 규모가 커짐에 따라 복잡하게 엉킨 스파게티 코드가 되어 유지보수에 매우 힘들었다. 이를 해결하기 위해 함수형 프로그래밍이라는 패러다임이 등장하여 주목을 받았다. 함수형 프로그래밍은 거의 모든 것을 순수함수로 나누어 문제를 해결하는 기법으로 작은 문제를 해결하기 위한 함수를 작성하여 가독성을 높이고 유지보수를 용이하게 해준다.

함수형 프로그래밍에 대한 이해

클린코드의 저자는 함수형 프로그래밍을 대입문이 없는 프로그래밍이라고 정의하였다.

함수형 프로그래밍은 대입문을 사용하지 않는 프로그래밍이며, 작은 문제를 해결하기 위한 함수를 작성한다.

예시

1
+
process(10, print(num));
+

process 함수는 첫번째 인자로 몇까지 iteration을 돌 것인가를 받고, 두번째 인자로는 전달받은 값을 출력하라는 함수를 매개변수로 받고 있다.

함수형 프로그래밍은 무엇을에 포커스를 두는 프로그래밍이고, 따라서 함수형 프로그래밍에서는 출력을 하는 함수를 파라미터로 넘길 수 있고, 이는 함수형 프로그래밍의 기본 원리 중 First-class Citizen이 보장되기에 가능한 방법이다.

명령형 프로그래밍에서는 메소드를 호출하면 상황에 따라 내부의 값이 바뀔 수 있다. 즉, 우리가 개발한 함수 내에서 선언된 변수의 메모리에 할당된 값이 바뀌는 등의 변화가 발생할 수 있다.

하지만 함수형 프로그래밍에서는 대입문이 없기 때문에 메모리에 한번 할당된 값은 새로운 값으로 변화할 수 없다.

함수형 프로그래밍의 특징

부수 효과가 없는 순수함수를 1급 객체로 간주하여 파라미터로 넘기거나 반환값으로 사용할 수 있으며, 참조 투명성을 지킬 수 있다.

여기서 부수효과(side effect)는 다음과 같다.

  • 변수의 값이 변경됨.
  • 자료 구조를 제자리에서 수정함
  • 객체의 필드값을 설정함
  • 예외나 오류가 발생하며 실행이 중단됨
  • 콘솔 또는 파일 I/O가 발생함

이러한 부수효과들을 제거한 함수들을 순수함수라고 부르며, 함수형 프로그래밍에서 사용하는 함수는 이러한 순수함수들이다.

  • Memory or I/O 관점에서 side Effect가 없는 함수
  • 함수의 실행이 외부에 영향을 끼치지 않는 함수(파라미터 + 자기 내부의 값만 사용)

순수함수를 이용하면 얻는 장점들

  • 함수 자체가 독립적이며 side-effect가 없기 때문에 thread에 안정성을 보장받을 수 있다. 따라서 병렬처리를 동기화 없이 진행할 수 있다.
  • 부수효과가 없기에 신뢰성있게 어디서든 쓸 수 있다.

참조투명성 (Referential Transparency)

  • 동일한 인자에 대해 항상 동일한 결과를 반환해야한다.
  • 참조 투명성을 통해 기존의 값은 변경되지 않고 유지된다.

명령형 프로그래밍과 함수형 프로그래밍에서 사용되는 함수는 부수효과의 유무에 따라 차이가 있다. 그에따라 함수가 참조에 투명한지 안한지 나뉘어진다.

참조에 투명하다는 것은 같은 값에 항상 동일한 결과를 반환하여 실행결과를 참조(예측)할 수 있다는 말이다. 이렇게 부작용을 제거하여 프로그램의 동작을 이해하고 예측을 용이하게 하는 것이 함수형 프로그래밍으로 개발하려는 핵심 동기이다.

또, 함수형 프로그래밍에서는 값의 대입이 없어 항상 동일한 실행에 동일한 결과를 반환하기 때문에, 병령처리 환경에서 Race condition에 대한 비용을 줄어준다.

순수함수의 예시

  • 순수함수
1
+2
+3
+4
+5
+6
+7
+
function countAdd(val1, val2) {
+  return val1 + val2;
+}
+console.log(countAdd(5, 10)); // result - 15
+console.log(countAdd(5, 10)); // result - 15
+console.log(countAdd(5, 10)); // result - 15
+console.log(countAdd(10, 20)); // result - 30
+
  • 비 순수함수
var number = 1;
+function countAdd2(val1, val2) {
+    return val1 + val2 + number;
+}
+console.log(countAdd2(5, 10)); // result - 16
+number = 10;
+console.log(countAdd2(5, 10)); // result - 25
+
var number = 1;
+function countAdd3(val1, val2) {
+    number = val1 + val2;
+    return val1 + val2 + number;
+}
+console.log(countAdd3(5, 10));
+
  • 리액트에서의 순수함수 : 함수형 컴포넌트의 구현
    • 모든 React 컴포넌트는 자신의 props을 변경하지 않고, 자신의 내부에 있는 상태들만을 변경해야함.
    • 따라서 props를 변화시켜야하는 상황이 발생할 경우에는, props의 값을 state로 복사하여 그 state를 변화시키는 방식으로 동작함.

함수형 프로그래밍을 위한 자바스크립트 패턴

자바스크립트는 함수형 프로그래밍을 위해 좋은 언어지만, 멀티 프로그래밍 패러다임 언어이기에, 함수형 프로그래밍으로 작성하려면 항상 신경을 써야한다.

let보다 가급적 const를 사용해라

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+
// bad
+let foo = 100
+...
+foo = somthing(foo, "bar")
+
+
+// good
+const foo = 100
+...
+const new_foo = somthing(foo, "bar")
+

Array, Date에서 원본을 변형하는 메서드는 가급적 사용하지 마라

push, pop, shift, sort, reverse 등 원본을 mutate하는 method 대신 가급적 spread operator 로 대체하거나 값을 복사해서 사용하라

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+
// bad
+const example = (arr: number[], date: Date) => {
+  arr.push(4);
+  arr.sort();
+  date.setMonth(10);
+};
+// good
+const example = (arr: number[], date: Date) => {
+  const new_arr = [...arr, 4];
+  const sorted_new_arr = [...new_arr].sort();
+  const new_date = new Date(date).setMonth(10);
+  return [new_arr, new_date];
+};
+

가급적 Object의 필드에 대입연산자를 쓰지 마라

1
+2
+3
+4
+5
+6
+7
+8
+
// bad
+const example = (obj: Object) => {
+  obj.foo = 200;
+};
+// good
+const example = (obj: Object) => {
+  return { ...obj, foo: 200 };
+};
+

가급적이라는 표현대로, 극단적으로 지키기보단, 요구사항을 위해 어쩔 수 없다면 어길 수 밖에 없다. 이에 집착하지 마라. 좋은 점을 잘 골라서 취합

함수형 반응형 프로그래밍(WIP)

Functional Reactive Programming

반응형 프로그래밍을 달성하기 위해, 함수형 프로그래밍 규칙을 강제하는 방법이다.

실제 개발을 하다보면, 순수 함수형으로 개발하는 것은 불가능에 가깝다. 따라서 반응형 프로그래밍과 결합하여 프로그래밍을 할 수 있다.

리액트로 예를들면, 사용자가 입력을 하면 그것을 처리하여 외부의 상태를 업데이트하는 함수가 존재하고, 그 상태에 반응하여 호출되는 함수들이 존재한다.

출처

https://mangkyu.tistory.com/111

https://gyuwon.github.io/blog/2020/07/24/react-has-no-functional-components.html

https://velog.io/@teo/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EC%9D%BC%EB%8B%A8-%ED%95%9C%EB%B2%88-%ED%95%B4%EB%B3%B4%EC%84%B8%EC%9A%94-%ED%95%A8%EC%88%98%ED%98%95-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D

This post is licensed under CC BY 4.0 by the author.

알고리즘 팁(WIP)

9935 문자열 폭발

Comments powered by Disqus.

diff --git "a/posts/\355\225\250\354\210\230\355\230\225\355\224\204\353\241\234\352\267\270\353\236\230\353\260\215/index.html" "b/posts/\355\225\250\354\210\230\355\230\225\355\224\204\353\241\234\352\267\270\353\236\230\353\260\215/index.html" new file mode 100644 index 000000000..2a59ad2a9 --- /dev/null +++ "b/posts/\355\225\250\354\210\230\355\230\225\355\224\204\353\241\234\352\267\270\353\236\230\353\260\215/index.html" @@ -0,0 +1,67 @@ + 함수형 프로그래밍 (이해하기 쉽게) | 디피의 개발일지
Posts 함수형 프로그래밍 (이해하기 쉽게)
Post
Cancel

함수형 프로그래밍 (이해하기 쉽게)

액션, 계산, 데이터

순수함수 vs 부수효과를 대체한 개념들.

데이터 : 이벤트에 대한 사실. 액션에 의해 변화됨

액션 : 데이터를 변화시킬 수 있음. 실행시점이나 횟수에 의존하여 언제하느냐에 따라 결과가 달라지면 액션이다.

계산 : 입력값을 통해 출력을 만들어내는 것. 같은 입력에 대해 항상 같은 출력값만 내놓아야한다. 외부 세계에 영향을 주면 안됨.

함수형 프로그래밍 예시)

1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+
// 함수형 프로그래밍 관점에서 분리해보자.
+function App() {
+  // 데이터
+  const [count, setCount] = useState(0);
+
+  // 계산
+  const increase = (value) => value + 1;
+
+  // 액션
+  const onClick = () => setCount(increase(count));
+
+  // 선언적 패턴
+  return <button onClick={onClick}>{count}</button>;
+}
+
  • 액션과 계산을 분리해야함. 계산은 외부세계에 영향을 주지 않고, 같은 입력에 같은 출력이 나오는 함수(순수함수)

더 좋은 예시) 계산함수를 아래처럼 변경

1
+
const increase = (value, offset) => value + offset;
+
  • 암묵적 입출력을 없애도록 함.
  • 언제든 재사용이 가능하며 테스트하기에도 용이하다.

불변성 - 카피온라이트, 방어적복사

카피온라이트(얕은 복사)

버튼 클릭시 배열의 값을 하나씩 늘려간다고 해보자.

1
+2
+3
+4
+5
+
const increase = (arr) => {
+  const value = arr[arr.length - 1];
+  arr.push(value + 1);
+  return arr;
+};
+

자바스크립트에서는 pass by reference로 동작하기 때문에, 위처럼 코드를 짜게 되면 원본이 변화한다. 따라서 다음과 같이 원본을 복사하여 사용하여야 불변성을 지킬 수 있다.

1
+2
+3
+4
+5
+6
+7
+8
+
const increase = (arr) => {
+  arr = arr.slice(); // array를 조작하기 전에 복사해서 사용한다.
+  const value = arr[arr.length - 1];
+  arr.push(value + 1);
+  return arr;
+};
+//or
+const increase = (arr) => [...arr, arr[arr.length - 1]];
+

방어적복사

만약 계산이나 액션 중에 우리가 수정할 수 없는 라이브러리를 사용한다면 어떻게 불변성을 지킬 수 있을까?

이때는 방어적 복사를 사용한다.

다음과 같이 structuredClone() 이라는 최근 JS에 추가된 API를 사용하여 구현할 수 있다.

1
+2
+3
+4
+5
+
const someCalcuation = (obj, value) => {
+  const clone = structuredClone(obj); // 완전한 clone을 만들어 낸다.
+  someActionLibray(clone, value); // clone값을 변경해도 원본은 변하지 않는다.
+  return clone;
+};
+

참고) structuredClone 지원 브라우저 목록

img

IE에서는 polyfill 에서 지원

선언적 패턴과 계층형 구조

계층적 구조

데이터, 액션, 계산으로 구조를 나누면 자연스럽게 코드 구조에 계층이 생기게 되고, 좋은 설계와 쉬운 리팩토링이 보장됨.

img

액션으로 갈수록 코드는 무엇을 할지에 대한 기획서에 가까운 코드가 되고, 데이터에 가까워질수록 데이터 중심적 코드를 작성하게 되고, 상대적으로 재사용성이 높고 테스트하고 쉬운 코드가 나오게 된다.

이렇게 계층을 나누고, 각 계층을 침범하지 않도록 코드를 짜다보면 자연스럽게 추상화벽이 만들어지면서 벽 상단으로의 코드변화가 하단에는 영항을 미치지 않고, 하단의 변화도 상위에 영항을 주지 않도록 할 수 있다.

이렇게되면 상위에서는 무엇을 할지 기술하는 선언적 패턴으로 코드를 작성할 수 있고, 하위에서는 테스트 하기 쉬운 코드 조각들로 이루어질 수 있다.

함수형 프로그래밍을 돕는 라이브러리들

lodash : 데이터의 필수적인 구조를 쉽게 다룰 수 있게끔 도와줌. 설명 lodash/fp 로 함수형 프로그래밍을 지원하는듯

rambda

rxjs : 이벤트 기반 프로그래밍에서 함수형 프로그래밍을 이용해 선언적으로 이벤트를 처리할 수 있도록 해줌. 설명

redux

immutable : Map, List 등의 새로운 객체를 제공하여, 변화가 일어나는 구조체는 새로만들고 나머지는 재사용하는 방식으로 불변성을 지킨다. 설명

date-fns : 날짜 관련 라이브러리. 함수형프로그래밍을 지원하는 모듈이 있음.

출처

https://velog.io/@teo/functional-programming

This post is licensed under CC BY 4.0 by the author.

view

velog 메모

Comments powered by Disqus.

diff --git a/redirects.json b/redirects.json new file mode 100644 index 000000000..a68feabbe --- /dev/null +++ b/redirects.json @@ -0,0 +1 @@ +{"/norobots/":"https://seongil-shin.github.io/404.html","/assets/":"https://seongil-shin.github.io/404.html","/posts/":"https://seongil-shin.github.io/404.html"} \ No newline at end of file diff --git a/robots.txt b/robots.txt new file mode 100644 index 000000000..a29166c9f --- /dev/null +++ b/robots.txt @@ -0,0 +1,4 @@ +User-agent: * +Allow: / + +Sitemap: https://seongil-shin.github.io/sitemap.xml \ No newline at end of file diff --git a/sitemap.xml b/sitemap.xml new file mode 100644 index 000000000..2591834ad --- /dev/null +++ b/sitemap.xml @@ -0,0 +1,1799 @@ + + + +https://seongil-shin.github.io/posts/1005/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/1007/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/10165/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/10217/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/1043/ +2023-05-30T23:05:25+09:00 + + +https://seongil-shin.github.io/posts/10775/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/10830/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/10942/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/11049/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/11054/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/11066/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/1107/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/11401/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/11437/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/1149/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/11657/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/1167/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/1197/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/1202/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/12100/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/1248/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/12849/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/12969/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/1339/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/13458/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/13549/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/14003/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/14499/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/14500/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/14938/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/14939/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/1509/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/1516/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/1562/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/1644/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/1647/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/1655/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/16562/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/16566/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/16724/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/16946/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/16954/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/17144/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/17404/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/1753/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/17837/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/1806/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/1865/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/19235/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/1956/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/1987/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/20055/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/2056/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/2098/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/2143/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/2151/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/2166/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/2186/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/2239/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/2252/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/2342/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/2529/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/2568/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/2887/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/2931/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/2933/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/3190/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/3197/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/4386/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/4811/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/4991/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/5014/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/6064/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/6087/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/7453/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/7579/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/9252/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/9328/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/9466/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/9527/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/12865/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/13460/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/14501/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/14502/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/14503/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/14863/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/14888/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/14889/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/14890/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/14891/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/15486/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/15683/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/15684/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/15685/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/15686/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/16234/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/16235/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/16236/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/1629/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/17140/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/17142/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/1748/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/17779/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/17822/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/17825/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/1799/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/19236/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/19238/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/20056/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/20057/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/20058/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/2473/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/2623/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/3568/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/5373/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/9095/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/13303/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/node_ch3/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/node_ch5/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/8980/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/node_ch4/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/node_ch6/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/node_ch7/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/React_cors_problem_under_dev_mod/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/Firebase_cloudfunction_puppeteer/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/firebase_function_firestore_storage/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/15971/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/2437/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/14238/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/16928/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/junction_seoul_2021_review/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/CSS_position/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/CSS_unit/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/lottie_animation/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/junction_portfolio/ +2021-11-24T15:29:16+09:00 + + +https://seongil-shin.github.io/posts/1039-%EA%B5%90%ED%99%98/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/3108-logo/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/GraphQL-back/ +2022-11-05T21:22:33+09:00 + + +https://seongil-shin.github.io/posts/GraphQL-front/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/5213-%EA%B3%BC%EC%99%B8%EB%A7%A8/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/10021-watering-the-field/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/18500-%EB%AF%B8%EB%84%A4%EB%9E%84-2/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/16639-%EA%B4%84%ED%98%B8-%EC%B6%94%EA%B0%80%ED%95%98%EA%B8%B0-3/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/14466-%EC%86%8C%EA%B0%80-%EA%B8%B8%EC%9D%84-%EA%B1%B4%EB%84%88%EA%B0%84-%EC%9D%B4%EC%9C%A0-6/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/react-native-%EA%B3%B5%EB%B6%80/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/5827-What's-Up-With-Gravity/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/9874-Wormholes/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/10875-%EB%B1%80/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC/ +2023-05-31T23:16:42+09:00 + + +https://seongil-shin.github.io/posts/14529-Where's-Bessie/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/11048-%EC%9D%B4%EB%8F%99%ED%95%98%EA%B8%B0/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/1890-%EC%A0%90%ED%94%84/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/15989-1,2,3-%EB%8D%94%ED%95%98%EA%B8%B0-4/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/12872-%ED%94%8C%EB%A0%88%EC%9D%B4%EB%A6%AC%EC%8A%A4%ED%8A%B8/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/15653-%EA%B5%AC%EC%8A%AC%ED%83%88%EC%B6%9C4/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/1504-%ED%8A%B9%EC%A0%95%ED%95%9C-%EC%B5%9C%EB%8B%A8%EA%B2%BD%EB%A1%9C/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/1208-%EB%B6%80%EB%B6%84%EC%88%98%EC%97%B4%EC%9D%98-%ED%95%A92/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/11779-%EC%B5%9C%EC%86%8C%EB%B9%84%EC%9A%A9%EA%B5%AC%ED%95%98%EA%B8%B0-2/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/17387-%EC%84%A0%EB%B6%84%EA%B5%90%EC%B0%A8-2/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/2096-%EB%82%B4%EB%A0%A4%EA%B0%80%EA%B8%B0/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/9251-LCS/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/react-boilerplate/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/expo-%EB%B0%B0%ED%8F%AC/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/1918-%ED%9B%84%EC%9C%84%ED%91%9C%EA%B8%B0%EC%8B%9D/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/typescript/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/paasta-noti/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/9576-%EC%B1%85-%EB%82%98%EB%88%A0%EC%A3%BC%EA%B8%B0/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/12906-%EC%83%88%EB%A1%9C%EC%9A%B4-%ED%95%98%EB%85%B8%EC%9D%B4%ED%83%91/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/10986-%EB%82%98%EB%A8%B8%EC%A7%80%ED%95%A9/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/2629-%EC%96%91%ED%8C%94%EC%A0%80%EC%9A%B8/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/flutter-%EA%B3%B5%EB%B6%80/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/yourlist/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/st-algorithm/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/flutter-(rn-%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%A5%BC-%EC%9C%84%ED%95%9C-%EC%A0%95%EB%A6%AC)/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/flutter-(rn-%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%A5%BC-%EC%9C%84%ED%95%9C-%EC%A0%95%EB%A6%AC-2)/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/flutter-(rn-%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%A5%BC-%EC%9C%84%ED%95%9C-%EC%A0%95%EB%A6%AC3)/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/flutter-(rn-%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%A5%BC-%EC%9C%84%ED%95%9C-%EC%A0%95%EB%A6%AC4)/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/react-%EC%9E%91%EB%8F%99%EC%9B%90%EB%A6%AC%EB%B6%80%ED%84%B0-tailwindcss-%EC%82%AC%EC%9A%A9%EA%B9%8C%EC%A7%80/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/1613-%EC%97%AD%EC%82%AC/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/4485-%EC%A0%A4%EB%8B%A4/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/14658/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/%EB%8D%B0%EC%BD%94%EB%A0%88%EC%9D%B4%ED%84%B0%ED%8C%A8%ED%84%B4/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/%EB%AF%B8%EC%84%B8%EB%A8%BC%EC%A7%80%EC%98%88%EC%B8%A1%EB%AA%A8%EB%8D%B8/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/generator/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/redux-saga/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/css%EC%84%A0%ED%83%9D%EC%9E%90/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/%EC%A2%8B%EC%9D%80%EC%BD%94%EB%93%9C%EB%9E%80/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/reactv18/ +2023-03-01T21:20:35+09:00 + + +https://seongil-shin.github.io/posts/oop/ +2023-05-30T23:18:27+09:00 + + +https://seongil-shin.github.io/posts/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%ED%8C%81-%EC%A0%95%EB%A6%AC/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/%ED%95%A8%EC%88%98%ED%98%95-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/9935-%EB%AC%B8%EC%9E%90%EC%97%B4-%ED%8F%AD%EB%B0%9C/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/RESTful-API/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/TDD/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/MVC-%ED%8C%A8%ED%84%B4/ +2023-06-06T22:59:21+09:00 + + +https://seongil-shin.github.io/posts/git/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/MVVM-MVP/ +2023-06-06T22:59:21+09:00 + + +https://seongil-shin.github.io/posts/13144/ +2022-01-18T20:00:00+09:00 + + +https://seongil-shin.github.io/posts/flex,grid%EB%A1%9C-%EB%82%A8%EB%8A%94%EA%B3%B5%EA%B0%84-%EC%B1%84%EC%9A%B0%EA%B8%B0/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/(CSS)%EB%8B%A4%EB%A5%B8-%EC%97%98%EB%A6%AC%EB%A8%BC%ED%8A%B8%EC%9D%98-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%B0%9C%EC%83%9D-%EC%8B%9C,-%EC%8A%A4%ED%83%80%EC%9D%BC-%EC%A0%81%EC%9A%A9%EB%B2%95/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/typescript-react-hack/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/datastructure/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/network/ +2022-08-31T19:51:08+09:00 + + +https://seongil-shin.github.io/posts/1522/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/os/ +2022-08-31T19:51:08+09:00 + + +https://seongil-shin.github.io/posts/2631%EC%A4%84%EC%84%B8%EC%9A%B0%EA%B8%B0/ +2022-01-30T14:00:00+09:00 + + +https://seongil-shin.github.io/posts/react-%EB%A1%9C%EB%94%A9-%EC%B2%98%EB%A6%AC/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/database-%EA%B0%9C%EC%9A%94/ +2023-06-03T19:34:28+09:00 + + +https://seongil-shin.github.io/posts/architecture/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EA%B0%9C%EC%9A%94/ +2022-08-31T19:51:08+09:00 + + +https://seongil-shin.github.io/posts/eventloop/ +2022-02-08T11:20:00+09:00 + + +https://seongil-shin.github.io/posts/hoisting/ +2023-06-03T19:34:28+09:00 + + +https://seongil-shin.github.io/posts/closure/ +2023-05-31T23:16:42+09:00 + + +https://seongil-shin.github.io/posts/this/ +2022-08-31T19:51:08+09:00 + + +https://seongil-shin.github.io/posts/promise-async-await/ +2022-08-31T19:51:08+09:00 + + +https://seongil-shin.github.io/posts/arrowfunction/ +2023-06-03T19:34:28+09:00 + + +https://seongil-shin.github.io/posts/cs-os/ +2022-08-31T19:51:08+09:00 + + +https://seongil-shin.github.io/posts/Gof/ +2022-08-31T19:51:08+09:00 + + +https://seongil-shin.github.io/posts/case/ +2022-08-31T19:51:08+09:00 + + +https://seongil-shin.github.io/posts/oop-analysis/ +2022-08-31T19:51:08+09:00 + + +https://seongil-shin.github.io/posts/UML/ +2022-08-31T19:51:08+09:00 + + +https://seongil-shin.github.io/posts/rumbaugh/ +2022-08-31T19:51:08+09:00 + + +https://seongil-shin.github.io/posts/integration-test/ +2022-08-31T19:51:08+09:00 + + +https://seongil-shin.github.io/posts/prefix-postfix/ +2022-08-31T19:51:08+09:00 + + +https://seongil-shin.github.io/posts/blackbox-whitebox/ +2022-08-31T19:51:08+09:00 + + +https://seongil-shin.github.io/posts/scheme/ +2022-08-31T19:51:08+09:00 + + +https://seongil-shin.github.io/posts/%EC%9D%91%EC%A7%91%EB%8F%84/ +2022-11-05T21:22:33+09:00 + + +https://seongil-shin.github.io/posts/denormalized/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/data/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/subnetting/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/17088-%EB%93%B1%EC%B0%A8%EC%88%98%EC%97%B4%EB%B0%98%ED%99%98/ +2022-03-02T14:00:00+09:00 + + +https://seongil-shin.github.io/posts/deadlock/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/ipv6/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/view/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/%ED%95%A8%EC%88%98%ED%98%95%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/velog-%EB%A9%94%EB%AA%A8/ +2022-04-24T23:00:00+09:00 + + +https://seongil-shin.github.io/posts/react18%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8/ +2022-06-03T20:05:08+09:00 + + +https://seongil-shin.github.io/posts/virtual-Dom/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/summary/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/js-%EC%BD%94%ED%85%8C-%EB%8C%80%EB%B9%84/ +2022-07-31T22:42:12+09:00 + + +https://seongil-shin.github.io/posts/react-key/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/%ED%81%B4%EB%9E%98%EC%8A%A4%ED%98%95-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8/ +2023-06-12T23:56:00+09:00 + + +https://seongil-shin.github.io/posts/css-%EC%B5%9C%EC%A0%81%ED%99%94/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/%EC%BD%94%ED%85%8C-%EB%8C%80%EB%B9%84-sql-%EB%AC%B8%EB%B2%95/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/%EC%8B%A4%ED%96%89%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8/ +2023-06-03T19:34:28+09:00 + + +https://seongil-shin.github.io/posts/%ED%8E%98%EC%9D%B4%EC%A7%95/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/react-query-cache/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/%EC%84%A0%EC%96%B8%ED%98%95/ +2023-06-03T19:34:28+09:00 + + +https://seongil-shin.github.io/posts/babel/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/useEffect-%EC%98%AC%EB%B0%94%EB%A5%B8-%EC%82%AC%EC%9A%A9/ +2022-08-24T14:00:00+09:00 + + +https://seongil-shin.github.io/posts/java-%EA%B8%B0%EB%B3%B8/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/%EB%A6%AC%EC%95%A1%ED%8A%B8%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/Mysql-Count%EC%9D%98-%EC%86%8D%EB%8F%84/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/opengraph/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/was-webserver/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/%EC%8A%A4%ED%94%84%EB%A7%81-%EA%B3%84%EC%B8%B5%EA%B5%AC%EC%A1%B0/ +2022-11-05T22:31:18+09:00 + + +https://seongil-shin.github.io/posts/%ED%94%84%EB%A1%A0%ED%8A%B8-%EC%95%84%ED%82%A4%ED%85%8D%EC%B3%90-%EB%B3%80%ED%99%94/ +2023-06-06T22:59:21+09:00 + + +https://seongil-shin.github.io/posts/web-%EC%BF%A0%ED%82%A4-%EC%9D%B8%EC%A6%9D-vs-%EC%84%B8%EC%85%98-vs-JWT/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/spring-%EC%84%9C%EB%B8%94%EB%A6%BF-%ED%95%84%ED%84%B0/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/spring-%EC%8A%A4%ED%94%84%EB%A7%81-MVC-%EA%B5%AC%EC%A1%B0/ +2023-06-06T22:59:21+09:00 + + +https://seongil-shin.github.io/posts/spring-%EC%98%88%EC%99%B8%EC%B2%98%EB%A6%AC%EC%99%80-%EC%98%A4%EB%A5%98%ED%8E%98%EC%9D%B4%EC%A7%80/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/spring-%EC%BB%A8%ED%8A%B8%EB%A1%A4%EB%9F%AC-%EB%A7%A4%EA%B0%9C%EB%B3%80%EC%88%98-%EB%B0%98%ED%99%98-%ED%83%80%EC%9E%85-%EC%9C%A0%ED%98%95/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/spring-%EC%BB%A8%ED%8A%B8%EB%A1%A4%EB%9F%AC-%ED%8C%8C%EB%9D%BC%EB%AF%B8%ED%84%B0-%ED%81%B4%EB%9E%98%EC%8A%A4%EC%99%80-@NoArgsConstructor-%EC%9D%B4%EC%8A%88/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/spring-AOP/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/spring-API-%EC%98%88%EC%99%B8%EC%B2%98%EB%A6%AC/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/devops-%EC%82%AC%EC%9D%B4%EB%93%9C%EC%B9%B4-%ED%8C%A8%ED%84%B4/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/devops-istio/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/devops-pinpoint/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/java-%ED%81%B4%EB%9E%98%EC%8A%A4-%EC%B4%88%EA%B8%B0%ED%99%94-%EB%B8%94%EB%A1%9D/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/spring-@Valid-vs-@Validated/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/spring-ArgumentResolver-%ED%99%9C%EC%9A%A9/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/spring-controller%EC%97%90%EC%84%9C-enum%EC%9D%84-%EC%9D%B8%EC%9E%90%EB%A1%9C-%EB%B0%9B%EA%B8%B0/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/spring-converter/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/spring-DI%EB%8A%94-IOC%EB%A5%BC-%ED%95%84%EC%9A%94%EB%A1%9C%ED%95%98%EC%A7%80-%EC%95%8A%EB%8A%94%EB%8B%A4(%EC%A0%95%EB%A6%AC)/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/spring-HTTP-%EB%A9%94%EC%8B%9C%EC%A7%80-%EC%BB%A8%EB%B2%84%ED%84%B0/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/spring-Interceptor/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/spring-InvalidDefinitionException/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/spring-mybatis-dynamic-field/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/web-CORS/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/web-keep-alive/ +2023-05-30T23:18:27+09:00 + + +https://seongil-shin.github.io/posts/design-pattern-%EC%9C%A0%EB%8B%9B%ED%85%8C%EC%8A%A4%ED%8A%B8%EC%97%90%EC%84%9C-%EA%B5%AC%ED%98%84-%EA%B2%80%EC%A6%9D%EC%9D%B4%EB%9E%80/ +2023-05-30T23:05:25+09:00 + + +https://seongil-shin.github.io/posts/spring-Validation/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/spring-%ED%8C%8C%EC%9D%BC-%EC%97%85%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/devops-blue-green-%EB%B0%B0%ED%8F%AC/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/devops-Canary-%ED%85%8C%EC%8A%A4%ED%8A%B8/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/devops-Helm/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/devops-HPA/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/java-checked-vs-unchecked-exception/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/spring-PathPattern%EA%B3%BC-servletPath/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/spring-%EC%8A%A4%ED%94%84%EB%A7%81-%EC%BA%90%EC%8B%9C/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/web-Content-Disposition/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/java-equals-hashcode-%EB%8C%80%EC%B2%B4/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/java-junit-Spy/ +2022-12-17T16:42:25+09:00 + + +https://seongil-shin.github.io/posts/spring-Json-%EC%9D%91%EB%8B%B5-%EC%8B%9C-%ED%8A%B9%EC%A0%95-%ED%95%84%EB%93%9C-%EB%B9%BC%EA%B3%A0-%EB%B3%B4%EB%82%B4%EA%B8%B0/ +2022-12-17T16:42:25+09:00 + + +https://seongil-shin.github.io/posts/java-lombok-%EC%82%AC%EC%9A%A9%EC%8B%9C-%EC%A3%BC%EC%9D%98%EC%A0%90/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/spring-RestTemplate/ +2022-12-17T16:42:25+09:00 + + +https://seongil-shin.github.io/posts/design-pattern-%EB%8B%A8%EC%9D%BC%EC%B1%85%EC%9E%84%EC%9B%90%EC%B9%99/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/spring-@ResponseStatus-vs-ResponseEntity/ +2022-12-17T16:42:25+09:00 + + +https://seongil-shin.github.io/posts/spring-%EB%B9%88-%EC%8A%A4%EC%BD%94%ED%94%84/ +2022-12-17T16:42:25+09:00 + + +https://seongil-shin.github.io/posts/spring-SpEL/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/design-pattern-%EC%A2%8B%EC%9D%80-%EB%A1%9C%EA%B9%85%EC%9D%84-%EC%9C%84%ED%95%B4-%EC%95%8C%EC%95%84%EC%95%BC%ED%95%A0-%EA%B2%83/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/spring-mybatis.type-aliases-package-%EB%8F%99%EC%9E%91%EC%9B%90%EB%A6%AC/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/web-STOMP/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/java-%EC%BB%A4%EC%8A%A4%ED%85%80-annotation/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/design-pattern-Apache-Kafka/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/DB-ElasticSearch/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/spring-%EC%84%9C%EB%B8%94%EB%A6%BF-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/web-SockJS/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/devops-docker-nginx-+-spring-+-react-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EB%B9%8C%EB%93%9C-%EB%B0%8F-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88-%EC%8B%A4%ED%96%89/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/devops-kubernetes/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/devops-kubenetes-%EC%8B%A4%EC%8A%B5/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/web-nginx%EC%97%90%EC%84%9C-%ED%99%98%EA%B2%BD%EB%B3%80%EC%88%98-%EC%82%AC%EC%9A%A9/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/devops-ingress/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/php-php-%EA%B0%95%EC%9D%98/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/next.js-data-fetching/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/next.js-container-%EC%95%88%EC%97%90%EC%84%9C-pm2%EB%A1%9C-next.js-%EC%95%B1-%EC%8B%A4%ED%96%89%ED%95%98%EA%B8%B0/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/next.js-next-image-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/next.js-Automatic-Static-%EC%B5%9C%EC%A0%81%ED%99%94/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/next.js-Satic-HTML-Export/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/next.js-Script-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/next.js-production-%EB%B0%B0%ED%8F%AC-%EC%A0%84-%EC%B2%B4%ED%81%AC%EB%A6%AC%EC%8A%A4%ED%8A%B8/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/next.js-Deployment/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/next.js-Output-File-Tracing/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/next.js-Compiler/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/react-SWR/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/web-module-federation/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/web-Headless-CMS/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/react-vite/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/nextjs-case-study/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/appdir/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/typescript5/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/js-memory-leak/ +2023-06-06T22:59:21+09:00 + + +https://seongil-shin.github.io/posts/typescript-extends/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/react-state/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/pagehide/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/bfcache/ +2023-05-30T21:53:32+09:00 + + +https://seongil-shin.github.io/posts/deep-link/ +2023-06-03T19:34:28+09:00 + + +https://seongil-shin.github.io/posts/index/ +2023-06-03T16:00:00+09:00 + + +https://seongil-shin.github.io/posts/video-notplaying/ +2023-06-06T22:59:21+09:00 + + +https://seongil-shin.github.io/posts/es6/ +2023-06-12T23:00:00+09:00 + + +https://seongil-shin.github.io/posts/ios-16-5-video/ +2023-06-18T18:00:00+09:00 + + +https://seongil-shin.github.io/posts/react-%EB%92%A4%EB%A1%9C%EA%B0%80%EA%B8%B0-%EB%A7%89%EA%B8%B0/ +2023-07-29T22:00:00+09:00 + + +https://seongil-shin.github.io/posts/next-props-drilling/ +2023-08-05T16:00:00+09:00 + + +https://seongil-shin.github.io/posts/next-caching/ +2023-08-13T22:00:00+09:00 + + +https://seongil-shin.github.io/posts/next.js-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%B5%9C%EC%A0%81%ED%99%94-%EA%B3%BC%EC%A0%95/ +2023-10-22T16:00:00+09:00 + + +https://seongil-shin.github.io/posts/%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8-1%EC%9E%A5-%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98-%EC%B2%A0%ED%95%99/ +2024-09-01T15:56:31+09:00 + + +https://seongil-shin.github.io/posts/%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8-2%EC%9E%A5-%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98-%EC%A0%91%EA%B7%BC%EB%B2%95/ +2024-07-21T20:57:22+09:00 + + +https://seongil-shin.github.io/posts/%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8-3%EC%9E%A5-%EA%B8%B0%EB%B3%B8%EB%8F%84%EA%B5%AC/ +2024-08-13T21:26:58+09:00 + + +https://seongil-shin.github.io/posts/%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8-4%EC%9E%A5-%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98-%ED%8E%B8%EC%A7%91%EC%A6%9D/ +2024-08-02T22:08:35+09:00 + + +https://seongil-shin.github.io/posts/%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8-5%EC%9E%A5-%EA%B5%AC%EB%B6%80%EB%9F%AC%EC%A7%80%EA%B1%B0%EB%82%98-%EB%B6%80%EB%9F%AC%EC%A7%80%EA%B1%B0%EB%82%98/ +2024-08-14T12:23:50+09:00 + + +https://seongil-shin.github.io/posts/%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8-6%EC%9E%A5-%EB%8F%99%EC%8B%9C%EC%84%B1/ +2024-08-04T18:04:36+09:00 + + +https://seongil-shin.github.io/posts/%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8-7%EC%9E%A5-%EC%BD%94%EB%94%A9%ED%95%98%EB%8A%94-%EB%8F%99%EC%95%88/ +2024-08-07T17:31:47+09:00 + + +https://seongil-shin.github.io/posts/%EC%9E%98%EB%AA%BB%EB%90%9C-DRY/ +2024-08-06T11:17:27+09:00 + + +https://seongil-shin.github.io/posts/%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8-8%EC%9E%A5-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%A0%84%EC%97%90/ +2024-08-14T12:23:50+09:00 + + +https://seongil-shin.github.io/posts/%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8-9%EC%9E%A5-%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8/ +2024-08-13T21:26:58+09:00 + + +https://seongil-shin.github.io/posts/%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8-%EC%A0%84%EC%B2%B4-%ED%9B%84%EA%B8%B0/ +2024-09-01T15:56:31+09:00 + + +https://seongil-shin.github.io/posts/Esbuild-to-reduce-build-times-by-90/ +2024-09-03T21:54:55+09:00 + + +https://seongil-shin.github.io/posts/How-I-Use-AI/ +2024-08-31T19:45:24+09:00 + + +https://seongil-shin.github.io/posts/JS-bundler,-package-manager,-compiler/ +2024-08-31T19:45:24+09:00 + + +https://seongil-shin.github.io/categories/ +2024-09-03T21:55:31+09:00 + + +https://seongil-shin.github.io/archives/ +2024-09-03T21:55:31+09:00 + + +https://seongil-shin.github.io/tags/ +2024-09-03T21:55:31+09:00 + + +https://seongil-shin.github.io/temp/2023-04-03-next-13.html + + +https://seongil-shin.github.io/temp/2023-04-22-value-reference.html + + +https://seongil-shin.github.io/temp/2023-05-30-browser-rendering.html + + +https://seongil-shin.github.io/temp/2023-05-30-http.html + + +https://seongil-shin.github.io/temp/2023-05-30-optimization.html + + +https://seongil-shin.github.io/temp/2023-05-30-pub-sub.html + + +https://seongil-shin.github.io/temp/2023-05-31-browser-storage.html + + +https://seongil-shin.github.io/ + + +https://seongil-shin.github.io/tags/firebase/ + + +https://seongil-shin.github.io/tags/css/ + + +https://seongil-shin.github.io/tags/graphql/ + + +https://seongil-shin.github.io/tags/browser/ + + +https://seongil-shin.github.io/tags/javascript/ + + +https://seongil-shin.github.io/tags/redux/ + + +https://seongil-shin.github.io/tags/react/ + + +https://seongil-shin.github.io/tags/oop/ + + +https://seongil-shin.github.io/tags/fp/ + + +https://seongil-shin.github.io/tags/rest/ + + +https://seongil-shin.github.io/tags/tdd/ + + +https://seongil-shin.github.io/tags/architecture/ + + +https://seongil-shin.github.io/tags/%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0/ + + +https://seongil-shin.github.io/tags/network/ + + +https://seongil-shin.github.io/tags/os/ + + +https://seongil-shin.github.io/tags/db/ + + +https://seongil-shin.github.io/tags/singleton/ + + +https://seongil-shin.github.io/tags/summary/ + + +https://seongil-shin.github.io/tags/declare/ + + +https://seongil-shin.github.io/tags/web/ + + +https://seongil-shin.github.io/tags/spring/ + + +https://seongil-shin.github.io/tags/sidecar/ + + +https://seongil-shin.github.io/tags/istio/ + + +https://seongil-shin.github.io/tags/pinpoint/ + + +https://seongil-shin.github.io/tags/java/ + + +https://seongil-shin.github.io/tags/cors/ + + +https://seongil-shin.github.io/tags/design/ + + +https://seongil-shin.github.io/tags/kubernetes/ + + +https://seongil-shin.github.io/tags/kafka/ + + +https://seongil-shin.github.io/tags/next-js/ + + +https://seongil-shin.github.io/tags/webpack/ + + +https://seongil-shin.github.io/tags/vite/ + + +https://seongil-shin.github.io/tags/typescript/ + + +https://seongil-shin.github.io/tags/issue/ + + +https://seongil-shin.github.io/tags/issues/ + + +https://seongil-shin.github.io/tags/cache/ + + +https://seongil-shin.github.io/tags/study/ + + +https://seongil-shin.github.io/tags/review/ + + +https://seongil-shin.github.io/categories/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98/ + + +https://seongil-shin.github.io/categories/beackjoon/ + + +https://seongil-shin.github.io/categories/study/ + + +https://seongil-shin.github.io/categories/node/ + + +https://seongil-shin.github.io/categories/react/ + + +https://seongil-shin.github.io/categories/firebase/ + + +https://seongil-shin.github.io/categories/projects/ + + +https://seongil-shin.github.io/categories/css/ + + +https://seongil-shin.github.io/categories/graphql/ + + +https://seongil-shin.github.io/categories/react-native/ + + +https://seongil-shin.github.io/categories/web/ + + +https://seongil-shin.github.io/categories/typescript/ + + +https://seongil-shin.github.io/categories/flutter/ + + +https://seongil-shin.github.io/categories/javascript/ + + +https://seongil-shin.github.io/categories/clean-code/ + + +https://seongil-shin.github.io/categories/design-pattern/ + + +https://seongil-shin.github.io/categories/software-engineering/ + + +https://seongil-shin.github.io/categories/datastructure/ + + +https://seongil-shin.github.io/categories/network/ + + +https://seongil-shin.github.io/categories/os/ + + +https://seongil-shin.github.io/categories/database/ + + +https://seongil-shin.github.io/categories/cs/ + + +https://seongil-shin.github.io/categories/algorithm/ + + +https://seongil-shin.github.io/categories/forkie/ + + +https://seongil-shin.github.io/categories/summary/ + + +https://seongil-shin.github.io/categories/db/ + + +https://seongil-shin.github.io/categories/java/ + + +https://seongil-shin.github.io/categories/spring/ + + +https://seongil-shin.github.io/categories/devops/ + + +https://seongil-shin.github.io/categories/php/ + + +https://seongil-shin.github.io/categories/next-js/ + + +https://seongil-shin.github.io/categories/js/ + + +https://seongil-shin.github.io/categories/study/ + + +https://seongil-shin.github.io/categories/%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8/ + + +https://seongil-shin.github.io/categories/article/ + + +https://seongil-shin.github.io/page2/ + + +https://seongil-shin.github.io/page3/ + + +https://seongil-shin.github.io/page4/ + + +https://seongil-shin.github.io/page5/ + + +https://seongil-shin.github.io/page6/ + + +https://seongil-shin.github.io/page7/ + + +https://seongil-shin.github.io/page8/ + + +https://seongil-shin.github.io/page9/ + + +https://seongil-shin.github.io/page10/ + + +https://seongil-shin.github.io/page11/ + + +https://seongil-shin.github.io/page12/ + + +https://seongil-shin.github.io/page13/ + + +https://seongil-shin.github.io/page14/ + + +https://seongil-shin.github.io/page15/ + + +https://seongil-shin.github.io/page16/ + + +https://seongil-shin.github.io/page17/ + + +https://seongil-shin.github.io/page18/ + + +https://seongil-shin.github.io/page19/ + + +https://seongil-shin.github.io/page20/ + + +https://seongil-shin.github.io/page21/ + + +https://seongil-shin.github.io/page22/ + + +https://seongil-shin.github.io/page23/ + + +https://seongil-shin.github.io/page24/ + + +https://seongil-shin.github.io/page25/ + + +https://seongil-shin.github.io/page26/ + + +https://seongil-shin.github.io/page27/ + + +https://seongil-shin.github.io/page28/ + + +https://seongil-shin.github.io/page29/ + + +https://seongil-shin.github.io/page30/ + + +https://seongil-shin.github.io/page31/ + + +https://seongil-shin.github.io/page32/ + + +https://seongil-shin.github.io/page33/ + + +https://seongil-shin.github.io/page34/ + + +https://seongil-shin.github.io/page35/ + + +https://seongil-shin.github.io/page36/ + + diff --git a/sw.js b/sw.js new file mode 100644 index 000000000..a4a88e584 --- /dev/null +++ b/sw.js @@ -0,0 +1 @@ +self.importScripts('/assets/js/data/swcache.js'); const cacheName = 'chirpy-20240903.2156'; function verifyDomain(url) { for (const domain of allowedDomains) { const regex = RegExp(`^http(s)?:\/\/${domain}\/`); if (regex.test(url)) { return true; } } return false; } function isExcluded(url) { for (const item of denyUrls) { if (url === item) { return true; } } return false; } self.addEventListener('install', e => { self.skipWaiting(); e.waitUntil( caches.open(cacheName).then(cache => { return cache.addAll(resource); }) ); }); self.addEventListener('fetch', event => { event.respondWith( caches.match(event.request) .then(response => { if (response) { return response; } return fetch(event.request) .then(response => { const url = event.request.url; if (event.request.method !== 'GET' || !verifyDomain(url) || isExcluded(url)) { return response; } /* see: */ let responseToCache = response.clone(); caches.open(cacheName) .then(cache => { /* console.log('[sw] Caching new resource: ' + event.request.url); */ cache.put(event.request, responseToCache); }); return response; }); }) ); }); self.addEventListener('activate', e => { e.waitUntil( caches.keys().then(keyList => { return Promise.all( keyList.map(key => { if(key !== cacheName) { return caches.delete(key); } }) ); }) ); }); diff --git a/tags/architecture/index.html b/tags/architecture/index.html new file mode 100644 index 000000000..8bfc32051 --- /dev/null +++ b/tags/architecture/index.html @@ -0,0 +1 @@ + architecture | 디피의 개발일지
Home Tags architecture
Tag
Cancel
diff --git a/tags/browser/index.html b/tags/browser/index.html new file mode 100644 index 000000000..4bac2608b --- /dev/null +++ b/tags/browser/index.html @@ -0,0 +1 @@ + browser | 디피의 개발일지
Home Tags browser
Tag
Cancel
diff --git a/tags/cache/index.html b/tags/cache/index.html new file mode 100644 index 000000000..fde5393c6 --- /dev/null +++ b/tags/cache/index.html @@ -0,0 +1 @@ + cache | 디피의 개발일지
Home Tags cache
Tag
Cancel
diff --git a/tags/cors/index.html b/tags/cors/index.html new file mode 100644 index 000000000..d0dfce57b --- /dev/null +++ b/tags/cors/index.html @@ -0,0 +1 @@ + cors | 디피의 개발일지
Home Tags cors
Tag
Cancel
diff --git a/tags/css/index.html b/tags/css/index.html new file mode 100644 index 000000000..47c6235f5 --- /dev/null +++ b/tags/css/index.html @@ -0,0 +1 @@ + css | 디피의 개발일지
Home Tags css
Tag
Cancel
diff --git a/tags/db/index.html b/tags/db/index.html new file mode 100644 index 000000000..5ab0637dc --- /dev/null +++ b/tags/db/index.html @@ -0,0 +1 @@ + db | 디피의 개발일지
Home Tags db
Tag
Cancel
diff --git a/tags/declare/index.html b/tags/declare/index.html new file mode 100644 index 000000000..6f560400f --- /dev/null +++ b/tags/declare/index.html @@ -0,0 +1 @@ + declare | 디피의 개발일지
Home Tags declare
Tag
Cancel
diff --git a/tags/design/index.html b/tags/design/index.html new file mode 100644 index 000000000..188a7fd30 --- /dev/null +++ b/tags/design/index.html @@ -0,0 +1 @@ + design | 디피의 개발일지
Home Tags design
Tag
Cancel
diff --git a/tags/firebase/index.html b/tags/firebase/index.html new file mode 100644 index 000000000..6d9cf2120 --- /dev/null +++ b/tags/firebase/index.html @@ -0,0 +1 @@ + firebase | 디피의 개발일지
Home Tags firebase
Tag
Cancel
diff --git a/tags/fp/index.html b/tags/fp/index.html new file mode 100644 index 000000000..df1afe827 --- /dev/null +++ b/tags/fp/index.html @@ -0,0 +1 @@ + fp | 디피의 개발일지
Home Tags fp
Tag
Cancel
diff --git a/tags/graphql/index.html b/tags/graphql/index.html new file mode 100644 index 000000000..893dd492d --- /dev/null +++ b/tags/graphql/index.html @@ -0,0 +1 @@ + GraphQL | 디피의 개발일지
Home Tags GraphQL
Tag
Cancel
diff --git a/tags/index.html b/tags/index.html new file mode 100644 index 000000000..8f4e92b25 --- /dev/null +++ b/tags/index.html @@ -0,0 +1 @@ + Tags | 디피의 개발일지
Home Tags
Tags
Cancel
diff --git a/tags/issue/index.html b/tags/issue/index.html new file mode 100644 index 000000000..2aa0815ae --- /dev/null +++ b/tags/issue/index.html @@ -0,0 +1 @@ + issue | 디피의 개발일지
Home Tags issue
Tag
Cancel
diff --git a/tags/issues/index.html b/tags/issues/index.html new file mode 100644 index 000000000..ff6221d80 --- /dev/null +++ b/tags/issues/index.html @@ -0,0 +1 @@ + issues | 디피의 개발일지
Home Tags issues
Tag
Cancel
diff --git a/tags/istio/index.html b/tags/istio/index.html new file mode 100644 index 000000000..b18fcf59c --- /dev/null +++ b/tags/istio/index.html @@ -0,0 +1 @@ + istio | 디피의 개발일지
Home Tags istio
Tag
Cancel
diff --git a/tags/java/index.html b/tags/java/index.html new file mode 100644 index 000000000..418410eee --- /dev/null +++ b/tags/java/index.html @@ -0,0 +1 @@ + java | 디피의 개발일지
Home Tags java
Tag
Cancel
diff --git a/tags/javascript/index.html b/tags/javascript/index.html new file mode 100644 index 000000000..1e3363182 --- /dev/null +++ b/tags/javascript/index.html @@ -0,0 +1 @@ + javascript | 디피의 개발일지
Home Tags javascript
Tag
Cancel
diff --git a/tags/kafka/index.html b/tags/kafka/index.html new file mode 100644 index 000000000..db8002032 --- /dev/null +++ b/tags/kafka/index.html @@ -0,0 +1 @@ + kafka | 디피의 개발일지
Home Tags kafka
Tag
Cancel
diff --git a/tags/kubernetes/index.html b/tags/kubernetes/index.html new file mode 100644 index 000000000..dc6ba58be --- /dev/null +++ b/tags/kubernetes/index.html @@ -0,0 +1 @@ + kubernetes | 디피의 개발일지
Home Tags kubernetes
Tag
Cancel
diff --git a/tags/network/index.html b/tags/network/index.html new file mode 100644 index 000000000..88409cedc --- /dev/null +++ b/tags/network/index.html @@ -0,0 +1 @@ + network | 디피의 개발일지
Home Tags network
Tag
Cancel
diff --git a/tags/next-js/index.html b/tags/next-js/index.html new file mode 100644 index 000000000..50f28bece --- /dev/null +++ b/tags/next-js/index.html @@ -0,0 +1 @@ + next.js | 디피의 개발일지
Home Tags next.js
Tag
Cancel
diff --git a/tags/oop/index.html b/tags/oop/index.html new file mode 100644 index 000000000..4d86c4dfa --- /dev/null +++ b/tags/oop/index.html @@ -0,0 +1 @@ + oop | 디피의 개발일지
Home Tags oop
Tag
Cancel
diff --git a/tags/os/index.html b/tags/os/index.html new file mode 100644 index 000000000..1159be62c --- /dev/null +++ b/tags/os/index.html @@ -0,0 +1 @@ + os | 디피의 개발일지
Home Tags os
Tag
Cancel
diff --git a/tags/pinpoint/index.html b/tags/pinpoint/index.html new file mode 100644 index 000000000..504c0d3ec --- /dev/null +++ b/tags/pinpoint/index.html @@ -0,0 +1 @@ + pinpoint | 디피의 개발일지
Home Tags pinpoint
Tag
Cancel
diff --git a/tags/react/index.html b/tags/react/index.html new file mode 100644 index 000000000..1ebf5be36 --- /dev/null +++ b/tags/react/index.html @@ -0,0 +1 @@ + react | 디피의 개발일지
Home Tags react
Tag
Cancel
diff --git a/tags/redux/index.html b/tags/redux/index.html new file mode 100644 index 000000000..73c8bfc5b --- /dev/null +++ b/tags/redux/index.html @@ -0,0 +1 @@ + redux | 디피의 개발일지
Home Tags redux
Tag
Cancel
diff --git a/tags/rest/index.html b/tags/rest/index.html new file mode 100644 index 000000000..58cdec5b7 --- /dev/null +++ b/tags/rest/index.html @@ -0,0 +1 @@ + rest | 디피의 개발일지
Home Tags rest
Tag
Cancel
diff --git a/tags/review/index.html b/tags/review/index.html new file mode 100644 index 000000000..f836d4759 --- /dev/null +++ b/tags/review/index.html @@ -0,0 +1 @@ + #review | 디피의 개발일지
Home Tags #review
Tag
Cancel
diff --git a/tags/sidecar/index.html b/tags/sidecar/index.html new file mode 100644 index 000000000..45f628b7d --- /dev/null +++ b/tags/sidecar/index.html @@ -0,0 +1 @@ + sidecar | 디피의 개발일지
Home Tags sidecar
Tag
Cancel
diff --git a/tags/singleton/index.html b/tags/singleton/index.html new file mode 100644 index 000000000..3320c6b23 --- /dev/null +++ b/tags/singleton/index.html @@ -0,0 +1 @@ + singleton | 디피의 개발일지
Home Tags singleton
Tag
Cancel
diff --git a/tags/spring/index.html b/tags/spring/index.html new file mode 100644 index 000000000..31e6984f5 --- /dev/null +++ b/tags/spring/index.html @@ -0,0 +1 @@ + spring | 디피의 개발일지
Home Tags spring
Tag
Cancel
diff --git a/tags/study/index.html b/tags/study/index.html new file mode 100644 index 000000000..ca55527ba --- /dev/null +++ b/tags/study/index.html @@ -0,0 +1 @@ + #study | 디피의 개발일지
Home Tags #study
Tag
Cancel
diff --git a/tags/summary/index.html b/tags/summary/index.html new file mode 100644 index 000000000..ea0c78f88 --- /dev/null +++ b/tags/summary/index.html @@ -0,0 +1 @@ + summary | 디피의 개발일지
Home Tags summary
Tag
Cancel
diff --git a/tags/tdd/index.html b/tags/tdd/index.html new file mode 100644 index 000000000..f18687106 --- /dev/null +++ b/tags/tdd/index.html @@ -0,0 +1 @@ + tdd | 디피의 개발일지
Home Tags tdd
Tag
Cancel
diff --git a/tags/typescript/index.html b/tags/typescript/index.html new file mode 100644 index 000000000..5f5e62a8c --- /dev/null +++ b/tags/typescript/index.html @@ -0,0 +1 @@ + typescript | 디피의 개발일지
Home Tags typescript
Tag
Cancel
diff --git a/tags/vite/index.html b/tags/vite/index.html new file mode 100644 index 000000000..46bf7d973 --- /dev/null +++ b/tags/vite/index.html @@ -0,0 +1 @@ + vite | 디피의 개발일지
Home Tags vite
Tag
Cancel
diff --git a/tags/web/index.html b/tags/web/index.html new file mode 100644 index 000000000..79ce23174 --- /dev/null +++ b/tags/web/index.html @@ -0,0 +1 @@ + web | 디피의 개발일지
Home Tags web
Tag
Cancel
diff --git a/tags/webpack/index.html b/tags/webpack/index.html new file mode 100644 index 000000000..742275f90 --- /dev/null +++ b/tags/webpack/index.html @@ -0,0 +1 @@ + webpack | 디피의 개발일지
Home Tags webpack
Tag
Cancel
diff --git "a/tags/\354\236\220\353\243\214\352\265\254\354\241\260/index.html" "b/tags/\354\236\220\353\243\214\352\265\254\354\241\260/index.html" new file mode 100644 index 000000000..d3da50797 --- /dev/null +++ "b/tags/\354\236\220\353\243\214\352\265\254\354\241\260/index.html" @@ -0,0 +1 @@ + 자료구조 | 디피의 개발일지
Home Tags 자료구조
Tag
Cancel
diff --git a/temp/2023-04-03-next-13.html b/temp/2023-04-03-next-13.html new file mode 100644 index 000000000..c06464165 --- /dev/null +++ b/temp/2023-04-03-next-13.html @@ -0,0 +1,46 @@ +

app directory

+ +

Layout으로 레이아웃 잡고, 데이터 필요없는 거 먼저 렌더

+ +

streaming으로 데이터 필요한 컴포넌트 렌더

+ +

streaming에 사용하는 컴포넌트는 Server component (+ async Server component)

+ +

https://nextjs.org/blog/next-13#layouts

+ +
    +
  • https://beta.nextjs.org/docs/routing/fundamentals
  • +
+ +

https://nextjs.org/blog/next-13#streaming

+ +
    +
  • https://beta.nextjs.org/docs/data-fetching/fundamentals
  • +
+ +

서버 컴포넌트

+ +
    +
  • app dir 밑의 컴포넌트는 뭐든 RSC임. 따라서 별도의 작업이 필요없음.
  • +
  • 클라이언트 컴포넌트를 사용하는 방법도 편함.
  • +
  • 아직 베타라 사용은 안하지만 내용은 좀 더 자세히 공부해봐도 좋을 거 같다.
  • +
  • image-20230406191056879
  • +
+ +

turbopack

+ +

next/image

+ +

Lighthouse 검사 결과 이미지, 영상 로딩속도가 문제였음.

+ +

최신 포맷을 사용하는 걸 추천했는데, next/image 컴포넌트를 사용하라는 조언을 받음. 자동으로 모던 웹 포맷을 사용한다고.

+ +

그런데 브라우저 호환성 문제도 있기에 그 부분에서 조사가 좀 더 필요함.

+ +

이미지를 주소로 불러오는 경우, width/height를 지정하든가 fill 속성을 사용해야함.

+ +

but, 마크업을 받아서 사용하는 입장에서 css를 조작하기엔 부담스러워서 안쓰고 있다.

+ +

OG Image Generation

+ +

https://nextjs.org/blog/next-13#og-image-generation

diff --git a/temp/2023-04-22-value-reference.html b/temp/2023-04-22-value-reference.html new file mode 100644 index 000000000..edbc07df6 --- /dev/null +++ b/temp/2023-04-22-value-reference.html @@ -0,0 +1,7 @@ +

원시타입 (Primitive)

+ +

passed by value가 일어나는 5가지 데이터 타입(Boolean, Null, undefined, string, number)

+ +

출처

+ +

https://velog.io/@jakeseo_me/2019-04-01-1904-%EC%9E%91%EC%84%B1%EB%90%A8-2bjty7tuuf#%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%97%90%EC%84%9C-%EA%B0%92value%EA%B3%BC-%EC%B0%B8%EC%A1%B0reference

diff --git a/temp/2023-05-30-browser-rendering.html b/temp/2023-05-30-browser-rendering.html new file mode 100644 index 000000000..c487d0a7b --- /dev/null +++ b/temp/2023-05-30-browser-rendering.html @@ -0,0 +1,39 @@ +

렌더링 순서

+ +

웹 페이지에 접속하면 브라우저는 제일 먼저 해당 페이지의 html을 불러온다. 이후 가져온 html을 읽으며 다음과 같은 렌더링 과정을 수행한다.

+ +
    +
  1. Parsing : HTML과 CSS를 파싱해서 각각 Tree(DOM 트리, CSSOM 트리)를 만든다
  2. +
  3. Style : 두 Tree를 결합하여 Rendering Tree를 만든다
  4. +
  5. Layout : Rendering Tree에서 각 노드의 위치와 크기를 계산한다
  6. +
  7. Paint : 각 노드를 화면 상의 실제 픽셀로 변환하고 레이어를 만든다
  8. +
  9. Composite : 레이어를 합성하여 실제 화면에 나타낸다
  10. +
+ +


+ +

html parsing 차단

+ +

브라우저가 html을 파싱하다가 head 태그 내 위치한 script 태그를 만났을 때 파싱이 차단되는 경우가 있다. 이는 script로 불러와지는 코드가 html 을 변경시킬 수 있기에 막는 것이다. 이러한 html 파싱 차단을 막기 위해선 다음과 같은 방법이 있다.

+ +
    +
  • +

    scriptbody 태그 아래에 삽입하여 html 파싱이 종료된 이후 로드

    +
  • +
  • +

    async, defer 속성 사용

    + +
      +
    • +

      async : 로드할 때는 차단하지 않음. 실행할땐 차단함

      +
    • +
    • +

      defer : 로드, 실행할 때 모두 차단하지 않음.

      +
    • +
    +
  • +
+ +

CSS도 간접적으로 차단할 수 있다. script가 페이지 스타일에 영향을 줄 수 있기에 css 파싱이 완료되고 js를 실행하도록하여, 간접적으로 html 파싱이 차단되는 시간이 길어질 수 있다. (링크)

+ +

https://web-now-rbviiass9-calibreapp.vercel.app/_next/image?url=%2Fimages%2Fblog%2Fcss-performance%2Fparser-blocking-css.png&w=1920&q=75

diff --git a/temp/2023-05-30-http.html b/temp/2023-05-30-http.html new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/temp/2023-05-30-http.html @@ -0,0 +1 @@ + diff --git a/temp/2023-05-30-optimization.html b/temp/2023-05-30-optimization.html new file mode 100644 index 000000000..65efbd71b --- /dev/null +++ b/temp/2023-05-30-optimization.html @@ -0,0 +1,71 @@ +

프론트엔드에서 최적화를 할 수 있는 선택지를 모아놓은 글이다. 순서는 적용하기 비교적 간편한 순으로 정했다.

+ +

Resource

+ +

이미지/영상 압축

+ +

lazy loading

+ +

페이지내 모든 이미지/영상이 페이지가 로드되는 순간에 필요하지 않을 수 있다. 따라서 유저가 보는 뷰포트 근처에 있는 이미지/영상만 로드하여 로딩을 빠르게하는 기법을 lazy loading이라고 한다.

+ +

구현법은 다음과 같다.

+ +
    +
  1. img 태그에서 loading="lazy"를 주어 적용. +
      +
    • 단 구형 브라우저에서는 동작하지 않으므로 polyfill 사용
    • +
    • picture 태그에서는 img에서만 설정하면 된다.
    • +
    +
  2. +
  3. 자동재생이 안되도 되는 videopreloadnone이나 metadata로 설정할 수 있음.
  4. +
  5. lazy-loading 라이브러리나 js로 뷰포트 근처의 이미지, 비디오 태그에 src 부여 (처음엔 빈상태로 둠) +
      +
    • 자동재생이 되어야하는 video 에서 사용
    • +
    +
  6. +
+ +


+ +

Javascript

+ +

lodash - debounce, throttle

+ +

스크롤, resize 이벤트 콜백함수에서 무거운 작업을 수행하면, 성능에 악영향이 나타날 수 있다. 이때 lodash의 debounce, throttle를 사용하여 이벤트 콜백 호출 횟수를 조절하는 방법이 있다.

+ +


+ +

CSS

+ +

animation 최적화

+ +
    +
  1. CSS 애니메이션이 느릴 땐 브라우저 렌더링 과정 중 Composite 단계에 속하는 tranform 또는 opacity로 애니메이션이 동작할 수 있도록 변경한다.
  2. +
  3. css에 will-change 속성을 사용하여 브라우저에게 일어날 변화에 대해 알려줌으로써 최적화할 수 있다.
  4. +
  5. contain 속성을 사용하여 해당 엘리먼트의 변화가 다른 dom에 영향을 끼치지 않는다는 걸 명시할 수 있다.
  6. +
+ +

상세

+ +


+ +

React

+ +

useMemo, useCallback

+ +


+ +

Architecture

+ +

SSR 사용

+ +

만약 CSR을 사용중이라면 SSR로 전환하여 사용자가 처음 보는 화면을 더 빠르게 띄워줄 수 있다.

+ +

CSR은 리액트가 포함된 자바스크립트를 다운 받고, 리액트를 실행시켜 화면을 렌더링한다. 이때 리액트는 앱 전체를 실행시키기에, 리액트 코드가 방대하다면 그만큼 사용자는 빈 화면에 방치된다.

+ +

SSR은 서버에서 미리 첫번째 화면을 생성하고 그 생성된 첫번째 화면의 html, css를 사용자에게 보내는 방법. 사용자 브라우저는 이후 js를 다운 받고 리액트를 실행시켜 인터랙션이 가능하게 한다. 따라서 CSR을 사용하면 사용자가 첫 화면을 보는 시점이 SSR에 비해 늦어지게 된다. 또한 SEO에도 문제가 있다. (검색엔진의 JS 실행에는 한계가 있기에)

+ +

SSR에도 단점이 있다. 첫페이지를 구성한 이후 다른 페이지로 넘어갈 때, CSR에서는 그 다른 페이지를 렌더링하기 위한 코드가 이미 있고 필요한 부분만 다시 렌더링하면 되기에 다른 페이지로 넘어갈 때는 CSR이 더 빠르다. 이를 해결하기 위해 CSR와 SSR을 결합하는 방식을 채택한다. (next.js의 next/link 컴포넌트 참고)

+ +

또 기존 리액트에서 SSR에서는 리액트 코드를 입히는 hydration 동작을 앱 전체 단위로 진행시켜서 사용자가 최초로 인터랙션을 하는데까지 시간이 오래 걸렸는데, 이번에 react 18 업데이트에 포함된 Suspense API를 통해 컴포넌트 단위로 hydration을 진행시켜 우선도가 높은 컴포넌트부터 먼저 인터랙션이 가능했다 (상세)

+ diff --git a/temp/2023-05-30-pub-sub.html b/temp/2023-05-30-pub-sub.html new file mode 100644 index 000000000..5ad3fae0d --- /dev/null +++ b/temp/2023-05-30-pub-sub.html @@ -0,0 +1,3 @@ +

면접 공통질문에 위치

+ +

Pub/sub 원리 : 비동기식 메시징 패턴. publisher가 task를 publish하면, 그 publisher를 구독하고 있는 subscriber가 task를 받는다. 내부적으로 웹 소켓을 사용하고 있음.

diff --git a/temp/2023-05-31-browser-storage.html b/temp/2023-05-31-browser-storage.html new file mode 100644 index 000000000..9f415c28c --- /dev/null +++ b/temp/2023-05-31-browser-storage.html @@ -0,0 +1,30 @@ +

브라우저에서 웹페이지를 위해 제공하는 저장소과 특징은 다음과 같다.

+ +
    +
  • Localstorage +
      +
    • 직접 지울 때까지 남아있다.
    • +
    • 같은 도메인은 같은 localstorage를 가짐
    • +
    +
  • +
  • SessionStorage +
      +
    • session 기간에만 데이터를 저장함.(보안에 유리).
    • +
    • 같은 도메인을 여러개 열어도 다른 탭에 있으면 별도의 storage를 가짐
    • +
    +
  • +
  • Cookie +
      +
    • 서버에서 set-cookie를 통해 저장한 값. (js로도 값 접근/저장 가능)
    • +
    • credential 옵션을 주면 API 요청 시에 헤더에 담겨 보내진다.
    • +
    • 옵션 +
        +
      • httpOnly : js로는 접근 불가.
      • +
      • secure : https 통신에서만 사용가능
      • +
      • expire : 만료일을 정해 스스로 사라지게 설정가능함.
      • +
      • path : 특정 path에서만 사용가능하게 설정
      • +
      +
    • +
    +
  • +
diff --git a/temp/assets/image-20230406191056879.png b/temp/assets/image-20230406191056879.png new file mode 100644 index 000000000..ee85cecca Binary files /dev/null and b/temp/assets/image-20230406191056879.png differ diff --git a/temp/assets/image-20230421235917311.png b/temp/assets/image-20230421235917311.png new file mode 100644 index 000000000..d90c5a2ee Binary files /dev/null and b/temp/assets/image-20230421235917311.png differ diff --git a/temp/assets/image.png b/temp/assets/image.png new file mode 100644 index 000000000..8d74bcac4 Binary files /dev/null and b/temp/assets/image.png differ diff --git a/temp/assets/sequential-parallel-data-fetching.png b/temp/assets/sequential-parallel-data-fetching.png new file mode 100644 index 000000000..c6149873f Binary files /dev/null and b/temp/assets/sequential-parallel-data-fetching.png differ