📘

【チームプロジェクトReview4】オブジェクト指向のための努力② BEM, DRY原則(Optional, Lombokなど)

2024/02/15に公開

はじめに

今日は、プロジェクトのサーバーの設計とどのふうにオブジェクト指向のための努力をしたかについてお話したいと思います。

BEM

BEMを通して、CSSをクラス化し、変数を設定しつつ、重複コードを減らし、再活用できる仕組みを作る努力をしました。当時、htmlのみ約80~90ファイルになる可能性が高かったので、マークアップができない人も簡単にデザインができる方法は何か工夫し、BEMTailwind CSSからヒントを得ました。

以下がプロセスになります。


全域で使われるデザイン要素はグロバールに定義し、変数も活用する。この段階では要素単位にセレクターを適用します。

style.css
/* グロバール設定/
@import "base.css";
base.css
:root {
    /*サイトのカラー選定*/
    --color-primary: var(--color-white);
    --color-primary-variant: var(--color-dark-gray);
    --color-accent: var(--color-yellow);
    --color-error: var(--color-red);
    --color-text: var(--color-black);

    /*カラー選定*/
    --color-white: #ffffff;
    --color-black: #050a13;
    --color-yellow: #ffb94f;
    --color-dark-gray: #666666;
    --color-light-gray: #cccccc;
    --color-red: #F80D0D;
}
* {
    box-sizing: border-box;
    font-family: 'Noto Sans KR', sans-serif;
    -ms-overflow-style: none; /* 인터넷 익스플로러 */
    scrollbar-width: none; /* 파이어폭스 */
}

body{
    margin: 0;
    padding: 0;
}
body::-webkit-scrollbar {
    display: none;
}

h1,h2,h3,h4,h5,h6,
ul {
    margin: 0;
    padding: 0;
}
.
.
.


全域で使われるコンポーネント(枠)を決め、作成する。(例:header, buttonタグのデザイン)

header.css
header{
    display: flex;
    justify-content: space-between;
    align-items: center;
    position: fixed;
    top:0;
    width: 100%;
    height: 3.4375rem;
    padding: 0 15rem;
    background-color: var(--color-primary);
    box-shadow: rgba(0, 0, 0,0.24) 0 3px 8px;
    z-index:10;
}
.header__logo{
    font-size: 1.5rem;
    font-weight: bold;
}
.header__nav{
    font-size: 0.75rem;
}
.
.
.
custom.css
.
.
.btn{
    display: block;
    width: 23rem;
    height: 3rem;
    margin: 0.5rem 0 1rem 0;
    font-size: 1.125rem;
    font-weight: bold;
    border: solid black 1px;
    border-radius: 3px;
}

.btn--lg{
    width: 26rem;
}

.btn--mi{
    margin:0;
    display: inline;
    width: 6rem;
    height: 2.75rem;
    font-size: 0.75rem;
    border-radius: 10px;
}

.btn--xs{
    margin:0;
    display: inline;
    width: 2.5rem;
    height: 1.5rem;
    font-size: 0.6rem;
    border-radius: 10px;
}


.btn--black{
    background-color: var(--color-text);
    color: var(--color-accent);
}
.btn--white{
    background-color: var(--color-primary);
    color: var(--color-text);
}
.
.


③グロバールに設定したbase.css, コンポーネント化されたcssを変更する場合、pagesフォルダーに分けて保管する。

item-detail.css
.
.
.
.btn--aside{
    display: block;
    border-radius: 0;
    font-size: 0.75rem;
    transition: all 200ms ease-in;
}
.btn--aside:hover{
    background-color: var(--color-text);
    color: var(--color-accent);
}
.btn--no-border-bottom{
    border-width: 1px 1px 0 1px;
}
.item__countbtns--cart{
    width: 60%;
    height: 60%;
}

④最上位フォルダーにあるstyle.cssから管理する。

/* Global*/
@import "base.css";

/* Components */
@import "components/header.css";
@import "components/custom.css";
@import "components/icons.css";
@import "components/footer.css";
@import "components/section.css";
@import "components/table-client.css";

/* pages */
@import "pages/user/home.css";
@import "pages/user/myinfo.css";
@import "pages/user/update-member.css";
@import "pages/user/items.css";
@import "pages/user/item-detail.css";
@import "pages/user/cart.css";
@import "pages/user/order.css";

DRY原則

繰り返されるコードを控えるDRY法則(Don't Repeat Yourself)を参考し、Thymeleafのfragmentsを利用してhtmlをcomponents化し、Optionalクラス、Lombokライブラリ でコードの量を減らす努力をしました。

Thymeleaf Fragment

header, footer, nav, headのように、重複されるコードをThymeleafのfragment文法を活用し、減らし、headerの場合、sessionによって、異なるheaderが表示されるようにコードを作成しました。headの場合はページごとに少々の違いがあるため、**th:remove**という文法を使い、fragmentに指定したheadから追加でコードを作成する形に具象しました。

ページ

item-detail.html
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org" >
<head>
    <div th:remove="tag" th:replace="~{/fragments/head :: head} "></div>
    <title>PERION - 商品詳細情報</title>
    <meta name="description" content="商品情報, Q&A, 価格など様々な商品の情報をお確かめください。">
    <link rel="stylesheet" href="/css/pages/user/items.css">
    <link rel="stylesheet" href="/css/components/star.css">
    <script type="module" src="/js/pages/item-detail.js"></script>
    <script src = "/js/item-detail-CMS.js"></script>
    <script src = "/js/star.js"></script>
    <script src = "/js/member-updatecart.js"></script>
</head>
<body>
<header th:if="${session.loginMember != null}" th:replace="~{/fragments/login-header :: header}">
</header>
<header th:unless="${session.loginMember == null}" th:replace="~{/fragments/header :: header}">
</header>

fragments

head.html
<head th:fragment="head">
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE-edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta name="author" content="HyonHyonKOR" />
    <meta name="keywords"
            content="male e-commerce,shopping,male,unique,メンズファッション,..."
    />
    <link rel="icon" href="/images/spartan-helmet.svg" />
    <!--OG (Open Graph Data)-->
    <meta property="og:title" content="HyonHyon" />
    <meta property="og:type" content="Website" />
    <meta property="og:url" content="deploy後に生成されたURL" />
    <meta property="og:image" content="deploy後に生成された画像URL" />
    <!--フォント-->
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300;400;500;600;700&family=Sometype+Mono&display=swap" rel="stylesheet">
    <!--CSS-->
    <link rel="stylesheet" href="/css/style.css">
    <!--JS-->
    <script type="module" src="/js/header.js"></script>
</head>
header.html
<header th:if="${session.loginMember == null}" th:fragment="header">
            <a href="/" class="header__logo">PERION</a>
            <nav class="header__nav">
.
.
</header>
login-header.html
<header th:if="${session.loginMember != null}" th:fragment="header">
    <a href="/" class="header__logo">PERION</a>
    <nav class="header__nav">
.
.
</header>

Optional

Java 8のOptionalクラスを利用し、Null Pointer Exceptionをtry and catch文ではなく、Lambdaのコードで作成してみました。JavaScript、Pythonのように関数型プログラミングでコードを簡潔で分かりやすくするため、活用してみました。

/*会員退会*/
@Transactional
public void withdraw(Long memberNo,MemberDeleteDTO memberDeleteDTO){

Member member = MemberDeleteDTO.MemberDeleteDTOToMember(memberNo,memberDeleteDTO);
memberRepository.findByNo(member.getMemberNo())
                .filter(m -> m.getMemberPw().equals(member.getMemberPw()))
                .ifPresentOrElse(
                m -> memberRepository.deleteByNo(member.getMemberNo()),
                () -> { throw new IllegalStateException("パスワードを再度お確認ください。"); }
        );
    }

Lombok

Lombokで繰り返されるGetter、Setter、コンストラクタコードをLombokを利用し、コンパイル時に作成されるよう、アノテーションを利用しました。
また、依存するフィールドにfinalを付け、変更可能性をなくしDIをコンストラクタから行ったため、@RequiredArgsConstructorアノテーションを利用しました。

<Lombok利用前>

@Service

public class MemberService {

    private final MemberRepository memberRepository;

    @Autowired
    public MemberService(MemberRepository memberRepository){
        this.memberRepository = memberRepository
    }
.
.

<Lombok利用後>

@Service
@RequiredArgsConstructor
public class MemberService {

    private final MemberRepository memberRepository;
.
.

Discussion