カタベログ

IT技術に関するブログを書きたい.食べ物関連はInstagramをご参照の事.

Prismaを使ってみて分かった事と、ここ最近感じたプロジェクト開発での教訓的なもの

備忘録的な側面が多分にある。 みんなが興味ありそうなPrismaの話は頭に持ってきたので、それだけ読んで閉じてもらってもいいと思う。

  • HerokuとPrismaの相性があまり良くない
    • publicスキーマしか実質使えない
    • Heroku Connectとの相性が悪い
  • テスト
    • コントローラのテストをするならサービスはモック化した方が良い
    • DBを繋いだ単体テストにおいてはテストデータの管理がとても重要
    • テストコードがある安心感
  • 設計
    • 新しい技術を使うのはほどほどに
    • 「共通部品は必要になったら都度作ろう」
  • プロジェクト運営
    • 少人数開発でもコミュニケーション不足は容易く起きる

HerokuとPrismaの相性があまり良くない

publicスキーマしか実質使えない

HerokuでDBを扱うとなると、Add-onでHeroku Postgresを使うことになる。 Heroku Postgresをアプリに追加すると、環境変数にDATABASE_URLが追加される。 schema.prismaにenv(DATABASE_URL)と記載すると、publicスキーマに対して定義が可能になる。 ここでDBにスキーマを新たに切ったとしたとき、環境変数と文字列を結合した文字列をenv()の引数に渡したい。 だが現状、引数に当たる箇所で演算はできない。

Heroku Connectとの相性が悪い

Heroku ConnectでSalesforceと繋いだ時にSalesforceスキーマが作成される。 Salesforce上の(カスタム)オブジェクトをHeroku Postgresに同期させる事ができる。 つまり、SalesforceスキーマにあるテーブルはSalesforce上で管理される。 Prismaで管理するテーブルとSalesforceで管理するテーブルがあり、そして(当然)プログラムからはその両方を扱わねばならない。 だがローカル環境で試験を行うにはローカルDBにSalesforceで管理するテーブルがないと試験できないので、Prismaスキーマは作らないといけない。 Salesforceスキーマだけは必ず二重管理になってしまうので相性が悪いと思う。

続きを読む

zshでPROMPTを変更する設定を書く場合の注意点

zsh使ってて、カラフルな方が良いのでcolorsを入れたり、Gitも使うのでvcs_info入れたりしていた。 結構前に設定していたんだけども、vcs_infoの設定内容が反映されない、というか表示されていなかった。 面倒くさいし、所詮は見栄えの話だからとほっておいたんだけども、zshの設定をいじる瞬間が生まれたので直すことにした。

結論、設定内容自体は間違えていなかったのだけども、設定を書いている場所の問題でした。 zprofileに全ての設定を書いていたんですが、これが悪かった。 zshの設定読み込み順番のPROMPTの設定をしても読み込み順番的に早すぎる模様。 RPROMPTは正常に読み込めていたので疑っていなかった…。 zshrcを用意して、そちらにPROMPTの設定変更関連を移設して見たら正常に設定されました。

ソースはこちら。

github.com

create-nuxt-appを使って作ったサンプルをHerokuで動かすまでの話

事前準備

npmをインストールしておきましょう。

create-nuxt-appをグローバルにインストール

npm i -g create-nuxt-appを実行する。

create-nuxt-appでプロジェクト作成

create-nuxt-app your-project-nameで実行(your-project-nameは各自設定)。

設定した内容はこんな感じ。

create-nuxt-app v3.4.0
✨  Generating Nuxt.js project in your-project-name
? Project name:your-project-name
? Programming language: TypeScript
? Package manager: Npm
? UI framework: Vuetify.js
? Nuxt.js modules: Axios, Progressive Web App (PWA)
? Linting tools: ESLint, Prettier, StyleLint
? Testing framework: Jest
? Rendering mode: Single Page App
? Deployment target: Server (Node.js hosting)
? Development tools: Semantic Pull Requests, Dependabot (For auto-updating dependencies, GitHub only)
? Continuous integration: GitHub Actions (GitHub only)
? What is your GitHub username? your-github-name

自分はTypesctiptを使ってPWAのSPAを作りたかったのでこのようにしました。 GIthubも使うつもりなので便利なものはぽいぽいと入れてしまいました。

stylelintの設定

無事プロジェクトが出来上がったら、stylelint.config.jsを編集します。

module.exports = {
  extends: [
    'stylelint-config-standard',
    'stylelint-config-recess-order',
    'stylelint-config-recommended-scss',
    'stylelint-prettier/recommended',
  ],
  // add your custom config here
  // https://stylelint.io/user-guide/configuration
  rules: {
    indentation: 2,
    'string-quotes': 'double',
  },
}

extendsに記載のモジュールはnpmでインストールしましょう。

eslintの設定

コーディングスタイルにAirbnbのものを採用するようにしました。 npmのコマンドは以下です。

npm i -D @vue/cli-service @vue/eslint-config-airbnb eslint-import-resolver-alias

実際の.eslintrc,jsはこんな感じ。 prettierより前にairbnbのコーディングスタイルは持ってくる必要があるそうだ。

module.exports = {
  root: true,
  env: {
    browser: true,
    node: true,
  },
  extends: [
    '@nuxtjs/eslint-config-typescript',
    '@vue/airbnb',
    'plugin:prettier/recommended',
    'plugin:nuxt/recommended',
    'prettier',
    'prettier/vue',
  ],
  plugins: ['prettier'],
  // add your custom rules here
  rules: {
    'prettier/prettier': 'error',
  },
}

prettierの設定もこのタイミングで少し変えているので参考までに載せます。

{
  "trailingComma": "all",
  "tabWidth": 2,
  "semi": false,
  "singleQuote": true
}

サンプルソースの修正

ここまでやって試しにnpm run lint:js && npm run lint:styleを実行するとぽろぽろとエラーが出ると思うので、ESLintの案内通りに修正してください。

package.jsonの編集

追加でdependenciesにtypescriptとvuetifyを追加した。 vuetifyがnuxtで入れたはずだけど、ESLintがうるさいから入れた。 要らないのかもしれない(ESLintのエラーをコメントいれて無視させればいい?)。

heroku向け設定

Dev Centerの内容を見ればわかるけども、新しくenginesを設けてnode.jsのバージョンを指定する必要がある。

また、Typescriptをbuildしなければならないけど、Slug Sizeを削減したかったので、devDependenciesにあった@nuxt/typescript-build、@nuxtjs/stylelint-module、@nuxtjs/vuetifyをdependenciesに移動した。

{
  "name": "your-project-name",
  "version": "1.0.0",
  "private": true,
  "engines": {
    "node": "15.x"
  },
  "scripts": {
    "dev": "nuxt-ts",
    "build": "nuxt-ts build",
    "start": "nuxt-ts start",
    "generate": "nuxt-ts generate",
    "lint:js": "eslint --ext .js,.vue --ignore-path .gitignore .",
    "lint:style": "stylelint **/*.{vue,css} --ignore-path .gitignore",
    "lint": "npm run lint:js && npm run lint:style",
    "test": "jest"
  },
  "dependencies": {
    "@nuxt/typescript-runtime": "^2.0.0",
    "@nuxtjs/axios": "^5.12.2",
    "@nuxtjs/pwa": "^3.0.2",
    "core-js": "^3.6.5",
    "nuxt": "^2.14.6",
    "@nuxt/typescript-build": "^2.0.3",
    "@nuxtjs/stylelint-module": "^4.0.0",
    "@nuxtjs/vuetify": "^1.11.2",
    "typescript": "^4.1.3",
    "vuetify": "^2.4.0"
  },
  "devDependencies": {
    "@nuxt/types": "^2.14.6",
    "@nuxtjs/eslint-config": "^5.0.0",
    "@nuxtjs/eslint-config-typescript": "^5.0.0",
    "@nuxtjs/eslint-module": "^3.0.2",
    "@vue/cli-service": "^4.5.9",
    "@vue/eslint-config-airbnb": "^5.3.0",
    "@vue/test-utils": "^1.1.0",
    "babel-core": "7.0.0-bridge.0",
    "babel-eslint": "^10.1.0",
    "babel-jest": "^26.5.0",
    "eslint": "^7.10.0",
    "eslint-config-prettier": "^7.1.0",
    "eslint-import-resolver-alias": "^1.1.2",
    "eslint-plugin-nuxt": "^2.0.0",
    "eslint-plugin-prettier": "^3.1.4",
    "jest": "^26.5.0",
    "prettier": "^2.2.1",
    "stylelint": "^13.7.2",
    "stylelint-config-prettier": "^8.0.2",
    "stylelint-config-recess-order": "^2.3.0",
    "stylelint-config-recommended-scss": "^4.2.0",
    "stylelint-config-standard": "^20.0.0",
    "stylelint-prettier": "^1.1.2",
    "stylelint-scss": "^3.18.0",
    "ts-jest": "^26.4.1",
    "vue-jest": "^3.0.4"
  }
}

dependabotさんを導入している為、create-nuxt-appで既定になっているバージョンから上げているものが幾つかある。 実際にGithubにPushすればPull Requestが来るので適宜ご対応ください。

heroku環境の用意

これもDev Center見れば書いていることをそのままやっただけなのだけど、buildpackにnode.js、環境変数に以下の二つを設定した。

  • HOST
    • 0.0.0.0
  • NODE_ENV
    • production

あとはGithubと連携させてbuildするだけ。

最後に

昼からトライアンドエラーを繰り返していたので、抜け漏れがあるかもしれない。 でも、一番困ったところは欠かさずに書いたので、これ読んでる人はきっと自力で直してくれると思う。 もし気が向いたらコメントください。

Heroku上にdeno + deno-express + typescriptのAPIサーバを作ってみた

色々なことをサボってきてまもなく2020年が終わろうとしている。 少しでも今年のことは今年のうちに済ませないといけないと思うのが人間ってもんなので、1日しっかりとプログラミングしてみました。

最初はバックエンドはPythonで書こうと思ってたんですけど、実務に役立たないし、Pythonでバックエンド書くメリットを見失ったのでもっと先端的なTypeScriptを選択しました。

local環境を整える

私の環境はMac、Big Surにバージョンアップしたばかりの出来立てほやほやMac。 なので、hombrewを使う。

homebrewでdenoをインストールする。

brew install deno

ない人はVS Codeも必要なので入れる。 そんな人はこのブログ見てないだろうけど。 Visual Studio Code – コード エディター | Microsoft Azure

VS Codeの環境を整える

この記事を書く時点ではVS CodeにはdenoのExtensionが存在しているのでそれを入れる。 marketplace.visualstudio.com

ない人はESLintとPretterのExtensionぐらいは入れておいた方が書きやすいと思う。

setting.json(denoの拡張の設定)を書く

.vscodeフォルダを作って、 以下の人のを参考にこんな感じに書きました。

{
    "deno.enable": true,
    "deno.import_map": "import_map.json",
    "[typescript]": {
        "editor.defaultFormatter": "denoland.vscode-deno",
        "editor.formatOnSave": true
    }
}

【VSCode】おすすめ拡張機能 - Deno - 開発覚書はてな版

Heroku 関連の設定

Githubリポジトリ作ってHeroku Appに結びつける

このあたりはTrailheadにTrailがあるので、それをやって勉強するといいと思います。

trailhead.salesforce.com

Heroku Appにbuild packを設定する

これ以降は以下のQiitaの記事がすごく役立った。 というか、これを見たから始めたまでもある。

qiita.com

GUIでもビルドパックは設定でき、それは先のTrailheadやってれば何となく分かると思うのでお好みでお試しください。

Procfile を書く

フォルダのトップにファイルは置く。 最終的にはこんな感じになりました。

web: deno run --allow-net=:${PORT} --allow-read --allow-env --import-map=import_map.json --unstable --log-level=${LOG_LEVEL} server.ts

${}で書かれているのは環境変数。 PORTは既定で存在するので、追加したのはLOG_LEVELだけ。 Denoはオプションで指定しないと環境変数読めなかったりなのでOFFなのでONにしたりしてます。 あと、今回の使い方だとstableのままではErrorになるのでunstableにしてます。本番システムではまだまだ使えないですね。

参考サイトだとcacheを使うようにしているのだけど、deno-expressの読み込みでErrorになるので外しました。 バージョン固定しているからキャッシュで十分なんだけどやむ無しかと。

runtime.txt を書く

これもフォルダのトップにファイルは置く。 Denoのバージョンを固定化する。ローカルにインストールされたDenoのバージョンを参考に指定した。

v1.6.0

deno-express を使う

import_map.json を書く

毎回毎回importするのにURL書くのがだるいのでマニュアルにもある機能を使う。

https://deno.land/manual@v1.6.1/linking_to_external_code/import_maps

これもフォルダのトップにファイルは置く。

{
  "imports": {
    "flags/": "https://deno.land/std@0.81.0/flags/",
    "http/": "https://deno.land/std@0.81.0/http/",
    "ws": "https://deno.land/std@0.81.0/ws/",
    "log/": "https://deno.land/x/deno_structured_logging@0.4.2/",
    "express/": "https://raw.githubusercontent.com/NMathar/deno-express/master/",
    "dotenv/": "https://deno.land/x/dotenv@v2.0.0/",
    "testing/": "https://deno.land/std@0.81.0/testing/"
  }
}

この記事時点であればexpressとdotenvだけで十分な気がする。 まだ作り途中なので後々使うものも含めて書いてます。

publicフォルダにindex.htmlを置く

見た目で動いた!って分かる方が嬉しさ100倍なので、適当なHTMLの静的ファイルを置いておきます。

フォルダのトップにpublicフォルダを新たに作成して、以下のHTMLでも置いておいてください。

<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <title>Deno Backend Test</title>
  </head>
  <body>
    <h1>静的コンテンツ配信テスト</h1>
    <p>表示確認用のテストページ</p>
  </body>
</html>

server.ts を書く

お待ちかね?ですかね。DenoなのでTypeScriptで書きます。

// Copyright 2020 tkm1988 All rights reserved.
import * as expressive from "express/mod.ts";
import * as rateTickerSymbol from "./controller/rateTickerSymbolController.ts";
import * as tradeSetting from "./controller/tradeSettingController.ts";
import * as userAssets from "./controller/userAssetsController.ts";

/**
 * バックエンドの使用ポートの定数(ローカルで環境変数がなければ8080を設定する)
 */
const PORT: string = Deno.env.get("PORT") ?? "8080";
/**
 * 割り当てホスト(ローカルでアクセスする場合は環境変数で127.0.0.1を指定する事)
 */
const HOST: string = Deno.env.get("HOST") ?? "0.0.0.0";
/**
 * deno-express
 */
const app: expressive.App = new expressive.App();

// # deno-expressの基本設定
// ## ログ設定
app.use(expressive.simpleLog());
// ## 静的ファイルのエンドポイント定義
app.use(expressive.static_("./public"));
// ## ボディのパーサ設定
app.use(expressive.bodyParser.json());

// ## ルーティング定義
// ### ユーザ固有の情報のCRUDのAPIに関するルーティング
// 1. ユーザのBTCの総資産を取得する
app.get("/api/user/assets", userAssets.getAllBtcAssets);

// ### ユーザ固有の情報のCRUDのAPIに関するルーティング
// 1. サポートしているティッカーシンボルのレートを取得する
app.get("/api/rate/{ticker_symbol}", rateTickerSymbol.getRateWithTickerSymbol);

// ### ユーザ固有の情報のCRUDのAPIに関するルーティング
// 1. 高値、低値、それぞれの利確指値を設定する
app.post("/api/trade/setting", tradeSetting.setTradingParams);

// # サーバ起動処理
const server = await app.listen(Number(PORT), HOST);
console.log("アプリケーション起動: ポート番号 = " + server.port);

元になるのはdeno-expressのGithubのexampleです。

github.com

この中で大切なのは以下の箇所。

/**
 * バックエンドの使用ポートの定数(ローカルで環境変数がなければ8080を設定する)
 */
const PORT: string = Deno.env.get("PORT") ?? "8080";
/**
 * 割り当てホスト(ローカルでアクセスする場合は環境変数で127.0.0.1を指定する事)
 */
const HOST: string = Deno.env.get("HOST") ?? "0.0.0.0";
...
// # サーバ起動処理
const server = await app.listen(Number(PORT), HOST);

HerokuでDeno + deno-expressのアプリを起動させるには以下の2つの事実を押さえておく必要がある。

  1. アプリケーションに動的にポートは割り当てられるので環境変数からポート番号は取得する
  2. deno-expressではlisten関数でhostを0.0.0.0で指定しないとローカルホストを指定して正常起動しない

1についてはよく記事になってるんだけど、2は案外記事がなかなか見つからない。 途方に暮れてたところで以下のQiitaの記事を見つけ、試したら起動した。 他言語だからって無下にしてはいけないですね。

LINE Bot + Python + Heroku で「Error R10 (Boot timeout) 」エラーが出たときの解消方法 - Qiita

MVCで言うControllerにあたるところを書く

個人的に、server.tsに処理をController部分だけだとしても処理を書きたくないので外出しした。

// Copyright 2020 tkm1988 All rights reserved.
import * as expressive from "express/mod.ts";

/**
 * ユーザのBTCの総資産を取得するコントローラ関数
 * 
 * @param req HTTPリクエスト
 * @param res HTTPレスポンス
 */
async function getRateWithTickerSymbol(
  req: expressive.Request,
  res: expressive.Response,
): Promise<void> {
  // dummy
  await res.json([{ data: req.params.ticker_symbol }]);
}

export { getRateWithTickerSymbol };

TSDocを使うとJava Docっぽくコメント書けるのでおすすめです。 BTCとか書いているのは私の趣味の問題なのでお気になさらず。 importを使っている(requireではない)ので、無名関数をExportできないので名前付きです。

参考サイトはこちら。

zebevogue.hatenadiary.org

さいごに

いかがでしたか?😂

以下に該当する人は是非本記事を参考にお試しください。

  • TypeScriptが流行っているからやりたい
  • どうせならトランスパイル不要なDenoで書いてみたい
    • と言うかTypeScriptの設定とかnpmの設定とか面倒臭いからDenoにしたい
  • express.jsはnode.jsで使ったことあるから同じ感じに書きたい

参考文献をいくつか載せてますが、この他にTypeScriptのコーディング規約読んだり、Denoのマニュアル読んだり、JS PrimerやMDNでJSの構文とか調べながら書いています。 つまり、変なところがあるかもしれません。ご容赦願います。 あと、開発早いから数ヶ月もしたら時代遅れな記事なっているかもなので、それもご容赦ください。

業務でJavaを使う際に気をつける事 Part2

序文

前回の記事の後、またJava + Spring Bootでバックエンドを担当する機会を得た。 8ヶ月Java + Spring Bootを使ってきたし、その案件のメンバーの中では実力派ではあったので意気揚々と火中の栗を拾いに行った。 この前口上からお察しなのだがボコボコだった。井の中の蛙大海を知らずとはまさにこの事。 控えめに言っても迷惑をかけてしまったなぁと思いつつ、次また同じ機会が巡ってきた時に同じ轍を踏まないよう学びを記録しておく。

自分がハンドリングしている範囲ならば、念の為なんて理由でNull安全に書くな

これすなわちただの怠慢である。 Nullが返ってくることなんて仕様上あり得ないのにif (Objects.isNull(hoge))なんて書くのは無駄だし他の作業に対しても悪影響でしかない。

安易にNullを戻り値とするな

空のオブジェクトの方がいいのではないか?よく考えた方がいい。

「とりあえずLombok書いとくか〜」はやめろ

Lombokはボイラープレートを省略できるのでとてもありがたい反面、自動生成されるコードを意識しないと痛い目にあう。 Setter、Getterぐらいならまぁいいのかもしれないが、コンストラクタ関連のものは後述の件も含めて気をつけた方がいい。 自分は安易にAllArgsConstructorを付けがちだったが、RequiredArgsConstructorの方が適切かもしれないから見直した方がいい。

リクエストのDTOクラスは特に気をつけろ

リクエスDTOだとNoArgsConstructorじゃないとエラーが起きたりする。 インスタンス化する過程で引数なしでインスタンス化することがあるようだ。

Enumをもっと活用しよう

Enumには静的メソッドを定義できる。 例えば、引数の値から逆引きで特定のEnum要素を取り出すなんてこともできる。 これができるようになるとかなりコード量を削減できる。 Effective Javaを読めば書いているらしい。読んだけど鳥頭なので忘れていたようだ。

拡張for文なんて使ってる暇あるならStream APIも活用しよう

Java 8から導入のStream API。平たく言えば関数型言語っぽくCollectionを扱える。 Java手続き型言語なので読みづらいかな?と思って使わないようにしていた。 でも今時のJavaエンジニアはStream APIを使う事に慣れているので読みづらいなんて事はないようだ。 自分の知識が2013年あたりで止まっているから生じた誤解でしたね。 Stream APIだからって速度(実行?コンパイル?)がひどく低下することもないらしい。 Stream API出始めの頃だとそういう話が合った気もしたが、仮にそうだったとしても改善されたのであろう。

Stream APIを使えば記述量を拡張for文使った時より数割減らせるので、開発速度向上が期待できる。 先ほど出たEnumもStream APIで扱えるので組み合わせると尚良い。

どうしても定数定義クラスを作るというなら取り扱う範囲は限定しよう

Enumでは、例えばバリデーションのアノテーションで指定する値を定数としては扱えない。 自分の理解では定数をベタがきするのはよろしくないと思っているので、致し方なく定数定義クラスを作ってしまう。 せめて作るならその定数定義クラスが取り扱う範囲は限定して責任を明確化する方が良い。 Enumが果たしていた役割の一つがそれなので、同じことをしようという話です。

DTOとDBのカラム名が異なるならModelMapperをうまく活用しよう

JPAでカスタムクエリは使わないでSave(CreateやUpdate)する時はEntityインスタンスを引数にする。 なので、リクエストをDTO使ってインスタンス化した後、それをEntityインスタンスに変換してそれを引数にする事になる。 ModelMapperを使うとキャメルケース同士でもJacksonのObjectMapperのように簡単に変換ができる。 ただし、フィールド変数の名前が一致してなければいけない。

CoCで行きたいところではあるものの、DBのスキーマが固まっていない状況だと疎結合にしたくなる。 その結果、DTOのフィールド変数の名前がEntityクラスのそれと一致しなくなってしまう。 ModelMapperならばBean定義に規約から外れているものMapさせたり、とあるフィールドの値によってはMapしないなど設定できる。 柔軟にカスタマイズできるので、よく仕様を理解して活用すると良い。 Bean定義外にゴニョゴニョ処理書いてる場合はその処理はおかしいと思った方がいい。

細かなことでも統一感を持たせて書こう

規約がしっかりしていれば起きづらいけど、規約がないからって無法地帯にしても構わないなんて話ではない。 特に他所者としてプログラマとして入ったならば、郷にいれば剛に従えという話である。 どうしても、どうしてもそれは唾棄すべき書き方だとするならば、面倒臭がらずに説明して規約化していきましょう。

APIはコントローラから書くと全体的にスッキリ書ける(気がする)

テスト駆動開発と同じで、要件に近いところなのでサービスやリポジトリを考えるときの見通しが立てやすい。

その他

残りは愚痴っぽくなっちゃうので箇条書きでさらっと書いとく。

  • テストコードを書く暇がない計画の案件はダメ
    • 間違いなく結合試験で単体試験レベルのバグが出まくる(戒め)
  • ブルックスの法則を忘れるな
    • 炎上した時にはそれはただの人不足ではない
  • 仕様書は開発期間中だけでも最新状態にしておけ
    • これがダメだとフロントと調整が入ってさらに開発遅延する
  • Create、UpdateについてはNullが何を意味するのか決めておけ
    • 更新不要なのか対象項目の削除なのか判断つかない
    • 特にJPAを使って、かつカスタムクエリを用いない場合はNullで上書きされてしまう

業務でJavaを使う際に気をつける事

今年から業務でプログラミングをするようになった。 言語はJava 13、FWはSpring Bootという最近のポピュラーな組み合わせでAPIを作った。 最初、学生に毛が生えたレベルだったので、よく書けているように見えて結合するとバグがポンポン出る感じだった。

書いてはバグ出て、それを直してを4ヶ月ぐらい続けた。 せっかくなので、その経験を通して学んだ事を書き留めておこうと思う。順番は適当。 レビュワーや反面教師、一緒に頭捻って考えてくれた方々には感謝。 ただ、これ自体も実はバッドノウハウかもしれない。なので、誰か指摘してれると嬉しい。

目次 [:contents]

外からきた値はNullである可能性を考慮する

基本的にはユーザも他の開発者もDBの中身も信じない。 信じ切ってしまうとNullPointer Exceptionが待っている。信じるものはすくわれる。 自分はDBを疑っていなかった。結合試験の初期だとDBの中身や設定が想定通りではないのですくわれた。

Nullを検査するのには大抵以下の4つを使うようにしていた。Nullでも安全に使える優秀なメソッドたちだ。

  • Objects.equals()
  • Objects.isNull()
  • Objects.nonNull()
  • org.apache.commons.lang.StringUtils.isEmpty()
  • org.apache.commons.lang.StringUtils.isNotEmpty()

ちなみに、Spring BootにもStringUtilsがあるけど、FW内部で使うために用意しているものなのでApacheの方を使うのが良いそうです。

安易に外からきたオブジェクトをメソッドチェーンをしない

例えば以下のようなコードを考えた時、リクエストボディに必ずしもメールアドレスは入っていないかもしれない。 DTO(model)の定義上はStringクラスになっているので、IDEの補完機能を使って安易にStringクラスのメソッドを繋げてしまうかもしれない。 でも、そうするとNullPointer Exceptionによるバグの元になる。

var foo = requestBody.getEmail().trim();

Optionalクラスで実装されているならOptionalクラスのメソッドを駆使して上手いこと書けばいいと思う。 そうじゃ無い場合、自分は以下のように書くように癖づけてた。

var foo = requestBody.getEmail();
foo = (Objects.nonNull(foo))
          ? foo.trim()
          : foo;

他所の人では定数はNullにならないので以下のようなやり方してる人もいた。 個人的には定数なのにメソッドが続くのは見慣れず違和感が合った。 Objects.equals()の方が汎用性のある書き方でバカの一つ覚えで行けるので優れている気がしている。

if (CONSTANT_VAL.equals(foo)) { ... }

!を使わないで書く方法を考える

Notとか書ければいいんだけどJavaにはそんな気の利いた演算子はない。 !は細くて見辛いし直感的じゃないのが嫌い。 !Objects.equals(foobar, null)よりもObjects.nonNull(foobar)の方が分かりやすい。 実際、自分は今前者を書いてても何回も確認してしまった。間違えてそうで怖い。

三項演算子を効果的に使う

2つ前の例で登場してムムッてなった人もいるかもしれないけど、三項演算子をうまく使うと逆に読みやすくなると思った。 当初は三項演算子は否定派だったけれども、逆にif文で書こうとすると冗長に感じてしまう。三行で同じじゃんって感じもするけど。 勿論、三項演算子で書く場合はワンライナーで書くと読みづらいので改行を駆使するのがいいと思う。コーディング規約違反ならごめん。

var foo = requestBody.getEmail();
foo = (Objects.nonNull(foo))
          ? foo.trim()
          : foo;
  • if文の場合
var foo = requestBody.getEmail();

if (Objects.nonNull(foo)) {
   foo = foo.trim();
}

嫌かもしれないけどコメントはとにかく書く

よくコードは読めば分かるように書くべきだし、読めばいいのでコメントは不要っていう人がいると思う。 正しいしそうあるべきだし、そうなるよう努力すべきだと自分も思う。 ただ、現場はそうじゃない人の方が大多数だし、何より書いてる本人もしばらく見ないと内容忘れる。 開発が終わった後の保守フェーズでは、参画する要員は単価が安いので能力も低い可能性が増えてくる。 現実路線で考えると書かざるを得ないと思います。

変数宣言は利用する処理の近くに記載する

自分の頭が古く、変数はクラスやメソッドの先頭にまとめて定義するものだと思ってたけど、可読性が下がるのでNGらしい。 確かに、変数名工夫しても「この変数ってなんの結果入ってたっけ?」ってなる事あるのでできる限り処理の近くに書く方がいいと思います。 人間、みんな鳥頭なんです。すぐキャッシュは消すんです。

定数管理クラスより列挙型を選ぶ

定数管理クラスをみんなで寄ってたかって更新すると以下のような問題が出る。

  • 責任分界点が曖昧になる
  • マージするとコンフリクトする
  • 同じだけど別名の定数が何個もできる

列挙型なら意味単位でクラスが分かれるので上述の悪い点が全て解決する。

バリエーションがあるもの全て予め作っておく

業務仕様上バリエーションが存在するけど、今の段階のプログラムでは使う所が無いので定義しないでおこう。 こうすると、その経緯を知らない人はそれが定義されているものと思って開発を進めてしまい、結合するとバグになるみたいな事が起こり得る。 なので、例え今使っていないとしても仕様上バリエーションが定義されているのだから、仕様に従って定義する方がバグが少なくなると思う。

YAGNIの考え方とは逆行するように思うけど、あれは機能の話なのでこの話とは別だと思っている。

以上です。8個か。Javaと関係ない事も書いた気がするけど…まぁいっか。

Macでミュージック.app(旧iTunes)でCDの取り込み時にCD情報がGracenoteサーバから取得できない場合の対処法

事象

タイトルの通り。 家にあるCDで取り込んでいない物があったので取り込もうとしたら事象が発生した。

環境情報

No. 項目 情報
1 PC Mac mini (Late 2014)
2 OS macOS Catalina 10.15.6(19G73)
3 ソフトウェア ミュージック.app 1.0.6.10

対応方法

  1. ミュージック.appを終了する
  2. アプリケーション > ユーティリティ > ターミナル.appを開く
  3. cd ~/Library/Preferences を実行してフォルダを移動する
  4. ls | grep Grace を実行して削除対象のファイル(com.apple.iTunes.Gracenote.plist)の存在確認を行う
  5. rm com.apple.iTunes.Gracenote.plist を実行してファイルを削除する
  6. ミュージック.appを開き、改めて曲情報の取得を実行する

参考サイト

メモ:itunesのCDトラック名が取得できない の直し方(Mac) | mono-tomo