TECH PLAY

株式会社ラクス

株式会社ラクス の技術ブログ

941

はじめに 技術広報のyayawowoです。 いつも ラク スのエンジニアブログのご購読、そしてエンジニアイベントへのご参加、ありがとうございます。 ラク スでは、現場最前線のエンジニア達から普段の活動や開発・運用で得た知見などの技術情報をお届けするイベント・・・ 『Meetup』 を年に数回開催しております。 そして、2021年度1発目となる今回のMeetupも大盛況に終わりましたので、その中身についてまとめさせていただきました! はじめに イベントテーマ概要 発表の紹介 Mail Dealerにおけるインフラ基盤の歴史と生産性向上について スモークE2Eテストで20年モノのシステムからバグを燻し出す方法 返したいけど返せない、大きな技術的負債を返すために おわりに イベントテーマ概要 今回は、世の中の多くのエンジニア組織が抱える 『技術的負債』 と、チームの力を最大化するための 『生産性向上』 をテーマとしております。 弊社エンジニアから、以下サービスに関わるこれまでの取り組みと今後の展望について、ご紹介させていただきました! メールディーラー 楽楽販売 発表の紹介 それではここから各発表内容と資料を共有させていただきます! Mail Dealerにおけるインフラ基盤の歴史と生産性向上について 初めに、Mail Dealerのインフラ担当である中井の発表です。 ラク スに入社した当時、Mail Dealerのサーバ台数は現在の半数程度の規模ながら、すべて物理サーバで運用していました。 サービス立ち上げ当初からの機器も手付かずな状態で、様々な問題が顕在化しており、抜本的な改善が急務でした。 それから9年が経過し、現在はオンプレ環境での『オール仮想化』を実現することで、クリアとなった問題もたくさんありますが、新たな課題も生まれています。 今回の発表では、Mail Dealerのインフラ基盤の歴史を振り返りながら、その変化の過程でどのようなポイントに着目して改善を行い、生産性向上に結び付けてきたかを、事例を踏まえてご紹介しました。 MD Infra history from motoharunakai www.slideshare.net スモークE2Eテストで20年モノのシステムからバグを燻し出す方法 次は、Mail Dealerの開発担当である桑原からのご紹介です。 Mail Dealerは約20年続く大きなシステムであり、画面数も機能数も多いため、思いもよらない箇所でバグが発生する恐れがあります。 そのため、品質には特に気を配る必要があります。 しかし品質は上げたい一方でどの組織でもあるように、新規開発に追われ、人と時間が足りない状況が常態化していました。 そんなMail Dealerでは高コストな自動テストではなく、「スモークE2E」という簡易的なE2Eテストを増産する取り組みを始めました。 限られたリソースの中でメンバーを巻き込みながら品質を担保する取り組みについてご紹介しました。 speakerdeck.com 返したいけど返せない、大きな技術的負債を返すために 最後は、リリース13年目となる息の長いサービスである楽楽販売を担当している水間の発表です。 サービスが続くのは嬉しいことですが、いわゆる技術的負債はどうしても蓄積して続けてしまいます。 楽楽販売でも長い間、返したいけど返せない負債がありましたが、その中の一部をついに返済する機会がありました。 一般的に、負債の蓄積は避けがたいものであり、 リファクタリング 等の機会も限られているかと思います。 その中でどう返済チャンスを作っていくべきか、今回の体験を振り返りさせていただきました。 speakerdeck.com おわりに 大規模な SaaS サービス開発と20年以上向き合っている ラク スだからこその発表はどうだったでしょうか。 ラク スの取り組みが、皆さまにとって新しい気づきや成長につながる機会となっていますと幸いです! 今後も ラク スMeetupでは日々のエンジニアの取り組みを発信してまいりますので、次回もぜひご参加いただけますと幸いです。 エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 forms.gle イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! rakus.connpass.com
はじめに こんにちは、matsutairaです。僕は普段 Atom という テキストエディタ を使用しており、実用的な プラグイン から面白い プラグイン など必要な プラグイン を入れて作業しています。個人的には、実用的な プラグイン より面白い プラグイン を使うのが好きですが、見つけるのも大変なので自分で プラグイン の作成ができないかと思い作成方法を調べてみました。今回は調べた内容を元に プラグイン の作成を行いたいと思います。 目次 はじめに Atomとは プラグインとは プラグイン作成 パッケージ生成 パッケージ構造 機能作成 おわりに Atom とは そもそも Atom とは、 GitHub が開発した オープンソース の テキストエディタ です。 メリット UIが直観的で使いやすい 無料公開の プラグイン が多数存在し、作業効率アップや息抜きになる プラグイン 同様多数のテーマがあり、カスタマイズ性が高い ショートカットが使いやすい デメリット 起動に多少時間がかかる 動作が重い場合がある とはいえ オープンソース であること、かつデメリットを補えるだけのメリットがあるため、使う価値のある テキストエディタ だと思います。使ったことが無い方はこの機会に是非使ってみてください。 atom.io プラグイン 作成 本題の プラグイン の作成に入ります。今回作成する プラグイン は「入力を検知し何かをする」をコンセプトに作成したいと思います。 パッケージ生成 Atom にはデフォルトでパッケージジェネレータというものが存在しており、 プラグイン の作成に必要なパッケージの雛形を自動で生成してくれます。 メニューバーの[パッケージ]から[パッケージ ジェネレータ]を選択します。まずは雛形を生成してみましょう。 ※コマンドパレット( Ctrl+Shift+P )から「Package Generator: Generate Package」でも生成できます。 保存する場所と名前を入力し[Enter]を押します。 今回は「erai- bot 」という名前で作成します。 生成が完了すると新規でウィンドウが立ち上がります。 たったこれだけで プラグイン の作成に必要な雛形を簡単に作ることができ、デフォルトでこの機能が入っているのはかなりありがたいです。 パッケージ構造 次はパッケージの構造を見てみましょう。 erai-bot ├ keymaps │ └ erai-bot.json ├ lib │ ├ erai-bot-view.js │ └ erai-bot.js ├ menus │ └ erai-bot.json ├ spec │ ├ erai-bot-spec.js │ └ erai-bot-view-spec.js ├ styles │ └ erai-bot.less ├ .gitignore ├ CHANGELOG.md ├ LICENSE.md ├ package.json └ README.md keymaps: キーバインド の定義 lib:機能の定義 menus:メニュー表示の定義 spec:テスト用フォルダ styles: css やlessによるスタイルの定義 package. json :packageの定義を記述 というような構造になっています。今回はkeymaps、libの2つのみを編集していきます。 ※自分で作成する プラグイン は、 C:/Users/~/github/erai-bot 配下に置かれ、同時に C:/Users/~/.atom/packages 配下(インストールした プラグイン が格納される場所)にショートカットが作成されます。 機能作成 それでは実際に機能を作成していきます。 まずは使える AtomAPI を確認しましょう。 数ある API の中から今回は TextEditor という API と NotificationManager を利用します。 NotificationManager:画面右上にメッセージを表示 TextEditor:文字入力検知や文字の自動入力、削除、セーブの検知が可能 この2つの API を基本に作成します。 まずは、生成したままの プラグイン を起動してみます。デフォルトでは Ctrl+Alt+O がショートカットになります。 このままでは、 プラグイン を起動/停止したときに毎回表示されてしまいますし、ON/OFF状態がわかりずらいので右上にメッセージを表示してみます。 以下のように erai-bot.js を編集します。 toggle() { atom.notifications.addInfo( 'ON/OFF' ); // ↓使用しないのでコメントアウトor削除 // console.log('EraiBot was toggled!'); // return ( // this.modalPanel.isVisible() ? // this.modalPanel.hide() : // this.modalPanel.show() // ); } atom .notifications.addInfoを使うことで画面右上にメッセージを表示できます。 nofiticationsの種類は、 * Success * Info * Worning * Error * FatalError の5種類あり、それぞれ種類によって色やアイコンが変わります。optionでアイコンを設定することも可能です。 編集した箇所の反映は Ctrl+S だけではできないので、保存とウィンドウをリロード Ctrl+Shift+F5 をします。 もう一度 プラグイン を起動させてみます。 画面右上にメッセージが表示されるようになりました。しかしこのままだと プラグイン の起動/停止をするごとにメッセージが表示されるので、まだ起動状態かがわかりません。さらに記述していきます。 toggle() { this .active = ! this .active; if ( this .active) { return this .disable(); } else { return this .enable(); } // atom.notifications.addInfo('ON/OFF'); } , enable() { atom.notifications.addInfo( 'えらい!bot起動' ); } , disable() { atom.notifications.addInfo( 'えらい!bot停止' ); } 新たにenable関数とdisable関数を追加しました。これにより プラグイン が起動している状態からは停止→disable関数呼び出し、 プラグイン が停止している状態からは起動→enable関数呼び出しに遷移することが可能となりました。 次は文字を入力するだけで褒められるようにします。 まず、activate関数部分を編集します。ここには プラグイン の読み込み時にロードされる内容を記述します。 activate(state) { this .eraiBotView = new EraiBotView(state.eraiBotViewState); this .modalPanel = atom.workspace.addModalPanel( { item: this .eraiBotView.getElement(), visible: false } ); // 入力検知用 this .editor = atom.workspace.getActiveTextEditor(); this .editorChangeSubscription = this .editor.getBuffer().onDidChange( this .onChange.bind( this )); // Events subscribed to in atom's system can be easily cleaned up with a CompositeDisposable this .subscriptions = new CompositeDisposable(); // Register command that toggles this view this .subscriptions.add(atom.commands.add( 'atom-workspace' , { 'erai-bot:toggle' : () => this .toggle() } )); } , 次は、enable関数とdisable関数を編集します。 enable() { // 起動時に有効化 this .editorChangeSubscription = this .editor.getBuffer().onDidChange( this .onChange.bind( this )); atom.notifications.addInfo( 'えらい!bot起動' ); } , disable() { // disposeで入力検知を停止 this .editorChangeSubscription.dispose(); atom.notifications.addInfo( 'えらい!bot停止' ); } , 次は、onChange関数を作成します。 onChange(input) { atom.notifications.addInfo( '入力できてえらい!' ); } 入力するたびに「入力できてえらい!」と褒められるようになりました。 プラグイン の起動中はエディタ上に変化がある度にメッセージが表示されるので、EnterやSpaceの入力でも褒められます。 また、 onChange(input) { if (input.newText === " \n " ) { atom.notifications.addInfo( 'Enter押せてえらい!' ); } } のようにすることで、Enterの入力のみを検知することも可能です。 ただし、エディタ上へ入力したもののみ検知できるので、CtrlやShitfは検知できないので注意が必要です。 最後に保存するたびに褒められるようにします。 まず、activate関数部分を編集します。 activate(state) { this .eraiBotView = new EraiBotView(state.eraiBotViewState); this .modalPanel = atom.workspace.addModalPanel( { item: this .eraiBotView.getElement(), visible: false } ); // 入力検知用 this .editor = atom.workspace.getActiveTextEditor(); this .editorChangeSubscription = this .editor.getBuffer().onDidChange( this .onChange.bind( this )); // 保存検知用 this .editorSaveSubscription = this .editor.getBuffer().onDidSave( this .onSave.bind( this )); // Events subscribed to in atom's system can be easily cleaned up with a CompositeDisposable this .subscriptions = new CompositeDisposable(); // Register command that toggles this view this .subscriptions.add(atom.commands.add( 'atom-workspace' , { 'erai-bot:toggle' : () => this .toggle() } )); } , 次は、enable関数とdisable関数を編集します。 enable() { // 起動時に有効化 this .editorChangeSubscription = this .editor.getBuffer().onDidChange( this .onChange.bind( this )); this .editorSaveSubscription = this .editor.getBuffer().onDidSave( this .onSave.bind( this )); atom.notifications.addInfo( 'えらい!bot起動' ); } , disable() { // disposeで入力検知を停止 this .editorChangeSubscription.dispose(); this .editorSaveSubscription.dispose(); atom.notifications.addInfo( 'えらい!bot停止' ); } , 最後にonSave関数を作成します。 onSave() { atom.notifications.addSuccess( '保存できてえらい!' ); } プラグイン の起動中は保存をするたびに「保存できてえらい!」と褒められるようになりました。 最後にショートカットキーを変えます。変更は keymaps にある erai-bot.json を編集します。 { " atom-workspace ": { " ctrl-alt-q ": " erai-bot:toggle " } } これで Ctrl+Alt+Q で プラグイン の起動/停止ができるようになりました。 一通りの動作はこれで完了ですが、若干物足りないのでさらに機能を追加しましょう。 一定時間入力がない場合にメッセージを表示する設定を入れます。 var timeoutId; // 中略 onChange(input) { // タイマー停止 clearTimeout(timeoutId); atom.notifications.addInfo( '入力できてえらい!' ); this .startTimer(); } , // 中略 disable() { // タイマー停止 clearTimeout(timeoutId); // disposeで入力検知を停止 this .editorChangeSubscription.dispose(); this .editorSaveSubscription.dispose(); atom.notifications.addInfo( 'えらい!bot停止' ); } , // 中略 startTimer() { // 60秒後にメッセージを表示 timeoutId = setTimeout(() => { atom.notifications.addError( '休憩できてえらい!' ); } , 60000); } 某太陽神の名言を表示しつつ、 プラグイン の起動状態を強制的に維持する設定を入れます。 var count = 0; // 中略 disable() { if (count < 1) { // アクティブ状態に強制移行 this .active = ! this .active; count = count + 1; atom.notifications.addError( '諦めんなよ!' ); atom.notifications.addError( '諦めんなよ、お前!!' ); atom.notifications.addError( 'どうしてそこでやめるんだ、そこで!!' ); atom.notifications.addError( 'もう少し頑張ってみろよ!' ); atom.notifications.addError( 'ダメダメダメ!諦めたら!' ); atom.notifications.addError( '周りのこと思えよ、応援してる人たちのこと思ってみろって!' ); atom.notifications.addError( 'あともうちょっとのところなんだから!' ); atom.notifications.addError( '俺だってこのマイナス10度のところ、しじみがトゥルルって頑張ってんだよ!' ); atom.notifications.addError( 'ずっとやってみろ!必ず目標を達成できる!' ); atom.notifications.addError( 'だからこそNever Give Up!!' ); } else { // タイマー停止 clearTimeout(timeoutId); count = 0; // disposeで入力検知を停止 this .editorChangeSubscription.dispose(); this .editorSaveSubscription.dispose(); atom.notifications.addInfo( 'えらい!bot停止' ); } } , 何秒後にメッセージを表示するかとオン状態の維持回数をユーザが設定できるようにします。 config: { "TimeSetting" : { "type" : "integer" , "default" : 60, "description" : "最終入力時間から何秒経過後にコメントを表示するか" } , "CountSetting" : { "type" : "integer" , "default" : 1, "description" : "強制的に機能ON状態を継続する回数" } } , // 中略 disable() { if (count < (atom.config.get(`erai-bot.CountSetting`))) { // アクティブ状態に強制移行 this .active = ! this .active; count = count + 1; atom.notifications.addError( '諦めんなよ!' ); atom.notifications.addError( '諦めんなよ、お前!!' ); atom.notifications.addError( 'どうしてそこでやめるんだ、そこで!!' ); atom.notifications.addError( 'もう少し頑張ってみろよ!' ); atom.notifications.addError( 'ダメダメダメ!諦めたら!' ); atom.notifications.addError( '周りのこと思えよ、応援してる人たちのこと思ってみろって!' ); atom.notifications.addError( 'あともうちょっとのところなんだから!' ); atom.notifications.addError( '俺だってこのマイナス10度のところ、しじみがトゥルルって頑張ってんだよ!' ); atom.notifications.addError( 'ずっとやってみろ!必ず目標を達成できる!' ); atom.notifications.addError( 'だからこそNever Give Up!!' ); } else { // タイマー停止 clearTimeout(timeoutId); count = 0; // disposeで入力検知を停止 this .editorChangeSubscription.dispose(); this .editorSaveSubscription.dispose(); atom.notifications.addInfo( 'えらい!bot停止' ); } } , // 中略 startTimer() { // ユーザ設定から秒数を取得 var time = atom.config.get(`erai-bot.TimeSetting`); timeoutId = setTimeout(() => { atom.notifications.addWarning( '休憩できてえらい!' ); } , time * 1000); } 以上で プラグイン は完成です。 プラグイン のコードは以下に記載しますので、気になった方はこちらを参考に プラグイン の作成に役立ててみてください。 'use babel' ; import EraiBotView from './erai-bot-view' ; import { CompositeDisposable } from 'atom' ; // グローバル変数 var timeoutId; var count = 0; export default { // ユーザ設定用 config: { "TimeSetting" : { "type" : "integer" , "default" : 60, "description" : "最終入力時間から何秒経過後にコメントを表示するか" } , "CountSetting" : { "type" : "integer" , "default" : 1, "description" : "強制的に機能ON状態を継続する回数" } } , eraiBotView: null , modalPanel: null , subscriptions: null , activate(state) { this .eraiBotView = new EraiBotView(state.eraiBotViewState); this .modalPanel = atom.workspace.addModalPanel( { item: this .eraiBotView.getElement(), visible: false } ); // 入力検知用 this .editor = atom.workspace.getActiveTextEditor(); this .editorChangeSubscription = this .editor.getBuffer().onDidChange( this .onChange.bind( this )); // 保存検知用 this .editorSaveSubscription = this .editor.getBuffer().onDidSave( this .onSave.bind( this )); // Events subscribed to in atom's system can be easily cleaned up with a CompositeDisposable this .subscriptions = new CompositeDisposable(); // Register command that toggles this view this .subscriptions.add(atom.commands.add( 'atom-workspace' , { 'erai-bot:toggle' : () => this .toggle() } )); } , deactivate() { this .modalPanel.destroy(); this .subscriptions.dispose(); this .eraiBotView.destroy(); } , serialize() { return { eraiBotViewState: this .eraiBotView.serialize() } ; } , toggle() { this .active = ! this .active; if ( this .active) { return this .disable(); } else { return this .enable(); } } , enable() { // 起動時に有効化 this .editorChangeSubscription = this .editor.getBuffer().onDidChange( this .onChange.bind( this )); this .editorSaveSubscription = this .editor.getBuffer().onDidSave( this .onSave.bind( this )); atom.notifications.addInfo( 'えらい!bot起動' ); } , disable() { if (count < (atom.config.get(`erai-bot.CountSetting`))) { // アクティブ状態に強制移行 this .active = ! this .active; count = count + 1; atom.notifications.addError( '諦めんなよ!' ); atom.notifications.addError( '諦めんなよ、お前!!' ); atom.notifications.addError( 'どうしてそこでやめるんだ、そこで!!' ); atom.notifications.addError( 'もう少し頑張ってみろよ!' ); atom.notifications.addError( 'ダメダメダメ!諦めたら!' ); atom.notifications.addError( '周りのこと思えよ、応援してる人たちのこと思ってみろって!' ); atom.notifications.addError( 'あともうちょっとのところなんだから!' ); atom.notifications.addError( '俺だってこのマイナス10度のところ、しじみがトゥルルって頑張ってんだよ!' ); atom.notifications.addError( 'ずっとやってみろ!必ず目標を達成できる!' ); atom.notifications.addError( 'だからこそNever Give Up!!' ); } else { // タイマー停止 clearTimeout(timeoutId); count = 0; // disposeで入力検知を停止 this .editorChangeSubscription.dispose(); this .editorSaveSubscription.dispose(); atom.notifications.addInfo( 'えらい!bot停止' ); } } , onChange(input) { // タイマー停止 clearTimeout(timeoutId); atom.notifications.addInfo( '入力できてえらい!' ); // タイマー開始 this .startTimer(); } , onSave() { atom.notifications.addSuccess( '保存できてえらい!' ); } , startTimer() { // ユーザ設定から秒数を取得 var time = atom.config.get(`erai-bot.TimeSetting`); // タイマーのIDを取得しつつタイマー開始 timeoutId = setTimeout(() => { atom.notifications.addWarning( '休憩できてえらい!' ); } , time * 1000); } } ; おわりに プラグイン の作成はそこまで難しいことはなく、 JavaScript の知識さえあれば簡単に作成できました。僕自身 JavaScript の知識はほぼ皆無なので、この程度の プラグイン しか作成できませんでしたが、少しの知識でも プラグイン を作成することができました。個人的には、アニメーションや音等が出るような面白い プラグイン を作成したかったですが、できることはできたかなと思います。 プラグイン の作成自体、試行錯誤を重ねながらだったため、新鮮で勉強にもなりました。より勉強を重ねて自分の納得する プラグイン を作成できればと思います。 皆さんも Atom で自分だけのオリジナル プラグイン を作成してみてはいかがでしょうか。 エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 forms.gle イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! rakus.connpass.com
はじめに 技術広報のyayawowoです。 皆さんは、業務中に Excel (エクセル)を利用される機会が多いのではないでしょうか。 Excel (エクセル)には便利な関数がとても多くあります。 今回は、 Excel 初心者向けにビジネスシーンで多く利用されている Excel (エクセル)の中でも、 IF関数 に特化し、使い方をまとめました。 基本的な使い方となりますので、 Excel (エクセル)初心者の方だけでなく、復習をしたい方のご参考になれば幸いです。 はじめに Excel(エクセル)のIF関数とは Excel(エクセル)のIF関数 - 使い方(入門編)- 例題 実践 IF関数と論理式 Excel(エクセル)のIF関数 - 使い方(応用編)- IF関数と複数条件 例題 実践 Excel(エクセル)のIF関数とAND関数・OR関数 IF関数とAND関数 例題 実践 IF関数とOR関数 例題 実践 IF関数と空白(空欄) 例題 実践 IF関数とVLOOKUP関数 例題 実践 まとめ Excel (エクセル)のIF関数とは まず初めに、 Excel (エクセル)のIF関数とはどのようなことができるでしょうか。 簡単にいいますと… IF関数とは 「設定した条件によって、指定された値を返す関数」 のこと Excel (エクセル)のIF関数について、数式を利用した簡単な解説は以下のようになります。 = IF( ① ●●が▲▲だったら【条件】 , ② 設定した値を表示【条件一致】, ③ 設定した値を表示【条件不一致】 ) ①:条件を指定する ②:条件に一致する場合、表示したい値を指定 ③:条件に一致しない場合、表示したい値を指定 では、イメージがついたところでIF関数の使い方に入っていきます。 まずは、入門編からです。 Excel (エクセル)のIF関数 - 使い方(入門編)- 入場料の計算を例題にしていきたいと思います。 例題 入場料が以下条件の動物園があります。 20歳未満の入場料:1,000円 20歳以上の入場料:2,000円 ある旅行会社では、動物園の入場料をまとめるために名簿を作成しました。 各々の入場料がいくらになるか Excel (エクセル)の IF関数 を活用して算出してみましょう! 赤枠箇所の条件を、 以下の通りにすれば良いことがわかります。 = IF( ① 年齢が20歳以上だったら【条件】 , ② 2,000円を表示【条件一致】, ③ 1,000円を表示【条件不一致】 ) この式を使って、 Excel (エクセル)のIF関数の使い方を解説します。 実践 1. 設定した条件に合わせて、 Excel (エクセル)で表を作成します。 2.、 Excel (エクセル)で 作成した表のセルに式を入力します。 D6 に =IF( と入力 Excel (エクセル)が自動で候補の関数を =IF(論理式,[真の場合],[偽の場合]) に式を入力 論理式・・・【条件】 年齢が20歳以上だったら: C6>=20 年齢: C6 20歳以上: >=20 真の場合・・・【条件一致】 2,000円を表示: $C$3 2,000円を手入力しても良いが、汎用的に利用したいため C3 を指定する オートフィルでコピーをした際、セルがずれないように絶対参照 $ で固定する 偽の場合・・【条件不一致】 1,000円を表示: $C$2 1,000円を手入力しても良いが、汎用的に利用したいため C2 を指定する オートフィルでコピーをした際、セルがずれないように絶対参照 $ で固定する ◆式: =IF(C6>=20,$C$3,$C$2) が完成◆ 3. オートフィル 1 で式をコピーします。 D6 に入力した式を D7 、 D8 にもコピーしたいですよね。 そのようなときにオートフィルを利用します。 D6 セルの右下にカーソルをあてる 十字(+)のようなマークがでましたら、クリックしたまま下に引っ張る D7 、 D8 に式がコピーされ、条件通りの値が表示される 20歳以上である ラク ス花子さんと花見 ラク 子さんの入場料が「2,000円」であることがわかります。 Excel (エクセル)のIF関数は覚えてしまえばとても便利な機能です。 様々な場面で活用できるよう、この機会にしっかりと覚えてしまうことをお勧めします。 IF関数と論理式 Excel (エクセル)のIF関数で入力する論理式ですが、 「どのような式にすればいいの?」 「この条件の時、記号は何を使えば・・・」 と、迷われる方もいるかと思います。 以下の通り、少し簡単にまとめましたので、ご参考いただけますと幸いです。 条件 式 C6>20 C6データ(年齢)が20歳より大きい C6>=20 C6データ(年齢)が20歳以上 C6<20 C6データ(年齢)が20歳より小さい(未満) C6<=20 C6データ(年齢)が20歳以下 C6=20 C6データ(年齢)が20歳と等しい(同じ) C6<>20 C6データが20歳と等しくない(異なる) Excel (エクセル)のIF関数 - 使い方(応用編)- Excel (エクセル)のIF関数は、複数の条件を組み合わせて利用することも可能です。 ここからは応用的な使い方について解説をしていきたいと思います。 IF関数と複数条件 応用編では、入門編で扱った例題を再度利用します。 例題 以下の通り入場料の条件を変更しました。 入門編では、「20歳以上」という1つの条件で事足りましたが、そこに以下のような「20歳以上40歳未満」と「40歳以上」の条件が加わった場合はどのようにIF関数の式を作成すれば良いでしょうか。 20歳以上の入場料:1,000円 20歳以上40歳未満の入場料:2,000円 40歳以上の入場料:1,500円 では、複数条件を使った Excel (エクセル)のIF関数について説明していきます。 実践 1. Excel (エクセル)で作成した表のセルに式を入力します。 =IF(論理式,[真の場合],[偽の場合]) の通り、式を埋めていく 論理式・・・【条件】 20歳未満だったら: C7<20 真の場合・・・【条件一致】 1,000円を表示: $C$ 偽の場合・・【条件不一致】 次の条件:IF関数を追加・・・ (IF(論理式,[真の場合],[偽の場合])) 論理式・・・【条件】 40歳以上だったら: C7>=40 真の場合・・・【条件一致】 1,500円を表示: $C$4 偽の場合・・【条件不一致】 2,000円を表示: $C$3 ◆式: =IF(C7<20,$C$2,IF(C7>=40,$C$4,$C$3)) が完成◆ 2. オートフィルで式をコピーします。 D7 の式をオートフィルを利用して D8~D10 にコピーします。 D7 セルの右下にカーソルをあてる 十字(+)のようなマークがでましたら、クリックしたまま下に引っ張る D8 、 D9 、 D10 に式がコピーされ、条件通りの値が表示される 45歳である楽楽次郎さんの入場料が1,500円であることがわかります。 また、25歳の ラク ス花子さんと20歳の花見 ラク 子さんの入場料は2,000円と正しく算出できています。 このように、条件が複数となった場合でも、 Excel (エクセル)のIF関数を利用して簡単に計算することができますね! Excel (エクセル)のIF関数とAND関数・OR関数 Excel (エクセル)のIF関数ではさらに、より複雑な処理ができます。 次はIF関数と「AND関数(かつ)」と「OR関数(または)」の使い方について説明します。 IF関数とAND関数 例題 以下条件の試験を例題にAND関数の使い方を説明します。 80歳以上:合格 80歳未満:不合格 実践 1. Excel (エクセル)で作成した表のセルに式を入力します。 =IF(論理式,[真の場合],[偽の場合]) の通り、式を埋めていく 論理式・・・【条件】 午前試験が80点以上かつ、午後試験が80点以上だったら: (C6>=80,D6>=80) 真の場合・・・【条件一致】 合格を表示: $C$2 偽の場合・・【条件不一致】 不合格を表示: $C$3 ◆式: `IF(AND(C6>=80,D6>=80),$C$2,$C$3) が完成◆ 2. オートフィルで式をコピーします。 E6 の式をオートフィルを利用して E7~E9 にコピーします。 E6 セルの右下にカーソルをあてる 十字(+)のようなマークがでましたら、クリックしたまま下に引っ張る E7 、 E8 、 E9 に式がコピーされ、条件通りの値が表示される 今回用意した例題は、「午前試験が80点以上かつ(AND)、午後試験が80点以上」の人だけが合格となります。 表から読み解く通り、合格者は ラク ス太郎さんだけであることがわかります。 合否判定も正しくされていますね。 IF関数とOR関数 例題 次は、以下条件の試験を例題に、OR関数の使い方を説明します。 80歳以上:合格 80歳未満:不合格 実践 1. Excel (エクセル)で作成した表のセルに式を入力します。 =IF(論理式,[真の場合],[偽の場合]) の通り、式を埋めていく 論理式・・・【条件】 午前試験が80点以上または、午後試験が80点以上だったら: OR(C6>=80,D6,>=80) 真の場合・・・【条件一致】 合格を表示: $C$2 偽の場合・・【条件不一致】 そうでなければ、不合格を表示: $C$3 ◆式: =IF(OR(C6>=80,D6>=80),$C$2,$C$3) が完成◆ 2. オートフィルで式をコピーします。 E6 の式をオートフィルを利用して E7~E9 にコピーします。 E6 セルの右下にカーソルをあてる 十字(+)のようなマークがでましたら、クリックしたまま下に引っ張る E7 、 E8 、 E9 に式がコピーされ、条件通りの値が表示される OR関数の例題は、「午前試験が80点以上または、午後試験が80点以上」の人だけが合格となります。 午前・午後試験の両方とも80点以上がないのは楽楽次郎さんだけですので、IF関数の式が間違っていないことが確認できました。 IF関数と空白(空欄) 例題 例題としてあげていた合否判定ですが、もし合格の人だけを知りたい試験があったとします。 受験者が少ない試験でしたら、今まで通り合格・不合格の表示をしても良いと思うのですが、人数が多い場合はどうでしょうか。 以下のように表示してしまうと少し見にくくなりますよね。 Excel (エクセル)のIF関数では、空白(空欄)を指定することもできますので、次はその説明をしたいと思います。 実践 1. Excel (エクセル)で作成した表のセルに式を入力します。 =IF(論理式,[真の場合],[偽の場合]) の通り、式を埋めていく 論理式・・・【条件】 点数が80点以上だったら: C6>=80 真の場合・・・【条件一致】 合格を表示: $C$2 偽の場合・・【条件不一致】 空白(空欄)を表示: "" ◆式: =IF(C6>=80,$C$2,"") が完成◆ 2. オートフィルで式をコピーします。 D6 の式をオートフィルを利用して D7~D16 にコピーします。 D6 セルの右下にカーソルをあてる 十字(+)のようなマークがでましたら、クリックしたまま下に引っ張る D7~D16 に式がコピーされ、条件通りの値が表示される どうでしょうか。 合格の人だけをすぐに見つけられる表になりました。 このように、IF関数で空白(空欄)を表示する式を作ることができます。 ちなみにですが、以下の通り論理式に空白(空欄)を指定することもできます。 =IF(論理式,[真の場合],[偽の場合]) の通り、式を埋めていく 論理式・・・【条件】 点数が空白(空欄)だったら: C6="" 真の場合・・・【条件一致】 不合格を表示: $C$3* 偽の場合・・【条件不一致】 合格を表示: $C$2 ◆式: =IF(C6="",$C$3,$C$2)</span></b> が完成◆ IF関数とVLOOKUP関数 最後は Excel (エクセル)のIF関数とVLOOKUP関数を組み合わせた応用的な使い方を説明します。 まず、VLOOKUP関数を初めて聞く方もいるかと思いますので、簡単にまとめると以下になります。 今回は Excel (エクセル)のIF関数についての説明を中心にしていますので、VLOOKUP関数の使い方は別でご確認いただけますと幸いです。 = VLOOKUP( ① 検索したい値を設定【検索値】 , ② 検索する範囲を設定【範囲】, ③ 設定した範囲の列番号を設定【列番号】, ④ 検索する際の型を設定【検索の型】 ) では、早速例題にうつります。 例題 以下条件に応じて、 E6 セルに入力した人の合否判定を行う表を作成する。 80歳以上:合格 80歳未満:不合格 実践 1. まずVLOOKUP関数で名前に紐づいた点数を検索します。 「=VLOOKUP( 検索値,範囲,列番号,[検索の型] )」の通り、式を埋めていく 検索値 検索したい名前( ラク ス太郎)を指定: E6 範囲 点数表の範囲から検索する: B6:C9 列番号 検索値があれば、指定した範囲の左から2列目の点数を返す: 2 検索の型 ただし、検索値は完全一致で返す: FALSE ◆式: =VLOOKUP(E6,B6:C9,2,FALSE) が完成◆ 以下の通り、名前に紐づいた点数を返すことができましたので、この結果に応じて合否判定もしていきたいと思います。 2. Excel (エクセル)のIF関数を利用して合否判定を行います。 =IF(論理式,[真の場合],[偽の場合]) の通り、式を埋めていく 論理式・・・【条件】 もし検索した点数が80点以上だったら: VLOOKUP(E6,B6:C9,2,FALSE)>=80 真の場合・・・【条件一致】 合格を表示: $C$2 偽の場合・・【条件不一致】 不合格を表示: $C$3 ◆式: =IF(VLOOKUP(E6,B6:C9,2,FALSE)>=80,$C$2,C3) が完成◆ まとめ 今回は Excel (エクセル)のIF関数の入門的な内容から、少し応用的なものまでご紹介しました。 Excel (エクセル)は私だけでなく、皆さんが業務を行う上でも利用機会が多いツールでもあるかと思います。 最近は、 Google の スプレッドシート を利用する方も多いと思いますので次はその内容についてまとめております。 ブログ Google スプレッドシート 使い方 【まとめ】 最後にはなりますが、本内容が1つでも皆様のお役に立てていれば幸いです! Excel (エクセル)のIF関数については以上となりますが、最後に宣伝をさせてください! エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 forms.gle イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! rakus.connpass.com 長々と失礼しました。 今後とも、 ラク スを宜しくお願い致します。 表中のある項目(セル)に特定の値を入力して選択状態にし、そのままマウスなどのドラッグ操作で縦あるいは横に選択領域を広げていくと、新たに選択された項目に次々に連続した値が入力されていく。 ↩
はじめに こんにちは、楽楽精算の開発に携わっているHiroto-Kitamuraです。 皆さまは、開発にどのような環境( IDE 、エディタ)を使用しているでしょうか? Eclipse に VSCode 、 Emacs に Vim など…個々の使い方によって選択肢は多いですよね。 本記事では、私が職場や家でインストールして使用している IDE の IntelliJ を紹介します。 IntelliJ は機能をフルに使おうとすると有料ですが、私はそれに見合う価値があると考えています。 インストールから日本語化などの簡単な使い方まで解説しているので、気になった方はぜひ最後までお読みください。 はじめに IntelliJとは? 有料版と無料版について インストール手順 Toolboxのインストール IDEのインストール 初期設定 日本語化・プラグイン導入の使い方 ライセンス購入 IntelliJの使い方 プロジェクト作成・プログラム実行 便利なショートカット Git連携の使い方 リポジトリ作成 commit, pushなどの使い方 switch, mergeなどの使い方 Git Bash埋め込み データベース連携の使い方 データベース接続 データベース操作 まとめ IntelliJ とは? IntelliJ (正確には IntelliJ IDEA)は チェコ の JetBrains 社が開発している 統合開発環境 ( IDE )の一つで、 主に JVM 言語( Java , Kotlin, Scala など)及びHTML/ CSS や JavaScript 等フロントエンドで使われる言語、 SQL をサポートしています。 JetBrains社は IntelliJ 以外にも Python や C言語 など使い方の異なる各言語に対応した IDE をそれぞれリリースしており、 これらの IDE と豊富な プラグイン をインストールしてフルに使用することであらゆる言語での開発ができるようになっています。 IDE 名 主なサポート言語 AppCode Objective-C , Swift CLion C, C++ DataGrip SQL 専用 GoLand Go IntelliJ IDEA Java , Kotlin, Scala , Groovy PhpStorm PHP PyCharm Python Rider C# , .NET Framework RubyMine Ruby WebStorm web専用(HTML, JavaScript など) 有料版と無料版について IntelliJ には有料版のUltimateと無料版のCommunity Editionが存在し、使い方によってどちらかをインストールします。 無料版は他の多くの環境と同じく料金がかかりませんが、フロントエンドや SQL 、Webフレームワークのサポートがないため、本格的な開発には向きません。 有料版は個人利用で1年17,200円(初年度・記事作成現在)と決して安くない価格ですので、導入を迷っている方はまず、無料版をインストールして試してみるのも良いでしょう。 また、有料版にも1ヶ月の試用期間がありますので、そちらを試すのも一つの手です。 ちなみに、 IntelliJ Ultimateを含めたJetBrains製 IDE が全部入りになっているAll Products Packというものも存在します。 個別に買い揃えるよりかなり安くなっているので、色々な言語を使いたい方はこちらを購入するのが良いでしょう。 インストール手順 Toolboxのインストール IntelliJ のインストールには、公式サイトから直接 IntelliJ の インストーラ をダウンロードする方法と、JetBrainsの各 IDE をまとめて管理できる Toolbox App からインストールする方法があります。 どちらがよいかは各々のPCの使い方等によって変わってくると思いますが、本記事では IDE のバージョンの管理が簡単で複数の IDE の管理にも便利なToolboxからのインストール方法を解説します。 まず、 こちら からToolboxの公式ページにアクセスし、OSに合わせた拡張子を選択してダウンロードをクリックしてください。 次に、ダウンロードした インストーラ を開き、指示に従ってインストールを行いましょう。 インストールが完了したら、タスクバー右側の通知領域にToolboxアイコンが表示されているかと思います( Windows の場合)。 これをクリックすることで、Toolboxを起動することができます。 表示されていない場合はスタートメニューなどから検索して起動すればOKです。 IDE のインストール Toolboxのインストールが終わったら、前項の方法で起動しましょう。 Toolsタブに表示されている IntelliJ IDEA UltimateかCommunity Editionのどちらかを選択し、インストールをクリックしてください。 私は既に IntelliJ をインストールしているので画像では表示が異なっていますが、本来は他の IDE と同様の表示になります。 インストールが完了したら、Toolsタブ上部・Installed項に表示された IntelliJ をクリックし、起動することができます。 初期設定 IntelliJ のインストール後初回起動時には、初期設定を行う必要があります。 ※私は既に IntelliJ でこの初期設定を完了してしまっているので、以降の一部画像は別のJetBrains製 IDE であるPyCharm, RubyMineをインストールした時のものになります。 やることは IntelliJ とほぼ変わりません。 起動するとまず設定ファイルをインポートするか聞かれるので、他の人や前のPCから設定ファイルを持って来ている人はそのファイルを選択しましょう。 ない人はDo not import settingsを選べばOKです。 次にライセンスの設定です(有料版のみ)。 30日間の無料試用期間があり、Evaluate for free→Evaluateを押すと試用することができます。 後述するライセンス購入を行っている方は、Activate→JB AccountでJetBrainsアカウントのログイン情報を入力するか、Activation codeからライセンスコードを入力してください。 最後にWelcome画面が開きますので、カラーテーマやフォントサイズ、 プラグイン 等の設定を行って最初のプロジェクトを作成すれば初期設定完了となります(これらは後からでも設定できます)。 IntelliJ の画面表示をできるだけ日本語にしたいという方は、ここで次項の日本語化 プラグイン をインストールするのがよいでしょう。 日本語化・ プラグイン 導入の使い方 インストール直後の IntelliJ は表記がすべて英語になっています。 私はこのまま使っているのですが、日本語化したい方も多いのではないでしょうか。 本項では IntelliJ の日本語化の方法とその他 プラグイン のインストール方法を説明します。 先程のWelcome画面でPluginsタブを開き、検索窓にjapaneseと打ち込みましょう。 すると「Japanese Language Pack / 日本語言語パック」という プラグイン が出てくるので、インストールボタンを押します。 他に使い方に合わせて入れたい プラグイン があれば同様にインストールすればOKです。 インストール完了後画面の指示に従ってRestart IDE から IntelliJ を再起動すると、表記がある程度日本語になりました。 完成されたプロジェクトではないので、英語のまま残っているところも多々あります。 プラグイン は IntelliJ のインストール直後だけでなくプロジェクトを作った後でも設定画面で入れることができますし、一度入れた後無効化することもできます。 この場合は画面上部の ツールバー 、File>Settings>Pluginsから設定を行いましょう。 まずは英語で試してみて、読みづらいので日本語にして、でもやっぱり英語に戻して…という使い方も可能です。 この記事では以降日本語化していない環境を前提に説明を進めていきますが、使い方は変わらないので、日本語化をした方は適宜翻訳しながら読み進めてください。 ライセンス購入 有料版の使用には、ライセンス購入が必要となります。 日本の代理店から購入する方法もありますが、ここではJetBrainsから直接購入する方法を紹介します。 まずは、 JetBrains公式サイト から右上のストアをクリックしてください。 IntelliJ のみ使用する場合は IntelliJ IDEA Ultimate、他の全 IDE も使いたい場合はAll Products Packの購入ボタンをクリックし、先に進みましょう。 あとは画面の指示に従って情報を入力し、クレジットカードや デビットカード で支払いを行えば、購入完了です。 この過程でJetBrainsアカウントを作成することになると思います。 Toolboxを開いて右上の歯車アイコンから設定画面に進み、アカウント情報を入力してログインしておくことでライセンスを有効化することができます。 先述した IDE のインストール後初期設定でも有効化できますが、こちらで設定しておくと IntelliJ 以外の IDE をインストールする際にも毎回いちいちアカウント情報を入力する必要がありません。 All Products Packを購入した方にはこちらの設定方法がおすすめです。 IntelliJ の使い方 ここからは、プログラム作成からGitやデータベースの連携まで、 IntelliJ の基本的な使い方・便利機能について説明します。 プロジェクト作成・プログラム実行 IntelliJ のインストールが終わったら、実際にプロジェクトを作ってコードを書いてみましょう。 インストール直後の場合はWelcome画面のProjects>New Project、 既に何らかのプロジェクトを開いている場合は上部 ツールバー のFile>New>Projectから新規プロジェクト作成画面に入ってください。 作るプロジェクトの種類を画面左から選択できますが、今回は基本的な Java プロジェクトを作ることにします。 開発を行うには、 SDK ( JDK 、 Java 開発に必要なツールキット)を選択する必要があります。 既に使いたいバージョンの JDK がPCにある人は表示された候補の中から選ぶか、Add JDK から JDK のフォルダを選択してください。 JDK がない、そもそも JDK って何という方はDownload JDK から使いたい Java バージョンを選んでインストールしましょう。 バージョンがわからなければ11あたりでいいと思います。 次にテンプレートを選ぶ画面に移ります。 チェックを入れると初期パッケージとMainクラスとmainメソッドが作られるので、入れておきましょう。 最後にプロジェクト名(と初期パッケージ)を決めてFinishを押せば、プロジェクトが作成されます。 プロジェクトの画面は下画像のようになっていて、左側にファイル管理、右側にコード、下に実行結果他もろもろが表示されています。 記事内で字が見えづらくならないようにウインドウを縮小しています。普段のコード画面はもっと広いです コードを書いていると自動補完が働きます。 デフォルト設定では特別なショートカットを押さなくとも、入力すると勝手に補完が出てきます。 使い方によると思いますがとても便利です。 大文字小文字の区別をなくすには、File>SettingsのEditor>General>Code CompletionからMatch caseのチェックを外してください。 コードの実行は画面右上などに表示されている右三角ボタンで実行できます。 表示がない場合はmain関数の上で右クリックしてRun Mainを実行すればOKです。 新しくパッケージやクラスなどを作る際は左側のフォルダを右クリック>New(または選んだ状態でalt+insert)で作成しましょう。 このあたりの使い方は Eclipse 等の IDE とほぼ変わらないと思いますが、 IntelliJ ではデフォルトでコードを自動保存してくれる点が大きく異なります。 便利なショートカット IntelliJ には、便利なショートカットが多数存在します。 全ショートカットの一覧は他サイトに譲りますが、ここでは私が普段重宝しているものを紹介しようと思います。 ショートカット 操作 Ctrl+Space 自動補完 Ctrl+Alt+L コードの自動整形 Shift+F6 名称変更 Ctrl+クリック 変数やクラスの宣言場所・使用場所にジャンプ Ctrl+D 行の複製 Ctrl+Y 行の削除(最初に使用した際 Redo と選択) F2 エラーや警告の場所にジャンブ Alt+Enter エラーや警告の修正候補を表示 Shift2回押し クラス・変数・テーブル・設定項目など何でも検索 Ctrl+Shift+F 全ファイル内から grep 検索 Alt+Insert ファイル作成・コンスト ラク タ/getter/setter/toString等の追加 Ctrl+/ 選択行の コメントアウト ※ショートカットはすべて Windows 版です Eclipse などの今使っている環境と、コマンドや使い方が全然違うと思う方もいらっしゃると思います。 こちらに慣れるのもいいですし、File>Settings>Keymapからお使いの環境風に一括で変えたり、1つずつショートカットを自分流にアレンジする使い方もできますよ。 Git連携の使い方 IntelliJ では作ったプロジェクトをGitと連携させると、 GUI でcommitやpush、switchなどを行ったり、ブランチのコミットグラフを図で確認することができるようになります。本項では、このGit連携の基本的な使い方を紹介します。 これらはコマンドで行うことももちろんできますが、ブランチが多くてごちゃごちゃしている場合や、コマンドを叩くより GUI 上で操作したい方におすすめです。 リポジトリ 作成 新しくプロジェクトを作った場合は、画面上部の ツールバー から VCS >Enable Version Control Integrationをクリックしてください。 使う バージョン管理システム の選択肢が出てくるのでGitを選んでOKを押すと、ローカル リポジトリ が作成されます。 既に GitHub などにあるプロジェクトをcloneする場合は、上部 ツールバー でFile>New>Project from Version ControlよりURLの入力や GitHub アカウントへのログインをすることでcloneできます。 commit, pushなどの使い方 ローカル リポジトリ を作ったら、実際にcommitなどの操作を行ってみましょう。 Gitの操作は主に画面右上と画面下部のGitタブ>Logタブ(画像で赤枠で囲まれた部分)で行います。 commitやpushpull、 ロールバック は画面右上のボタンで行います。 commitはチェックボタンです。 差分を見比べながらcommitするファイルを選択する、コミット前に自動整形をする、といった使い方ができるので大変便利です。 ちなみに、差分があるファイルはcommit画面を開かなくてもファイルウインドウやエディタ画面で青く表示されています。 pushは右上向き矢印ボタンで行えますが、 GitHub などのリモー トリポジ トリが必要なので、cloneやgit remoteなどで設定していない場合はpush>Define remoteからリモー トリポジ トリを設定しましょう。 pullは左下向き矢印(Update Project), 未コミット部分の ロールバック はU字矢印ボタンで行うことができます。 switch, mergeなどの使い方 ブランチ関連の操作は画面下部Gitタブで行います。 現在のブランチ名は常に画面右下に表示されています(前項の図参照)。 既存のブランチへのswitchはブランチを右クリック>Checkoutで、新ブランチの作成は派生元のブランチを右クリック>New Branch from Selectedから行いましょう。 簡単な操作は右下のブランチ名クリックから行うこともできます。 mergeはマージ先のブランチにcheckoutした後、マージ元のブランチ右クリック>Merge into Currentから行えます。 ここで紹介した使い方以外にも、様々なGit操作が可能です。 Git Bash 埋め込み 前章ではGitの GUI 操作の使い方について説明しましたが、コマンドで操作を行うGit Bash を IntelliJ に埋め込むことも可能です。 これを使えば、Gitコマンドを使うのにいちいち IntelliJ からウインドウを切り替える必要がありません。 本項で紹介する方法は Windows 用のものとなりますが、他のOSでも似たような使い方でgitが使える端末が埋め込めると思います。 ※Git Bash がPCに入っていない場合は、 IntelliJ への埋め込みの前にGit Bash をインストールしておいてください。 こちら からインストールできます。 まず、上部 ツールバー のFile>Settingsから設定メニューを開き、Tools>Terminalを開きましょう。 そしてShell Pathの欄にGit Bash のインストール ディレクト リのbin\ bash .exeのパスを入力すると(C:\Program Files\Gitあたりにあると思います) 、 IntelliJ 画面下部のTerminalタブでGit Bash を開くことができます。 この状態でGit Bash は使えるのですが、このままだとgit logなどで表示される日本語が正常に表示されず、Gitの使い方によってはとても不便です。 これを解消するためには、Git Bash の設定を変更する必要があります。 まずGit Bash を IntelliJ とは別ウインドウで開き、次の2つのコマンドを実行してください。 $ cd $ vi .bashrc vim が開くので、iボタンで挿入モードに移行した後 export LANG='ja_JP. UTF-8 ' と書き込み、 Escキーで ノーマルモード に移行して:wqと入力して保存します。 その後コマンドで $ source .bashrc を実行した後、 IntelliJ のターミナルを開き直す(Localの横の×ボタンで閉じた後再びTerminalタブを開く)と日本語が表示されているはずです。 データベース連携の使い方 IntelliJ Ultimateをはじめとする有料版JetBrains IDE では、Gitと同じようにデータベースも GUI から操作したり、コンソールを IDE 内で使用することができます。 Webアプリなどデータベースを活用するアプリケーションを開発する際、テーブルの作成やデータの挿入などが IDE 内で簡単に行えるため便利です。 本項では、このデータベース連携の使い方を紹介します。 ※Git Bash と同じくこちらも予め DBMS をインストールする必要があるので、PCに入っていない方はこの項を読む前に何らかの DBMS のインストールをしておいてください。 DBMS のインストール方法や使い方は割愛しますが、例えば PostgreSQL であれば こちら からインストールできます。 データベース接続 まず、画面右端に表示されているDatabaseをクリックしてください。 ない場合は画面端のどこかにあると思います(無料版にはありません)。 データベースのツールウインドウが開くので、+ボタン>Data Sourceから、お使いの DBMS を選択しましょう。 あとは画面の指示に従って JDBC ドライバのインストールや認証情報の入力を行えば、データベースに接続できます。 認証方法など詳しい使い方は DBMS によって異なるので、本記事では紹介しません。各 DBMS の仕様に従ってください。 データベース操作 データベースに接続すると、 SQL を打ち込めるコンソールが開きます。 使い方は概ね他のエディタや コマンドライン と同じですが、書いてある文の中で一部のみ実行といったこともできるので、長い SQL を一度にコピーしてきて少しずつ実行ということも可能です。 コードのエディタと同じくこちらでも強力な補完が自動で働くため、手動で SQL を入力する使い方の場合は非常に便利です。 私が普段使っている PostgreSQL の場合、接続するデータベースや スキーマ をコンソール右上から設定できるので、接続するデータベース クラスタ が同じならいちいちコンソールを開き直したりSET search_pathを入力する必要もありません。 なお、コンソールを閉じてしまった場合には、データベースツールウインドウ上部の SQL ボタンを押すと再度開くことができます。 IntelliJ では、簡単な使い方で GUI でデータベースを操作することも可能です。 データベースツールウインドウにはデータベースが ディレクト リ構造で表示されているので、 右クリックからテーブル作成・削除、 DDL 変更(Modify Table)などを簡単に行うことができます。 また、テーブルをダブルクリックするとそのテーブルに入っているデータが表示されますが、 これはただSELECT文の結果が見られるだけではありません。 画面上部の+-ボタンで行の追加・削除(INSERT・DELETE)ができる他、フィールド(マス)をダブルクリックすれば内容の変更(UPDATE)も可能です。 実際のデータベースへの反映はDB↑ボタンを押してから行われるので、間違って行を消してもボタンを押さなければ問題ありません。 複雑な操作には向きませんが、簡易的なデータの挿入・修正が直感的に行えるのは便利ですね。 まとめ 本記事では IntelliJ のインストール方法・基本的な使い方・便利な機能について紹介してきました。 ここで紹介したのは IntelliJ の機能のごく一部であり、まだまだ色々な使い方が存在します。 私も使い始めて1年も経っていないので、多くの機能を知らないまま使ってきていると思います。 ただ、「こういう機能あったら便利だな」と思って検索すると大体何かしらあるので、 開発時、環境に起因するストレスを感じることはほとんどありません。 個々人の使い方によって合う環境は変わってくると思いますが、 多少お金をかけてでも快適に開発が行いたいという方は、 一度 IntelliJ やJetBrains製 IDE をインストールして試用してみるのも良いのではないでしょうか。 エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 rakus.hubspotpagebuilder.com ラク スDevelopers登録フォーム https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/ イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! ◆TECH PLAY techplay.jp ◆connpass rakus.connpass.com
はじめに PHPのソースコードについて 標準関数のソースコードを読むコツ PHP本体のソースコードを読んでみる まとめ はじめに こんにちは。開発エンジニアのシカタです。 今回は PHP の標準関数の読み方について紹介します。 こんな経験をしたことはありませんか? PHP のバージョンアップで挙動が変わった 関数が想定外の値を返してきた 想定外の挙動をしている… リファレンスに書かれていないけど、この引数って使える…? こういうときに、 PHP 本体のコードがどう実装されているのか読むことができればスッキリしそうですよね。 今回、 PHP 本体の ソースコード を読む方法について、具体例を交えて紹介します。 PHP の ソースコード について PHP の ソースコード 自体は C言語 で実装されています。 C言語 と聞くだけでハードルが高く感じるかもしれませんが、標準関数のコードを読んでみる程度であれば意外と雰囲気で読めます。 C言語 の基本的な文法が分かる、雰囲気で上から下へ ソースコード が読むことができれば十分です。 標準関数の ソースコード を読むコツ キーワードは「 PHP _FUNCTION(関数名)」 雰囲気で理解する ある程度納得したらやめておく、適度に追いかける PHP 本体のコア部分は追わない 以上を抑えておけば雰囲気で読むことができます。 PHP 本体の ソースコード を読んでみる 今回はsleep関数のコードを例に追ってみます。 sleep ( int $seconds ) : int https://www.php.net/manual/ja/function.sleep.php この関数の引数が0だったとき、どんな挙動になるのか気にしながら見ていきたいと思います。 まずは ソースコード を入手 php -srcを下記から入手します https://github.com/php/php-src git環境があるならclone、 ソースコード を読みたいだけなのでzipで入手しても良いと思います。 入手した ソースコード を開く 規模のある ソースコード なので、 ソースコード 全体を検索しやすいエディタ、 IDE がオススメです。 今回は VSCode を使います。 「File > Open Folder」で php -srcを開きます。 関数の定義を探す PHP の標準関数は「 PHP _FUNCTION(function_name)」で定義されています。 PHP では、独自の関数(マクロ)を作成することができますが、 こちらでも関数は「 PHP _FUNCTION(function_name)」で定義するものだと説明されています。 関数の定義方法を詳しく知りたい場合には参考になります。 http://php.adamharvey.name/manual/ja/internals2.funcs.php 今回はsleep関数を追ってみるので、 php -srcの中で「 PHP _FUNCTION(sleep)」で検索をしてみます。 下記のようにすぐ見つかります。 関数の中身を読んで見る 上から見ていきます。 まず、「ZEND_XXXX」が出てきますが、ここは PHP のコアなところです。 PHP の 構文解析 や実行エンジンを担っているコアなところは「Zend Engine」と言われています。 PHP の ソースコード 内で出てくる「zend」はコアなところに繋がると思って良いと思います。 そのため、雰囲気で読むだけあればここを追う必要はありません。 ちなみに、ここは「PARSE」や「PARAMETERS、PARAM」と名前が付いたものが呼ばれているぐらいなので 引数を解析しているぐらいの理解で大丈夫だと思います。 その次は、引数が負の値だった場合は、エラー処理をしています。 最後に、「 php _sleep」というマクロが呼ばれてます。 ここを追っていきます。 関数定義を追っていく 「 php _sleep」の定義を探してみます。 2つのマクロ定義が見つかりますが、よくよく見ると windows とそれ以外みたいな分岐があります。 実行環境が windows と Unix 系で違うんだ…ぐらいの理解で大丈夫です。 windows の場合は、さらに「SleepEx」が呼ばれているので、さらに深追いしましょう。 もう一方は、「sleep」なので、ここで C言語 のsleepを叩いているようです。 さらに深追い①( windows の場合の挙動を追う) ここまでと同様に「SleepEx」の定義を探してみます。 しかし、先ほどのマクロ定義のところでしか引っかかりません。 PHP 内の定義されていない場合は、ライブラリまたは C言語 で定義されたものである可能性が高いです。 あとは適当にググってみます。 すると、SleepExはWin32APIで定義されている関数みたいです。 https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-sleepex windows の場合の挙動なので納得できます。 0秒の場合の挙動についても書かれています。 追うのはここまでにしておきます。 さらに深追い( windows 以外の場合の挙動を追う) C言語 の「sleep」まではたどり着けましたが、そこから先は明記されていませんでした。 https://www.ibm.com/support/knowledgecenter/ja/SSLTBW_2.2.0/com.ibm.zos.v2r2.bpxbd00/rtsle.htm ここから先は C言語 の仕様に依存する部分なので、今日はこのくらいにしておきましょう。 0秒を渡したときは、最終的に C言語 のsleepがいい感じに0秒スリープしてくれる、はずです。 まとめ PHP のコアコードの読み方を解説してみました。 身近な関数の実装であれば、意外と雰囲気だけで読むことはできます。 今回紹介した手順を参考にしていただければ、他の関数でも試してもらえますので、 一度、普段使っている PHP の標準関数の ソースコード を読んでみてはいかがでしょうか。 エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 forms.gle イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! rakus.connpass.com
はじめに 背景 Laravelのセッション管理について セッションドライバについて セッションの操作 セッションの共有を検討してみた LaravelでDBを使ってセッション管理する セッション共有の検討 Laravelのカスタムセッションドライバ まとめ はじめに こんにちは、開発エンジニアのシカタです。 今回は、開発を担当するプロダクトで アーキテクチャ を刷新することになり、Laravelの検討したときのお話です。 アーキテクチャ の刷新だけにたくさんの課題がありましたが、その中でも今回はセッションのことについて書きます。 Laravelをやってみようとか、既存プロダクトにLaravelを載せてみたいなーと思ってる人の心に刺されば幸いです。 背景 既存の運用中プロダクトの一部サブシステムを、新 アーキテクチャ で構築し直すプロジェクトがスタートしました。 一気にリリースすると影響が大きすぎるので、一部の機能から徐々にリリースしていく方針です。 現在ノン フレームワーク のため、せっかく新しくするなら フレームワーク を採用したい… ということで、Laravelを検討してみることになりました。 既存プロダクトのノン フレームワーク な世界と、Laravelで新しく作る世界を共存させることを検討する必要があり、 フレームワーク の機能を活用することを目指していますが、たくさんの課題が出てきました。 アーキテクチャ どうしよう… Laravelを活かすにはどういう アーキテクチャ で共存させるのが良いのか… 既存プロダクトとLaravelでどうやってセッション共有しよう… 認証周りとかどうするのが良いんだろう…etc 今回は課題の中からセッションについて取り上げます。 Laravelのセッション管理について まず、Laravelの基本的なセッション管理についてです。 セッションドライバについて Laravelでは、file、 cookie , DBなどでセッション情報を管理することができます。 config/session.php にセッションに関する設定は記載されています。 config/session.php return [ /* |-------------------------------------------------------------------------- | Default Session Driver |-------------------------------------------------------------------------- | | This option controls the default session "driver" that will be used on | requests. By default, we will use the lightweight native driver but | you may specify any of the other wonderful drivers provided here. | | Supported: "file", "cookie", "database", "apc", | "memcached", "redis", "dynamodb", "array" | */ 'driver' = > env('SESSION_DRIVER', 'file'), : : ] セッションの操作 Laravelでセッションの操作を行うには、 Requestインスタンス 、 sessionヘルパ 、 Sessionファサード を使う方法があります。 基本操作に大きな違いはありません。 <?php namespace App\Http\Controllers; use App\Http\Controllers\Controller; use Illuminate\Http\Request; use Illuminate\Support\Facades\Session; class HogeFunction extends Controller { /** * @param Request $request * @return Response */ public function hogee ( Request $ request ) {   /* セッションから1つデータを取得する */ // Requestインスタンス $ value = $ request -> session () -> get ( 'key' ) ; // Sessionヘルパ $ value = session ( 'key' ) ; // Sessionファサード $ value = Session :: get ( 'key' ) ;   /* セッションの値をすべて取得する */ // Requestインスタンス $ values = $ request -> session () -> all () ; // Sessionヘルパ $ values = session () -> all () ; // Sessionファサード $ values = Session :: all () ;   /* セッションに値を保存する */ // Requestインスタンス $ request -> session () -> put ( 'key' , 'value' ) ; // Sessionヘルパ session ([ 'key' => value ]) ; // Sessionファサード Session :: put ( 'key' , 'value' ) ; /* セッションに値を削除する */ // Requestインスタンス $ request -> session () -> forget ( 'key' ) ; // Sessionヘルパ session () -> forget ( 'key' ) ; // Sessionファサード Session :: forget ( 'key' ) ; /* セッション中に値が存在することの確認(existsはnullでもTrueを返す) */ // Requestインスタンス $ request -> session () -> has ( 'key' ) ; $ request -> session () -> exists ( 'key' ) ; // Sessionヘルパ session () -> has ( 'key' ) ; session () -> exists ( 'key' ) ; // Sessionファサード Session :: has ( 'key' ) ; Session :: exists ( 'key' ) ; } } セッションの共有を検討してみた 既存プロダクトとLaravelでセッションを共有させる方法を検討してみました。 既存プロダクトがDBでセッション管理されているため、今回はLaravelでもDBを用いたセッション管理を前提に考えています。 また、既存プロダクトではセッション周りは純粋な PHP で実装していますが、Laravelのセッション周りは独自実装になっています。 どこかで session_start(); しておけばいい感じになるだろう、というわけにはいきません。 この違いも少し厄介なポイントです。 LaravelでDBを使ってセッション管理する LaravelでDBを使いセッション管理する場合は、以下のようなテーブルを作成する必要があります。 Schema::create('sessions', function ($table) { $table- > string('id')- > unique(); $table- > foreignId('user_id')- > nullable(); $table- > string('ip_address', 45)- > nullable(); $table- > text('user_agent')- > nullable(); $table- > text('payload'); $table- > integer('last_activity'); }); 以下のように設定しておくと、セッション管理に使用するテーブルと認識されます。 config/session.php return [ /* |-------------------------------------------------------------------------- | Default Session Driver |-------------------------------------------------------------------------- | | This option controls the default session "driver" that will be used on | requests. By default, we will use the lightweight native driver but | you may specify any of the other wonderful drivers provided here. | | Supported: "file", "cookie", "database", "apc", | "memcached", "redis", "dynamodb", "array" | */ 'driver' = > env('SESSION_DRIVER', 'database'), : : /* |-------------------------------------------------------------------------- | Session Database Table |-------------------------------------------------------------------------- | | When using the "database" session driver, you may specify the table we | should use to manage the sessions. Of course, a sensible default is | provided for you; however, you are free to change this as needed. | */ 'table' = > 'sessions', : : ] セッション共有の検討 既存プロダクトで使用しているテーブルを使用するか、Laravelに合わせたテーブルを使用するのか…他にもいくつか選択肢がありました。 少なくとも、Laravelのセッション機能を活用しようと思うと、 LaravelでDBを使ってセッション管理する際にテーブル定義の通り作成が必要となるようです。 Laravelのセッションが確立するまでの独自実装を追ってみると、以下にたどり付きました。 Illuminate/Session/DatabaseSessionHandler.php ここを見る限りは指定されているテーブル定義前提で動くような実装になっています。 セッション管理のテーブルやセッション周りの実装が似ていれば移行前提で検討できるかもしれませんが、そうでなければ難しいです…。 よって、LaravelデフォルトのDBのセッションドライバのままでは、既存プロダクトとのセッション共存は困難であることがわかりました。 しかし、Laravelにはカスタムセッションドライバという仕組みがあり独自のセッションドライバを実装できます。 Laravelのカスタムセッションドライバ 以下のように、 SessionHandlerInterface を実装することで独自のセッションドライバの作成が可能です。 <?php namespace App\Extensions; class CustomSessionHandler implements \SessionHandlerInterface { // open: ファイルベースでなければ空でOK public function open ( $ savePath , $ sessionName ) {} // openと同様 public function close () {} // セッションの読み込み処理($sessionIdに紐づくセッションデータの取得) public function read ( $ sessionId ) {} // セッションの書き込み処理($sessionIdに紐づく$dataの書き込み) public function write ( $ sessionId , $ data ) {} // セッションの読み込み処理($sessionIdに紐づくセッションデータの削除) public function destroy ( $ sessionId ) {} // セッションの読み込み処理($lifetimeを超えたセッションデータの削除) public function gc ( $ lifetime ) {} } 次に、作成したカスタムセッションドライバの登録方法についてです。 Session ファサード の extend メソッドで登録することができます。 ServiveProvider の boot メソッドで上記メソッドを呼ぶことでカスタムセッションドライバの登録ができます。 既存の AppServiceProvider またはセッション用に作成した ServiceProvider から呼び出します。 下記は ServiceProvider から呼び出す例です。 <?php namespace App\Providers; use App\Extensions\CustomSessionHandler; use Illuminate\Support\Facades\Session; use Illuminate\Support\ServiceProvider; class SessionServiceProvider extends ServiceProvider { /** * 全アプリケーションサービスの登録 * * @return void */ public function register () { // } /** * 全アプリケーションサービスの初期起動 * * @return CustomSessionHandler */ public function boot () { Session :: extend ( 'custom' , function ( $ app ) { // Return implementation of SessionHandlerInterface... return new CustomSessionHandler; }) ; } } 最後に、作成したカスタムセッションドライバを config/session.php で設定すると、使用できるようになります。 config/session.php return [ /* |-------------------------------------------------------------------------- | Default Session Driver |-------------------------------------------------------------------------- | | This option controls the default session "driver" that will be used on | requests. By default, we will use the lightweight native driver but | you may specify any of the other wonderful drivers provided here. | | Supported: "file", "cookie", "database", "apc", | "memcached", "redis", "dynamodb", "array" | */ 'driver' = > env('SESSION_DRIVER', 'custom'), : まとめ Laravelのセッションについてまとめてみました。 既存のプロダクトとLaravelでセッションをさせる方法は現在検討中なので、 また次の機会に実際に実装したカスタムセッションドライバなどは紹介できればと思います。 エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 rakus.hubspotpagebuilder.com ラク スDevelopers登録フォーム https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/ イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! ◆TECH PLAY techplay.jp ◆connpass rakus.connpass.com
こんにちは。エンジニアのrs_shoです。 投稿は4回目になります。今回は 排他制御 についてお話ししようと思います。 はじめに 排他制御の種類 楽観ロック(楽観的排他制御) 悲観ロック(悲観的排他制御) それぞれの特徴 Javaにおける排他制御 Semaphore CountDownLatch synchronizedメソッド データベースのLOCK おわりに 参考資料 はじめに そもそも 排他制御 とは何か、ご存じでしょうか。 排他制御 とは、簡単に言うと、処理Aをしている間、他の処理は処理Aが終わるまで待つ、という制御のことです。 身近な例えで言うと、共有フォルダにある Excel などを誰かが開いて編集している間、 他の人は閲覧のみ可能で編集は不可の状態(設定によってはできますが…)のことです。 排他制御 の種類 排他制御 はただ処理待ちをするだけではなく、大きく分けて2つ種類があります。(厳密にはもっと細かく分かれていると思います) 楽観ロック(楽観的 排他制御 ) と 悲観ロック(悲観的 排他制御 ) です。 楽観ロック(楽観的 排他制御 ) 楽観ロックは同時更新は起こらないであろう、という前提の 排他制御 のことです。 更新対象のデータをロック(編集不可)することはせず、 更新時に「手元のデータの編集開始時点の状態」と、「更新対象データの状態」が同じかを確認して更新を行います。 誰でも編集・更新していいけど、更新時に他の人が上書きしていたら更新できないよ!っていう制御です。 個人的には「楽観的(前向きな考え)という割には、更新時にデータが吹っ飛ぶ可能性があって怖いな」と思いますね。 悲観ロック(悲観的 排他制御 ) 悲観ロックは同時更新が起こる可能性がある、という前提の 排他制御 のことです。 更新対象のデータをロックして、自分の編集・更新が終わるまで、他の人が編集できない状態にしてしまいます。 編集中は見たり、コピーを作ることはできても、原本に手を加えることはできないよ!っていう制御です。 こちらの方が自分が開いている間は誰も更新できないので、なんとなく安心感がありますね。 それぞれの特徴 先程説明した楽観ロック・悲観ロックについて、特徴や違いを挙げていこうと思います。 前提 楽観ロック:同時更新は起きないであろう 悲観ロック:同時更新が起きるかもしれない 整合性チェック 楽観ロック:データ編集終了(更新)時 悲観ロック:データ編集開始時 どの処理に向いているか 楽観ロック 同時更新がめったに起きない処理(単体で行う処理) 不整合が起きてもやり直せる処理 短時間で終わる処理 悲観ロック 同時更新が頻繁に起こる可能性がある処理(分担する処理) 不整合が起きてほしくない処理 長時間続く処理 Java における 排他制御 僕は業務中にある種別の中のカテゴリ内で番号が重複しないように実装しようと思い、 排他制御 について調べました。 (例えば、動物という種別の、哺乳類カテゴリの1:象, 2:イルカ …といった感じで、IDが重複しないように実装したかった) 上記はシーケンスを使えばいい、と思うかもしれませんが、カテゴリ毎にIDを1からスタートする場合、 カテゴリを追加する度にシーケンス生成が必要になるので、あまり現実的ではありません。 ですが、DB内で保持しているIDの最大値を取得するだけでは、採番処理が同時に行われた際に最大値が重複し、結果採番されたIDが重複してしまいます。 僕の個人的な解釈になりますが、調べた中で 排他制御 に使用できそうな方法を軽くご紹介しようと思います。 (いずれどこかでより詳しく深堀りしてみたいと思います。) Semaphore アクセス数を制限して、設定した値を超える数の処理が発生した場合、処理待ちをさせる 通常の 排他制御 はSemaphoreのロックが1つ(1つ以上は実行させない)の状態と同じ ロックを解放する方法が特殊で、実装を誤ると正常に機能しない(処理実行可能数を増やし続けてしまい、意図した 排他制御 ができない) CountDownLatch 複数の処理をブロック丸ごとロックする時に有効 引数にロック数を指定して、処理毎にロック数を減算、0になったらロックを解除する 複数メソッドにまたがってロックしたいときなどに有効だが、ロック数が0になったらロックが解放されるので、引数で渡す値には注意が必要 synchronizedメソッド メソッドの修飾子や 入れ子 で使用することで、同時実行をブロックできる 修飾子に追加する or 処理ブロックを 入れ子 にしてくくるだけで実装できる ただし、複数の継承先から参照されるクラスの場合は、 インスタンス 毎に処理が同時実行されてしまう可能性がある データベースのLOCK LOCK文を実行してテーブルのロックを取得、そのテーブルにアクセスしている処理があれば、解放されるまで待つ データ自体にロックがかかるので、整合性を保ちやすい ただ、 RDBMS の種類によっては、SELECTなどでもロックを取得してしまい、 デッドロック が発生したり、勝手に トランザクション をコミットされたりしてしまうため、注意が必要 おわりに 以上、 排他制御 についてざっと説明させていただきました。扱いが難しいですが、使いこなせればデータの整合性を保証できます。 この記事を通して 排他制御 について知っていただく、調べてみるきっかけになれば幸いです。 紹介した方法についても、いずれ深堀りしていこうと思います。 参考資料 排他制御(楽観ロック・悲観ロック)の基礎 【Java】マルチスレッド処理 - 排他制御 Javaの排他制御(ロック)に関係するクラスまとめ PostgreSQLは、SELECTもロックを獲得する エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 rakus.hubspotpagebuilder.com ラク スDevelopers登録フォーム https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/ イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! ◆TECH PLAY techplay.jp ◆connpass rakus.connpass.com
はじめに 株式会社 ラク ス チャットディーラー開発課のエンジニアRakusMoritaです。 2021年3月26日(金)~3月28日(日)開催のPHPerKaigi2021に、 ラク スエンジニア7名が参加してきました。 phperkaigi.jp PHPer(ペチパー)によるPHPerのためのこの大規模イベントは、今年はオンラインでの開催でした。 オンラインでありながらも、豪華な登壇者、絶えず流れるコメント、主催者の勉強になるコントなどなど・・・ お祭り騒ぎのような雰囲気伝わってきて、ワイワイと非常に楽しい勉強会でした。 ラク スは当イベントのスポンサーとして参加 させていただいている他、社内から2名が登壇しました。 この記事では、PHPerKaigi2021に参加した社内のエンジニアが、参加したセッションの内容をまとめましたので、ご紹介したいと思います。 各セッションのスライドも用意しておりますので、PHPerな人、これからPHPerになる人は新たな発見があること間違いなしです。 ~目次~ はじめに 3/27(土) 実践ATDD 〜TDDから更に歩みを進めたソフトウェア開発へ〜 負荷試験の観点から見るGraphQLにおけるPHPのコードチューニング 目的に沿ったDocumentation as Codeをいかにして実現していくか PHPでCSVを安心して扱うために PHPで学ぶ、セッションの基本と応用 PHP8になった今の時代に、PHPの「エラー」「例外」そして「Error」をおさらいしておこう Laravel のメール認証の内部実装を掘り下げる PHPWebアプリケーションパフォーマンスチューニング PHP でもアーキテクチャテストしたい! 3/28(日) そのコード、フレームワークの外でも動きますか? 無駄な物をなるべく作らないリプレイス戦略 なるせ先生のPHP学~PHP8新機能スペシャル~ 今こそ理解するDI(Dependency Injection) プログラマ三大美徳を実現するデプロイフローを目指して PHPのDI、attributesとこれから DNSを制する者はインターネットを制す!DNSの世界/市川@cakephper 新社会人のコード品質カイゼン記録 あとがき 3/27(土) 実践ATDD 〜TDDから更に歩みを進めたソフトウェア開発へ〜 report by id:radiocat speakerdeck.com ATDDとはAcceptance test-driven developmentの略で、日本語にすると受け入れ テスト駆動開発 です。 アジャイル 開発では開発要件をユーザーストーリーとして表現し、それが実現できていることを開発の完了条件として受け入れテストを実施する手法がありますが、 それを駆動型で開発に取り入れるやり方がATDDです。登壇者の現場で考えられている、中心となる2本柱が次の内容です。 四象 限におけるQ2を強化する 四象 限とは技術面とビジネス面の2軸と、開発チーム支援と製品批評の2軸を組み合わせたもので、Q2はその中でビジネス面と開発チーム支援の領域です。顧客視点の高レベルテストで自動化と手動テストを組み合わせるところがポイントです。 テストピラミッドを登る テストピラミッドとは ユニットテスト を土台として、サービステスト、UIテストと上位に階層化したテストレイヤーに分類してテストを概念化したもので、上位のUIテストのほうが重厚化してしまう アンチパターン に陥らないように必要に応じてテストピラミッドを登っていくことがポイントです。 知識的情報だけでもボリュームたっぷりでしたが、さらに PHP で作られた Webサービス を題材とした ケーススタディ では、自動受け入れテストツールを使った実践例も紹介されています。ATDDの自動化領域と手動テストとの共存のさせ方、 ユニットテスト などのTDDの手法との組み合わせ方などについても語られており、実践に向けた応用方法の示唆に富む内容でした。知識のインプットだけでなく実践に向けたインプットにも役立つボリュームたっぷりなセッションだったので、今後も資料を読み返して現場で実践活用したい内容です。 負荷試験 の観点から見るGraphQLにおける PHP のコードチューニング report by id:Y-Kanoh speakerdeck.com API で利用するクエリ言語である GraphQL は、REST と違い、複数のクエリを1度のリク エス トで行うことができます。 そのため、複数のクエリ発行のために何度もレスポンスを待つ必要がなく、 レイテンシは REST と比べて速くなる特性があります。 しかしこの仕組み上、秒間のリク エス ト数低下や、短時間に処理が集中してしまいCPU負荷が高くなること、 重いクエリが含まれると全体のレスポンスに影響を与えることなど、デメリットも存在します。 利用時には、取得するデータを絞る仕組みや、複雑すぎるクエリを制限する仕組み、 PHP の場合は処理向上のためのOPCache導入などを行なって、コードチューニングを行う必要があります。その詳しい勘所について説明されていました。 REST との比較を行いながらわかりやすく説明していただけたので、両者のメリットデメリットなども理解できました。 実際使ってみての勘所を説明いただけていたので、GraphQL の導入を考えるかたはぜひ確認していただければと思います。 目的に沿ったDocumentation as Codeをいかにして実現していくか report by id:Y-Kanoh speakerdeck.com 開発者がチームに join し、開発を始められる状態になるまでのオーバヘッドを削減する方法が、システムの理解を助けるドキュメントです。 しかし、更新漏れによる誤った理解を防ぐために、ドキュメントはメンテナンスを継続的に行わなければなりません。一方、開発と切り離された運用でのメンテナンスは、システムとの乖離を修正するまでの時間も長くなってしまい、 継続的なメンテナンスも難しくなってしまいます。 そこで、この発表では、システムとドキュメントをそれぞれ情報の集合と考え、 各情報同士が紐づいていない状態が増えることを「システムとドキュメントの乖離が大きくなった」と位置付けることで、 この問題の解決策を探っています。 その方法として、PHPDocなどのように、”システムとドキュメントの間に構造化データを用意し、 その構造化データをもとにドキュメントを作成する方法”や、OpenAPIのように “構造化データを先に用意し、 そこからシステムとドキュメントを作成する” 方法などが紹介されていました。 また、具体的に対策を行うためのツールも紹介されており、 どのような仕組みで対策を行うことができるのか想像しやすい内容でした。 ドキュメントのメンテナンス問題は私自身も思い当たることが多く、 発表内容の前半は胸が痛い内容でした。エンジニアらしく問題の本質を分析し、 仕組みでそれをカバーすることを考えさせられる内容でした。 PHP で CSV を安心して扱うために report by id:tsudachantan speakerdeck.com 他サービスとのデータ連携やバルクデータ入出力などで使い勝手が良く、根強く利用されている CSV ファイル。 PHP での CSV 処理は癖が強く、一見安全に対応できたように見えて運用面で深刻な問題を抱えることがあります。 現状とその解決方法について丁寧にお話いただきました。 fw3/streams OSに依存しない安定した入出力と、発生していた問題の根本解決としてライブラリfw3/streamsを紹介されていました。 以下が特徴です。 OSと PHP バージョンの組み合わせに影響されず動作する 安全かつ確実に入出力が可能である mb_convert_encordingを使った文字セットの変更ができる 妥当な文字セット自動検出が行える OSに影響されない改行コード出力を行える 変換できない文字があった場合の代替手段を設定できる ライブラリの説明に沿って様々な環境やパターンを網羅して CSV の扱い方について発表されており、大変勉強になりました。 PHP で学ぶ、セッションの基本と応用 report by id:soachr speakerdeck.com 以下の章立てで発表されていました。 生い立ち 技術的な仕組み 最低限の周辺知識 PHP 以前に、そもそもHTTPでセッションが使われるようになった背景から、現在どのようにセッションが使われているのかまでを紹介された上で、じゃあ実際に PHP はどのようにセッションを実現しているのか、という内容まで紹介されていました。 今までふわっとしていた PHP のセッションについて理解できました。 ログイン機構があるシステムに関わっている方はとりあえずこの発表を聞けば問題ないと思うくらい、わかりやすい&すっと理解できる内容でした。 新卒時代のときにこの発表に出会っていればもっとスムーズに理解できたのでは...とこの発表で初めてセッションについて学んだ方が羨ましいと思うくらいです。新卒の育成担当をしている身としては、ぜひ学習カリキュラムに入れさせていただきたいと強く感じました。 PHP8になった今の時代に、 PHP の「エラー」「例外」そして「Error」をおさらいしておこう report by id:tsudachantan speakerdeck.com PHP には「エラー」を知らせる仕組みがたくさん用意されていますが、それぞれの違いが答えられるでしょうか。 PHP のエラーの扱いはバージョンごとに進化しており、それが混乱する原因にもなりえます。 とにかく不用意に\Throwable、\Errorをキャッチすることは危ない!というメインの主張を。 では、そもそもエラーとは?という説明からお話しされており、納得しながら整理できました。 まだまだ PHP 歴が浅い自分や、わかっているけど人に説明するのは難しいといった方にも役に立つ内容だと思います。 「Throwable/Error/Exception」についての棲み分けについての認識がクリアになり、効果的な例外の使い方について考える機会になりました。 Discordのチャンネルでも様々な意見が交換されており、チーム開発での意識のすり合わせの大切さを感じました。 Laravel のメール認証の内部実装を掘り下げる report by id:Y-Kanoh speakerdeck.com PHP の フレームワーク であるLaravelは、さまざまなロジックが既に用意されており、 メール認証の仕組みも、少しの実装を行うことで、簡単に利用することができます。 しかし、既存のロジックは手軽さのために、拡張性が犠牲になっている部分があり、 独自の仕組みを組み込みたい場合は、内部ロジックを理解した上での改修が必要です。 メール認証の場合、この拡張性を損なっている原因は、App User モデルの責務が多すぎることです。 App User の持っている責務を明確化して、それぞれの依存関係を理解した上で、 分離独立させることにより、拡張性の高いロジックに作り替える方法を紹介いただいていました。 近年、機能が多くなりすぎて批判されることもある Laravel ですが、内部の依存関係を理解し、 責務に沿って作り替えれば上手く付き合っていけることを学べた発表でした。 PHPWebアプリケーションパフォーマンスチューニング report by id:soachr speakerdeck.com パフォーマンスチューニングについて、体系的にかつ具体的に説明されていました。 パフォーマンスチューニングの大原則の考え方である「推測するな、計測せよ」を軸として、どのように再現・計測・原因仮説、からの対策を取るのかを、Webアプリケーションを実現する ミドルウェア ごとに紹介されていました。 自身もパフォーマンス・チューニングを業務で行っていたのですが、まだ知らない視点や、計測時のコマンド、対策を知れてとても勉強になりました。 パフォーマンス・チューニングをやったことがない人で「なにから初めて何をすればいいのかわからない」方にもおすすめです。 PHP でも アーキテクチャ テストしたい! report by id:radiocat speakerdeck.com 弊社のリードエンジニア川並が アーキテクチャ テスト について発表しました。 コードレビューが機能している状況においても、 アーキテクチャ 観点でしっかりレビューするのは難しく、開発初期にしっかり検討していた設計方針がいつのまにか泥団子状態ということも起こりえます。 アーキテクチャ を維持するために、クラスの依存関係や実装ルールをコード化して自動テストするのが アーキテクチャ テストです。 発表では PHP の フレームワーク である deptrac と phpat を使った アーキテクチャ テストの実例がデモを交えて紹介されました。 デモで使われたサンプルコードが以下で公開されています。 github.com アーキテクチャ は一度決めたら終わりではなく、 アーキテクチャ テストが アーキテクチャ を育て、進化させていく!という、希望に満ちたまとめで勇気をもらえるお話でした。 3/28(日) そのコード、 フレームワーク の外でも動きますか? report by id:Y-Kanoh speakerdeck.com アプリケーションの長期運用には、ビジネスサイド要因による仕様変更の他にも、 フレームワーク のバージョンアップ/メンテナンスの終了や、 時代の変化によって利用していた フレームワーク が陳腐化してしまうなど、リプレースを求められることがあります。 そのような時に、 フレームワーク にガッツリ依存したコードになっていると、 大半のコードを書き直す必要が出てきてしまいます。 この発表では、リプレースやリニューアル時に飛型しないために、 チームで作成するコードを フレームワーク から分離し、 環境の変化に強いコードを作成する例を紹介していただけました。 発表中には、実際にLaravelで実装されたコードを、 Symfony に移植するサンプルコードを説明していただき、 具体的にどのような効果が得られるかを体験できました。 また、最後におっしゃっていた、違う フレームワーク や、違う仕組み、違う言語など、 「バリエーション」に強いコードにすることで、結果的に保守性を向上させるという考えが、 とても共感できました。 資料の中に ソースコード のリンクもあるので興味のある方は覗いてみてください。 無駄な物をなるべく作らないリプレイス戦略 report by id:MasaKu speakerdeck.com レガシーソースのリライトについてのお話でした。 リプレイス戦略は以下の段階で難易度が上がっていきます。 リファクタリング リプレイス リライト リライトとは、既存機能を完全に一から書き直すという作業であり、この作業を実施するには、そもそも なぜリライトが必要なのか という課題を明確にして、どのように書き直すかの方針を新たに決定する必要があります。 リライトを始めるには基本的なこととして、現状の仕様を完全に理解する必要があります。 そのための方法は コードリーディング リファクタリング 設計の振り返り 実際にサービスを使ってみる などの方法を行います。 また、実際のユーザと直接対話することも重要です。 サービスが提供している価値はどこにあるのかを再認識するためにも、ユーザストーリーを立てて、本当にリライトが完了するかを見ていく必要があります。 ご発表の中で リファクタリングを限界までしても現状の問題が解決しないと思ったらリプレイス という判断基準をご紹介いただき、とても理解しやすいご説明でした。 なるせ先生の PHP 学~PHP8新機能 スペシャ ル~ report by id:Y-Kanoh PHP8の新機能であるmatch式、NullSafe 演算子 、Attributeを、某テレビ番組風に紹介していただけました。 match式 フォールスルー(brakeがないと次の処理を実行してしまう)がなくて事故が起きにくい マッチする条件がないとエラーになるから安全 再帰 処理で使いやすい コードの見通しが良くなる NullSafe 演算子 オブジェクトがNullの場合、指定されたメソッドが実行されない 似たようなものでNull合体 演算子 や、 エルビス 演算子 がある エルビス 演算子 の” エルビス ”は、エルヴィス プレスリー の髪型が語源 Attribute 言語としてサポートされるようになった アノテーション 今の形になるまで様々な議論があった アトリビュート を読み取って トランザクション を張るなどの応用がありえる また、新機能を単に紹介するだけでなく、その機能の便利なところや、 ちょっとした裏話も共有していただけて、ドキュメントを読むだけでは得られない内容でした。 個人的には、エルヴィス プレスリー が初耳でした。 今こそ理解するDI( Dependency Injection) report by id:MasaKu speakerdeck.com DIについて詳しく再認識させていただけたご発表でした。 というのも、個人的にDIについていろいろと調べていたことがあったのですが、結局これがDIなのかな? といったぼんやりとした理解だったので、今回のご発表を聞き、少し理解が進んだように感じました。 DIとは保守性の高いコードを書くことを目的とした設計原則とパターンのセットであり、 疎結合 を実現することを目的とした デザインパターン です。 概念としては、制御の逆転を行い、必要になった時だけ呼び出す、という流れで外部からオブジェクトを使用するようにします。 例えば、メールを送信するメソッドを例にすると、外部から該当のメール送信メソッドを呼び出す際、メソッド内でメール送信オブジェクトを生成してしまうと、そのメソッドはメソッド内で生成したオブジェクトを自由に付け替えることができなくなくなります。 検証のタイミングでは、検証用のメールサーバを利用して、本番では AWS のメールサーバを利用する、など使い分けをしたい場合、そのメソッド内でオブジェクトを生成してしまうと、コードを修正しなければオブジェクトの付け替えができません。 こういった密結合の状態を回避するのがDIです。 なお、DIの説明がある際にDIコンテナの話も良く出てくるが( フレームワーク の標準機能として搭載されている場合が多い)、DIを実現するためにDIコンテナが必要なわけでなく、DIを実現しやすくするための工夫として存在しているものなので、DIコンテナは必須ではないようです。 また、類似の デザインパターン として、サービスロケーターの話もよく登場するが、目的はDIと同じだが実現する手段がが異なる デザインパターン とのことです。 プログラマ 三大美徳を実現するデプロイフローを目指して rebort by id:Jazuma プログラマ三大美徳を実現するデプロイフローを目指して from 智也 鈴木 www.slideshare.net 株式会社 M & A Cloud 所属 @yamotuki さんによるセッションです。 「 ベンチャー におけるプロダクトやチームの成長とデプロイフロー自動化」というサブテーマのもと話が進みました。 (デプロイフローにおける) プログラマ の三大美徳 怠惰 :同じ作業の繰り返しは面倒くさい →自動化したい 傲慢 :デプロイ手順をミスってエラーになるのはダサい →自動化したい 短期 :デプロイ作業に2時間もかけたくない →2秒で終われ! デプロイフロー自動化の歴史 サービス開発当初はデプロイフローが自動化されていませんでした。 開発者の人数が少ないのでやっている時間がない 売上が立っていないので運用フロー効率化よりも機能追加優先 自動化のノウハウがない 自動化文化がない ユーザーアクセスが少なく、障害発生リスクがない しかし、サービスが軌道に乗ってくると自動化をする理由が出てきました。 売上が立った 開発者が増えて手動作業だとミスが起こる可能性が大きくなった デプロイフローそのものが複雑になり手動作業がつらくなる 運用に時間を取られて機能追加もおろそかに なによりめんどくさことはしたくない! このような経緯でデプロイフロー自動化が段階的に進みました。 セッションの中で 「プロダクトとチームの成長は自動化と密接に関係している」 と述べられていたのが印象的でした。 エンジニアはつい自動化それ自体が目的になりがちですが、チームやプロダクトがどのようなフェーズにあり、どんな問題を解決するために自動化をするのかということが重要だと思いました。 PHP のDI、attributesとこれから rebort by id:rakusMorita speakerdeck.com DI( Dependency Injection・依存性の注入)とは、簡単に言うと Dependency :使われるオブジェクトを Injection:使う側のオブジェクトに渡す ということです。DIについては詳しく説明しませんが、 具象クラス(具体的な実装をしているクラス)との処理の結合を避けて、抽象クラスと処理を結合させることで、変更に強くすることが基本的な考え方です。 注入とは、 インスタンス を抽象クラスから作成して動かすというものです。まだ具体的な実装ができていなくてもダミーの インスタンス を作りやすい(モックの作成がしやすい)ので、その結果、実装もそうですが、 単体テスト なども非常にやりやすくもなります。 DIの歴史とポイント DIは Java から始まった PHP5でクラスの機能が強化され、DIっぽいことができるようになった。 近年 PHP でもDIが標準的な思想として認知されつつある(型指定が当たり前に/interfaceが使いやすくなった/autoloadでrequire不要に等々) 今までの型指定のない PHP 的な書き方もできますし、いい意味で Java っぽさも取り入れた実装もできるようになってきて、 PHP は良いとこ取りな言語に進化してきましたね。 APC やOPchaceなどによるコードキャッシュにより、実行速度も向上し、ますますDIがやりやすくなりました。 今時な フレームワーク (Laravel、 Symfony 、 CakePHP あたり)はDIコンテナを持っている(あるいは持とうとしている)ということは、特に意識しなくてもDIを使えるようになってきたと言えるでしょう。 PHP の動向 PSR-11という インターフェイス が普及 異なる フレームワーク やライブラリのを使っていても同じように呼び出せるDI用の インターフェイス が使えるようになりました。 PHP -DIというDI用のライブラリの存在 PSR-11互換。 フレームワーク と組み合わせて使えるので便利です。 @Inject( アノテーション )記法が廃止になって#[Inject]( アトリビュート )になった アノテーション は結局のところPHPDocだったので、文字列の解析になるので重かったのが、 アトリビュート だと言語としてサポートされていて、動作が格段に速くなりました。 Autowireという機能 明示的にInjectの必要がなくなり、設定ファイル書かなくても作れるようになりました。 DIがシームレスに使える=意識せずともDIのメリットを享受できるように開発できる ということですね。素晴らしい。 DIコンテナは今後も自然に広く使われていくことになるでしょう。 DNS を制する者はインターネットを制す! DNS の世界/市川@cakephper report by id:MasaKu ドメイン 名ハイジャックの恐ろしさについて改めて身に染みたご発表でした。 悪意あるユーザが名前解決先のIPを書き換えてしまうため、ユーザ側としては悪意あるWebページに遷移してしまっていることを認識できません( HTTPS にも対応させることができる)。 また、この問題に気づきにくくする厄介な方法として、常に悪意あるWebページに遷移させるのではなく、10人に1人だけ悪意あるページに遷移させるなどするとより気づきにくくなります。 過去の事象として、名前解決先のIPを返す レジストラ の 脆弱性 を突かれて情報改ざんされ、MXレコードが書き換えられたせいでメールも乗っ取られた、という事件も発生したようです。 この問題は レジストラ 側の問題になるため、対策のしようがないため、 改ざんを検知したらいち早く対応するなどの運用が必要 となります。 また、 DNS の歴史についてもご説明がありました。詳しい仕様についてはこの場では割愛しますが、面白かったエピソードとして、お子さんの YouTube 閲覧時間制限のために、 DNS パケットの解析を行い、アクセス制限を追加したというお話がありました。 DNS のクエリはどのIPの利用者が何時にどのアドレスの名前解決をしたか、という情報が暗号化されずに送信されるため、プライバシー上の問題もあるようです。(お子さんのプライバシーにかかわるため、上記対策は中断したとのこと) ODoHという仕組みを利用して DNS のメッセージを暗号化する ことも現在提案されているようです。 新社会人のコード品質 カイゼン 記録 rebort by id:rakusMorita speakerdeck.com ラク スのエンジニアの発表でした。 指摘が多く、コード品質が良いとは言えない状態から、どのように品質をアップさせてきたのかの発表でした。 チェックリストも、コミット前のコードの見直しも色々やってみたところ・・・ 目立った効果がありませんでした。しかし・・・! ペアプロ / モブプロ これで、大きく品質が向上したようです。 先輩エンジニアの視点や考え方がリアルタイムでわかるので、「どういうところに気を付ければいいのか」などがわかるようになったとのことです。 「 ペアプロ をしてもらえる環境が素晴らしい」 というコメントがたくさん流れていました。 もちろん、環境も大切だと思いますが、彼の 「思い切って先輩エンジニアに ペアプロ をお願いした」 こと。 その勇気が彼の品質向上に繋がった、とても前向きになれる発表でした。 きっとチームにとっても、長い目で見て開発コストを抑えることに繋がるはずなので、 ペアプロ ・モブプロは忙しい環境こそおすすめです! あとがき このPHPerKaigi2021に参加し、エンジニアの生の声を聞き、色々な考え方に直に触れることで視野が広がりました。 エンジニアの方なら、よくググったり、書籍を読んだりすると思うのですが、やはり 現場の経験から出た使えるア イデア だったり、 調べたこともない知識をインプットできる という点で、このような勉強会は貴重ですね。 皆様のより良いPHPerライフを応援しております。 エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 forms.gle イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! rakus.connpass.com
はじめに こんにちは、 id:FM_Harmony です。 iOS アプリの開発プロジェクトに参画して1年が経ち、自身の中で iOS 開発の土台が固まり、 ようやく様々な技術をキャッチアップ出来るようになりました。 そこで、今回はいま学習中のSwiftUIについて、MVVMの理解もかねて記事にしてみました。 SwiftUIについては公開されたのが2019年と新しい技術ですので、 この記事がこれから学習を始める方の参考になれば幸いです。 目次 はじめに 目次 MVVMとは SwiftUIとは アプリの概要 実装 Modelの実装 ViewModelの実装 Viewの実装 デモ おわりに 参考 MVVMとは ソフトウェア アーキテクチャ の一つです。 以下、 Wikipedia より引用します。 Model-View-ViewModel (MVVM、モデル・ビュー・ビューモデル) はUIを持つソフトウェアに適用されるソフトウェアアーキテクチャの一種である MVVMはソフトウェアをModel・View・ViewModelの3要素に分割する。プレゼンテーションとドメインを分離し(V-VM / M)また宣言的Viewを分離し状態とマッピングを別にもつ(V / VM) (中略) ### Model アプリケーションのドメイン(問題領域)を担う、そのアプリケーションが扱う領域のデータと手続き(ビジネスロジック - ショッピングの合計額や送料を計算するなど)を表現する要素である。 (中略) ### View View(ビュー)はアプリケーションの扱うデータをユーザーが見るのに適した形で表示し、ユーザーからの入力を受け取る要素である。すなわちユーザインタフェースの入出力が責務である。 (中略) ### ViewModel ViewModel(ビューモデル)はViewを描画するための状態の保持と、Viewから受け取った入力を適切な形に変換してModelに伝達する役目を持つ。すなわちViewとModelの間の情報の伝達と、Viewのための状態保持のみを役割とする要素である。 実装ではしばしば、Viewとの通信はデータバインディング機構のような仕組みを通じて行う。その場合ViewModelの変更は開発者から見て自動的にViewに反映される。 私は以下のように理解しています。 View ユーザからの入力をViewModelに伝える ViewModelの変更を画面に出力する ViewModel Viewから受け取った入力でModelを変更する Modelの変更を自身に反映する Model WebやDBにあるデータにアクセスする ViewModelによる変更を永続化する SwiftUIとは iOS 向けのUI フレームワーク で、iOS13以降で利用することができます。 宣言的に部品を記述することができるのが特徴で、 Combine フレームワーク によるイベントの通知、受信と組み合わせて、 View - ViewModel間のデータ バインディング を実現できます。 ※Combine フレームワーク もiOS13以降で利用することができます 個人的な感想ですが、SwiftUIを用いることで、 簡潔なコードで MVVMパターン によるアプリ実装を行えると思います。 アプリの概要 MVVM、SwiftUIの学習として、メモアプリを作ってみました。 大まかな仕様は以下の通りです。 1行のメモを追加できる 登録したメモは登録日の新しい順に表示する メモを選んで削除できる メモをすべて削除できる 実装 それでは実装内容の紹介に移ります。 今回はメモの一覧画面、メモのViewModel、メモのモデルを作成します。 また、登録したメモは Realm で端末に永続化します。 それぞれの関係性は以下の図の通りです。 MVVMに従い、ユーザの入力によりView - ViewModel間でイベント通知が行われ、 画面の再描画や永続化処理の呼び出しが自動的に行われます。 Modelの実装 具体的な実装内容の紹介に移ります。 まずは、Modelの実装です。 今回はModelに以下の役割を持たせています。 Realmからのデータ取得 Realmへの永続化処理 import Foundation import RealmSwift class Memo : Object , Identifiable { @objc dynamic var text = "" @objc dynamic var postedDate = Date() } extension Memo { private static var config = Realm.Configuration(schemaVersion : 1 ) private static var realm = try ! Realm(configuration : config ) static func findAll () -> Results < Memo > { realm.objects( self ) } static func add (_ memo : Memo ) { try ! realm.write { realm.add(memo) } } static func delete (_ memo : Memo ) { try ! realm.write { realm.delete(memo) } } static func delete (_ memos : [ Memo ] ) { try ! realm.write { realm.delete(memos) } } } ViewModelの実装 次はViewModelの実装です。 出力としてViewに表示するメモの一覧を、入力としてメモに登録するテキスト、削除するメモ、 全削除の処理が行われたかという状態を持たせています。 各プロパティには @Published を付けることで、値の変更が行われた際に、 変更されたことを通知できるようにしてあります。 また、 AnyCancellable 型のプロパティを持たせることで、 @Publish なプロパティの変更が行われた際に、ViewModelで自動的に永続化処理が始まるようにしています。 import Foundation import Combine class MemoViewModel : ObservableObject { @Published private ( set ) var memos : [ Memo ] = Array(Memo.findAll()) @Published var memoTextField = "" @Published var deleteMemo : Memo? @Published var isDeleteAllTapped = false private var addMemoTask : AnyCancellable? private var deleteMemoTask : AnyCancellable? private var deleteAllMemoTask : AnyCancellable? init () { addMemoTask = self . $memoTextField .sink() { text in guard ! text.isEmpty else { return } let memo = Memo() memo.text = text self .memos.append(memo) Memo.add(memo) } deleteMemoTask = self . $deleteMemo .sink() { memo in guard let memo = memo else { return } if let index = self .memos.firstIndex(of : memo ) { self .memos.remove(at : index ) Memo.delete(memo) } } deleteAllMemoTask = self . $isDeleteAllTapped .sink() { isDeleteAllTapped in if (isDeleteAllTapped) { Memo.delete( self .memos) self .memos.removeAll() self .isDeleteAllTapped = false } } } } Viewの実装 最後にViewの実装です。 Viewは MemoListView と MemoRowView に分けており、 前者がメモの一覧を、後者が登録したメモの表示を行っています。 MemoListViewではViewModelで @Published を付けたプロパティを受け取り、 Listを再描画できるようにしています。 また、ユーザ操作により、 @Published を付けたプロパティの変更を行い、 プロパティ変更の通知からViewModelの処理が自動的に始まるようにしています。 import SwiftUI // MARK : MemoListView struct MemoListView : View { @ObservedObject var viewModel = MemoViewModel() @State private var isMemoTextFieldPresented = false @State private var isDeleteAlertPresented = false @State private var isDeleteAllAlertPresented = false @State private var memoTextField = "" var body : some View { NavigationView { VStack { if (isMemoTextFieldPresented) { TextField( "メモを入力してください" , text : $memoTextField ) .textFieldStyle(DefaultTextFieldStyle()) .keyboardType(.asciiCapable) } List { ForEach(viewModel.memos.sorted { $0 .postedDate > $1 .postedDate }) { memo in HStack { MemoRowView(memo : memo ) Spacer() // Buttonにすると行全体にタップ判定がついてしまったので、Text.onTapGestureを代わりに使っている Text( "削除" ).onTapGesture { isDeleteAlertPresented.toggle() } .padding() .foregroundColor(.white) .background(Color.red) } .alert(isPresented : $isDeleteAlertPresented ) { Alert(title : Text ( "警告" ), message : Text ( "メモを削除します。\nよろしいですか?" ), primaryButton : .cancel(Text( "いいえ" )), secondaryButton : .destructive(Text( "はい" )) { viewModel.deleteMemo = memo } ) } } } } .navigationTitle( "メモの一覧" ) .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement : .navigationBarLeading) { Button( "全削除" ) { isDeleteAllAlertPresented.toggle() } .disabled(viewModel.memos.isEmpty) } ToolbarItem(placement : .navigationBarTrailing) { Button( "追加" ) { if (isMemoTextFieldPresented) { viewModel.memoTextField = memoTextField memoTextField = "" } isMemoTextFieldPresented.toggle() }.disabled(isMemoTextFieldPresented && memoTextField.isEmpty) } } .alert(isPresented : $isDeleteAllAlertPresented ) { Alert(title : Text ( "警告" ), message : Text ( "全てのメモを削除します。\nよろしいですか?" ), primaryButton : .cancel(Text( "いいえ" )), secondaryButton : .destructive(Text( "はい" )) { viewModel.isDeleteAllTapped = true } ) } } } } // MARK : MemoRowView struct MemoRowView : View { var memo : Memo var body : some View { VStack(alignment : .leading) { Text(formatDate(memo.postedDate)) .font(.caption) .fontWeight(.bold) Text(memo.text) .font(.body) } } func formatDate (_ date : Date ) -> String { let formatter = DateFormatter() formatter.dateStyle = .long formatter.timeStyle = .medium formatter.locale = Locale(identifier : "ja_JP" ) return formatter.string(from : date ) } } メイン部分では MemoListView を作成し、アプリ起動時にメモの一覧が表示されるようにしています。 import SwiftUI @main struct DemoApplicationApp : App { var body : some Scene { WindowGroup { MemoListView() } } } デモ 作成したアプリのデモ動画です。 概要に記載した操作が行えることを確認いただけるかと思います。 おわりに メモアプリ作成を通じて、SwiftUI、MVVMの知見が得られました。 元々、業務では MVC による iOS アプリ開発 を行っていましたが、 SwiftUIによる実装は画面の再描画を意識する必要が無くなり、処理が見通しやすくなったと感じました。 SwiftUIについては学習し始めたばかりなので、これからもキャッチアップを進めていこうと思います。 参考 Model View ViewModel - Wikipedia SwiftUIの概要 - Xcode - Apple Developer iOS13.3 @Publishedでの値更新からsinkが呼ばれなくなった?(ミス解決) - Qiita エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 rakus.hubspotpagebuilder.com ラク スDevelopers登録フォーム https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/ イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! ◆TECH PLAY techplay.jp ◆connpass rakus.connpass.com
こんにちは。 株式会社 ラク スで先行技術検証をしたり、ビジネス部門向けに技術情報を提供する取り組みを行っている「技術推進課」という部署に所属している鈴木( @moomooya )です。 ラク スの開発部ではこれまで社内で利用していなかった技術要素を自社の開発に適合するか検証し、ビジネス要求に対して迅速に応えられるようにそなえる 「 開 ( か ) 発の 未 ( み ) 来に 先 ( せん ) 手をうつプロジェクト(通称:かみせんプロジェクト)」 改め 「技術推進プロジェクト」 というプロジェクトがあります。 2020年度は通年で「無停止リリース」について取り組んでいるので、途中ではありますが紹介したいと思います。 今までの記事はかみせんカテゴリからどうぞ。 tech-blog.rakus.co.jp これまでの無停止リリースへの取り組み 前提 無停止の定義 想定している運用 検討したシステム構成 全体像 ポイント説明 DBプロキシにMariaDB MaxScaleを採用 WEBプロキシにApacheを採用 検証方法 負荷のかけ方 シナリオ説明 ミドルウェアのバージョンアップ アプリケーションとDB定義のアップデート 検証結果 残された懸念 立ち上げ時にどうするか まとめ これまでの無停止リリースへの取り組み これまで本ブログでも無停止リリースに関する記事を公開してきました。 tech-blog.rakus.co.jp tech-blog.rakus.co.jp 今回これらを踏まえた上で検証結果をまとめることができたので改めて公開していきたいと思います。 前提 無停止の定義 ここでいう『無停止』とはエンドユーザーから見てサービスが止まっていないことを指します。 冗長化 されたサーバー クラスタ のうち、再起動のために片系が停止するといったケースは許容します。 想定している運用 サービスカットオーバーから一度も停止せずに運用していく、といったことは目指しません。技術的にはできないこともなさそうですが、そこから発生する制約やコスト増などを考慮するとBtoBのビジネスを展開している弊社としては割に合いません。 目指している運用としてはほぼ毎回リリースのたびにサービス停止を伴っている現状から、年1回程度のサービス停止を伴うメンテナンスリリースと年数回~十数回のサービス停止が不要な通常リリースという運用に切り替えていければと考えています。 検討したシステム構成 全体像 システム構成自体は適用可能なサービスが多くなるよう、あまり特殊な構成にならないようにしました。DBプロキシの前段にクエリキューイングのレイヤを入れるなどすると、より対応可能な範囲が広がりそうではありましたが機能レイヤの追加コストと、必ず必要になるほどではないと考えて今回は見送っています。 アーキテクチャ 全体像 ポイント説明 DBプロキシに MariaDB MaxScaleを採用 DBのプライマリ、 セカンダリ の入れ替えなどを制御するためにDBプロキシとして MariaDB MaxScale を採用しました。 弊社のDBは PostgreSQL が多いのですが、 DBMS 自体がオンライン DDL への対応など無停止に向けた機能が充実していたため MariaDB + MariaDB MaxScaleの構成を採用しています。 既存サービスへの適用として PostgreSQL に適応させたDBプロキシについても後述します。 WEBプロキシに Apache を採用 昨今 nginx が優勢になり Apache が過去のものという印象も多いかと思いますが、今回改めて再評価したところ Apache 2.4を採用することにしました。 Apache 2.2の頃までは1プロセスシングルスレッドでプロセスがたくさん立ち上がるせいでメモリ大食いという印象でしたが、今では1プロセスマルチスレッドで動作させることができるのでメモリ消費は大幅に抑えられています。 そして決め手となったのは、 下流 ノード(APサーバー)の死活チェックを業務リク エス トと別でチェックすることができる、Active Health Checkが利用可能ということでした(nginxにもあるけど有償版が必要でライセンスが結構高い)。これを利用することによりAPサーバーの切り離しや復旧を、リク エス トを取りこぼすことなく行うことができるようになります。 検証方法 リリース作業をHTTPリク エス ト負荷をかけた状態で行って、HTTPリク エス ト負荷にエラーレスポンスが含まれないことを目標に検証しました。 負荷のかけ方 負荷は JMeter で、認証、参照、更新の処理を用意して、秒間10リク エス トの低負荷状態と秒間100リク エス トの高負荷状態のパターンで検証しました。 JMeter の使い方については弊社ブログのこちらの記事もご参照ください。 tech-blog.rakus.co.jp シナリオ説明 検証したリリースシナリオは2つ。 ミドルウェア のバージョンアップ APサーバーの ミドルウェア と DBMS のアップデートを行います。これらは必ず再起動が必要となります。OSのアップデートについてもこのパターンでカバーできると判断しています。 処理中のリク エス トを欠損させることなく、切り離しと復旧を行う必要があります。 操作は以下のように行いました。 DB 2号機 切り離し DBMS アップデート 復帰 DB 1号機の降格とDB 2号機の昇格 DB 1号機 切り離し DBMS アップデート 復帰 AP 2号機 切り離し ミドルウェア アップデート 復帰 AP 1号機 切り離し ミドルウェア アップデート 復帰 APサーバーについては1号機2号機の違いは特にないので入れ替えても問題ないかもしれません。 4のDB昇格/降格については MariaDB MaxScaleの switchover コマンドを用いました。 アプリケーションとDB定義のアップデート APサーバー上のアプリケーションバージョンのアップデートとDB上のテーブル定義変更を行います。また、アプリケーションは予期せぬリスクを考慮して本番環境でのホットリロードは行わず再起動を行います。テーブル定義変更は再起動を伴わずに実施します。 操作は以下のように行いました。 プライマリDBにテーブル定義変更の SQL ( DDL )を投入 ( セカンダリ DBには レプリケーション により自動反映) AP 2号機 切り離し ミドルウェア アップデート 復帰 AP 1号機 切り離し ミドルウェア アップデート 復帰 テーブル定義変更は以下の制約があります。 アプリケーション設計時の場合分け表 検証結果 おおむね期待通りに動かすことができたのですが…… 『DBのプライマリ、 セカンダリ の入れ替え時に1秒だけ書き込みエラー』 1つ目のシナリオにて、DBサーバーのプライマリ、 セカンダリ の昇降格処理中に書き込みクエリに対してのみ1秒弱のエラーが発生しました。 MaxScaleが持つコネクションエラー時にリトライするオプションなど(delayed_retryやtransaction_replayなど)を試してみたものの解消することはできませんでした。 試行錯誤の結果、DB 3台によるマルチマスタ構成にすることで解決できました。ただし、マルチマスタ構成の場合でもすべてを解決できるわけではなく、フェイルオーバー時には考慮が必要です。 残された懸念 マルチマスタ構成でも通常Write可能なノードは1つに限られていますが、フェイルオーバーが発生すると別のノードがWrite可能なノードに昇格します。このとき レプリケーション が完了してから昇格できれば良いのですが、 レプリケーション 完了前に レプリケーション 対象のレコードへの書き込みが発生していると不整合が起きてしまいます。 ノード1のレコードAを更新 ノード1が切り離され「ノード1の更新内容をノード2, 3に同期」する前にノード2が書き込み可能に昇格 ノード2のレコードAを更新 1と3のレコードAの状態に不整合が生まれノード1は自動復旧できない といった状況です。 このような状況を避けるために 共同編集機能の制御 システムからの更新を追記型にする など、特定の機能に対しては設計時に考慮が必要になります。 立ち上げ時にどうするか オンプレで構築する場合は2台か3台かのコストの違いが大きいので、アクセスが少ないうちは従来どおりの2台構成+DBプロキシ(今回はMaxScale)といったDB構成が良いと思いますが、 クラウド などの仮想基盤上に構築するのであればストレージ設計は要注意(合計サイズが1.5倍)ですが、最初から3台構成にするのが良いと感じました。 DB以外の構成は適宜サービスごとに改変して使うものの、先述の構成図の通りで問題ないと思います。 まとめ 完全な無停止運用(サービス停止を一切しない)を目指さず、停止を伴うメンテナンスリリースを何度かに1回計画することを前提とすれば、それほど特殊な構成にすることなく無停止リリースはできそうでした。 今回はオンライン DDL への対応が進んでいる MySQL 系統の MariaDB で検証を進めていましたが、以下の記事でも検証している通り、オンライン DDL に対応していない PostgreSQL でも気をつければ実用上は問題なさそうだったので、 PostgreSQL + HAProxy + Patroniなどの構成でも実現できそうな可能性が見えてきました(弊社既存サービスは PostgreSQL 採用率が高いので PostgreSQL が使えると嬉しい)。 tech-blog.rakus.co.jp 引続きHAProxyなどの活用も含めてユーザーの利便性向上を目指していきたいと思います。 エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 forms.gle イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! rakus.connpass.com
目次 目次 はじめに 結論 前提 TypeScript、Vue.jsで躓いた点 コンポーネント指向の入りやすさ SPA開発の入りやすさ フロントエンド、バックエンド、デザイナーが分かれている難しさ 開発を通じて感じたこと 半年間の反省 最後に 参考 はじめに こんにちは。新卒のmtaaaです。 プロダクト開発にフロントエンドエンジニアとして参加して半年が経とうとしているので、「新卒エンジニアとして」と、「フロントエンドエンジニア」としての2つの視点から感じたことをお話したいと思います。エンジニアを志す学生の方や、私と同じ新卒エンジニアの方に届けば幸いです。使用技術はTypeScript、Vue.jsがメインになります。これらの技術の突っ込んだ話はあまりしないので軽い気持ちで読んでみてください。 結論 後ろはかなり文章量があるので先に結論を言ってしまうと、「新卒エンジニアにとってフロントエンド開発はとても入りやすい」です。詳しくは後述しますが、私が参加したプロダクトが「Vue.jsを採用しているSPA」というのが大きな理由となっています。個人的にエンジニアとしての第一歩として、フロントエンドという選択肢はお勧めしたいです。 前提 最初に私がプロダクトに参加する前の学習期間にどんなことをしていたか軽くお話します。入社後2ヶ月程の Java を中心とした研修の後に配属、その後さらに2ヶ月かけて JavaScript 、TypeScript、Vue.js、Vuex、SCSSといった技術を学習、プロダクトに少しずつ参加していきました。 それまでに上記の技術に触れたことはほとんどなく、 JavaScript をかじった程度でした。これらに限らず自分のプログラミングのスキルは入門者レベルで、これからやっていけるかという不安もありました。 学習方法としては基本的に各公式ドキュメントを読み進め、わからない箇所を調べたり、先輩に教えていただいたりする形を繰り返していました。 また、プロダクトは昨年初リリースしたばかりの新しい商材です。 TypeScript、Vue.jsで躓いた点 まずはメインとなる技術2つを学ぶ上で躓いた点から話していきます。フロントエンドの難しいところとして他の領域に比べて技術の移り変わりが早いために書籍があまり出版されていなかったり、調べた情報が既に古いものだったりする点があります。 TypeScriptはざっくり言うと「 JavaScript に型付けができる言語」のことです。型の違いによるエラーにコーディング時点で気付くことができ、可読性も上がりやすいです。学習は「こういうものなのか」という感覚で比較的スムーズに行っており、わからない点も実際に書く時に調べていけばそれほど困ることはないという印象でした。どちらかと言えば JavaScript と共通の基本文法の知識不足で困ることの方が多いです。 Vue.jsはざっくり言うと「 JavaScript のデータとHTML要素を同期させるための フレームワーク 」です。同じ目的の フレームワーク では有名なものにReact.jsやAngular.jsがあります。中でもVue.jsは日本語の情報が多く公開されており学習しやすいメリットがあります。こちらは学習当初から難航していました。特に ライフサイクル 、 親子関係 、 コンポーネント指向 、 Vuex といった部分に躓き、これらは今でもわからなくなっては調べ直す、を繰り返しています。本記事では脇道に逸れるので詳しくは書きませんが、Vue.jsを勉強してみたいという方はこのあたりの単語を1度調べてみてもいいかもしれません。学習コストが低い、という話をよく見かけますし実際に長期間かけたわけではありませんでしたが、少なくとも私個人としては簡単に理解できたとは言い難いです。 コンポーネント 指向の入りやすさ 学習面では難しい面ばかり書きましたが、私はこの フレームワーク を取り入れた開発は新人が入りやすい形式だと考えています。大きな理由の1つとしてVue.jsの コンポーネント 指向があります。先ほども話に上がった コンポーネント 指向をざっくり言うと、「ページの コンポーネント (パーツ)を準備しておいてその組み合わせでWebページを作り上げること」です。そして私が参加した時点で既に コンポーネント の基盤は作られており、 コンポーネント を組み合わせるだけでひとまずそれらしい見た目のものが作れることは、シンプルに開発の楽しさやモチベーションに繋がりました。 SPA開発の入りやすさ Vue.jsを採用した開発に新卒エンジニアが入りやすかった大きな理由の2つ目としてSPA(Single Page Application)という設計を採用していたことがあります。SPAは「1つのページの必要な箇所のみ変更して画面の切り替えをする アーキテクチャ のこと」です。具体的には最初のページ遷移で全体の情報を取得し、あとはユーザーの操作によって変更する情報だけをサーバーとやり取りします。この形式におけるフロントエンドエンジニアの役割は、「バックエンドと情報をやり取りし、その結果をデザインに沿ってページに反映させること」です。極端なことを言ってしまうと、サーバーから返ってくる情報をデザイン通りに表示すれば最低限は作れます。つまり、小規模な改修のような案件に着手できるようになるまでがかなり早いです。 フロントエンド、バックエンド、デザイナーが分かれている難しさ 私が参加しているプロダクトではデザイナー、フロントエンド、バックエンドが比較的きっちり分かれており、最も重要かつ難しいのはコミュニケーションです。フロントエンドエンジニアは他領域のメンバー間の橋渡し的な役割もあるためコミュニケーションは大事になります。1つのものを作るにあたって領域ごとに違う人が担当する以上細かい認識のずれはどうしても発生してしまうため、手戻りが起こることもあります。これを少しでも減らすには仕様の共有はもちろんですが、なによりコミュニケーションを取りやすい文化が大切だと感じています。 また今はフロントエンドの開発で手一杯ですが、他領域についてもある程度わかっていた方が会話がスムーズに進みますし、相手の意図も汲みやすいので、デザインやバックエンド、インフラについても勉強したいと感じています。フロントエンドの学習に集中することで早くにプロダクトに参加していけるメリットはありますが、やはり最終的には吸収する必要があります。 開発を通じて感じたこと 学習期間中にあまり意識していませんでしたが実務で重要なものとして、 Google Chrome の デベロッパ ーツールを使いこなすことと、レビューされることを意識してコーディングすることがあります。 デベロッパ ーツールはフロントエンドの開発においては本当に必要不可欠です。プロダクトに入るまでは「ログを仕込んで確認する」程度の認識でしたが、サーバーからのレスポンスの確認、styleがどの要素に正しく当たっているか、いないかの確認に加えてなんと言っても 拡張機能 の Vue.js devtools が大きな助けになりました。この 拡張機能 では各ページでどんな コンポーネント を使っているか、どんな値が渡っているかを確認したり、直接書き換えて挙動を見たりしながら実装することができます。ページの特定の部分がどんな コンポーネント で構成されているのか直感的に調べることができるので、プロダクトのソース全体の理解もかなり楽になりました。 レビューされることを意識したコーディングも重要です。これはフロントエンドに限った話ではありませんが、他のメンバーや未来の自分が見て何をしているかわかりやすいコードは相当意識してもなかなか作れないです。 フレームワーク の思想を汲み取ることやプロダクト特有の文化、書き方に合わせることも覚えておきたいです。 また、私は学習面での不足がある段階からプロダクトのソースと挙動を見つつ、小さなバグ対応等からプロダクトに参加する所謂 OJT 形式にすぐに入りました。個人差があるとは思いますが、「習うより慣れろ」というのは大きく、自分自身開発業務の中で成長を感じられましたし、やはり動くものを作ってそれがリリースされるというのは嬉しいです。実務に必要な技術を都度学びながら進めるため回り道なく身に付きます。 半年間の反省 知識不足は当然多いですが、中でも以下は重きを置いて業務や学習に取り組むべきでした。 環境構築は特に勉強不足を感じています。エンジニアとして自分の作業環境をしっかり把握しておきたいですが、参加当初の環境構築以降ほとんど触ることもなかったため、問題が起きた際に原因や対処法に検討が中々つかないです。少なくとも Node.js の知識は深めておきたいです。 次に設計についてです。特にソースの設計は実務に入るまで意識がそもそもなく、コードレビューの中で指摘いただくことも多かったです。具体的にはどのメソッド、ファイルにどれだけの責任を持たせるかやクラスの枠組みといったところは、ただ動くだけではいずれバグの原因になる恐れがあります。ここは学業と仕事とで大きく違う点です。 今までなんとなくで捉えていた単語や技術がそのままになっていることも挙げられます。プロダクト特有の単語もあり、1度調べたり質問したりするだけでは数ヶ月後には忘れてしまっているものです。取ったメモは読み返すことがとても大事です。今もこの記事を書きながら再確認しています。 これらは来年度に解消していきたいですね。 最後に フロントエンドという分野が特に注目されたのはこの数年のことで、専門的な技術者が他の領域に比べて少ないです。そのため、長い目で見て新卒エンジニアにもチャンスのある領域だと思います。もし興味の出た方は是非チェックしてみてください。今回はVue.jsを使ったSPA開発で新卒エンジニアがプロダクトに入っていきやすかった点とその理由、逆に難しかった部分や、開発で得た教訓について話してみました。ここまで読んでいただきありがとうございました。 参考 JavaScript Primer - 迷わないための入門書 #jsprimer TypeScript Deep Dive 日本語版 - TypeScript Deep Dive 日本語版 はじめに — Vue.js Vuex とは何か? | Vuex Vue.js の Composition API における親子コンポーネント間のデータ受け渡し - Qiita フロントエンドのコンポーネント設計に立ち向かう - Qiita Node.jsとはなにか?なぜみんな使っているのか? - Qiita エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 rakus.hubspotpagebuilder.com ラク スDevelopers登録フォーム https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/ イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! ◆TECH PLAY techplay.jp ◆connpass rakus.connpass.com
目次 はじめに スキルマップ HTML CSS JavaScript SPA(Single Page Application) JavaScriptフレームワーク パッケージ管理 TypeScript Git Linux Web知識 テスト ブラウザ コーディングルール Docker(必須ではないが知っておくと良い) 情報収集に使えるWebサイト(おまけ) まとめ おわりに はじめに こんにちは、フロントエンドチームの北嶋です。 弊社でフロントエンドチームを立ち上げてから1年以上が経ち、少しずつメンバーも増えてきました。 最近は来年度の新卒メンバーに対する育成プランを考えているのですが、昨今のフロントエンド技術は移り変わりも激しく、学ぶべき項目が多岐に渡っているため、育成プランを考えるのも一苦労です。 ネットでフロントエンド関連の情報を調べると、今では参考にならない古めの記事も多いですし「これではフロントエンド初学者は何を勉強すれば良いのか分かりにくいだろうなぁ」という気持ちでいます。 そこで、 最近のフロントエンド初学者が身に付けておくべきスキルって具体的に何があるのだろう という疑問に対する答えをスキルマップという形で整理してみましたので、今回は皆さんに共有したいと思います。 特に、駆け出しフロントエンドエンジニアの方やフロントエンドの教育担当者の皆さんの力になれればと思いますので、是非参考にしてみて下さい! スキルマップ フロントエンド初学者(駆け出しフロントエンドエンジニア)が身に付けておくと良い技術のまとめです。 基本的には WEB DEVELOPER Roadmap 2021の Frontend Roadmap のトレンドに沿うように考えられていますが、個人的な意見も多いに反映されています。 目標レベル・学習項目・参考資料という3つの観点でまとめています。 目標レベル:現場に入っても問題なく業務をこなせるレベル感(脱駆け出しエンジニア程度のイメージ)を記載しています。 学習項目:理解しておくべき項目・用語を記載しています。 参考資料:目標レベルを満たすために参考となる資料(書籍・Webサイト等)を記載しています。 フロントエンドエンジニアとしてだけでなく、Webエンジニアとして身に付けておきたい技術も含まれています。 OSS -DBを入れるかが迷う所でしたが、実際に業務していて触れる機会が多くないのでひとまずドロップしています。将来的にはWebエンジニアとして理解しておいて欲しい項目ではあります。 1. HTML 目標レベル 基本的なタグを利用したHTMLの記述が行える 発展的なタグを把握している 学習項目 HTML5 参考資料 Webサイト HTML の学習: ガイドとチュートリアル - ウェブ開発を学ぶ | MDN HTML5リファレンス 2. CSS 目標レベル CSS で簡単なスタイル指定が行える SCSSで簡単なスタイル指定が行える 詳細度を理解している 学習項目 CSS3 擬似クラス Flexbox Sass 詳細度 参考資料 書籍 Web制作者のためのCSS設計の教科書 Web制作者のためのSassの教科書 Webサイト CSS - ウェブ開発を学ぶ | MDN 100年後も崩れないCSS勉強会 · 第1回「詳細度」 3. JavaScript 目標レベル ES2015以降の記法で JavaScript を記述できる API 通信を行うコードを記述できる 非同期処理を理解している 学習項目 ES2015以降の記法 Promise async/await API 通信 ESモジュール 参考資料 Webサイト JavaScript | MDN JavaScript Primer - 迷わないための入門書 #jsprimer JavaScript Promiseの本 Udemy 【JS】ガチで学びたい人のためのJavaScriptメカニズム | Udemy 4. SPA(Single Page Application) 目標レベル SPAの概念を理解している SSR ・SSGとの違いを理解している 学習項目 SPA SSR SSG 参考資料 Webサイト ようこそ!2020年、Webフロントエンドの世界へ SPA, SSR, SSGの違いについて図解でまとめてみた 5. JavaScript フレームワーク 目標レベル コンポーネント 指向の概念を理解している Vue.js(または React.js)の公式ガイドを基本的な部分を理解している 状態管理(Flux)の概念を理解している ルーティングの機能を理解している 上記の技術を用いて簡単な アプリ制作 が行える 学習項目(Vueのみ記載) Vue.js Vuex Vue Router 参考資料(Vueのみ記載) Webサイト Vue.js公式 書籍 基礎から学ぶVue.js Vue.js入門 基礎から実践アプリケーション開発まで Udemy 超Vue.js 2 完全パック (Vue Router, Vuex含む) | Udemy Vue.js + Firebaseで作るシングルページアプリケーション | Udemy 6. パッケージ管理 目標レベル nodeの概念を理解している npmの基本的なコマンドを利用できる パッケージ管理システムを利用してプロジェクト作成が行える パッケージ管理システムでパッケージの追加・削除が行える 学習項目 node npm package. json 参考資料 Webサイト 【初心者向け】NPMとpackage.jsonを概念的に理解する 7. TypeScript 目標レベル プリミティブ型の型指定が行える 簡単なオリジナルの型を作成し、型指定を行える 外部ライブラリの型を利用できる 学習項目 TypeScriptの概念理解 型の種類 型の指定 型の作成 参考資料 書籍 プログラミングTypeScript Webサイト 仕事ですぐに使えるTypeScript TypeScriptの型入門 - Qiita TypeScript Deep Dive Udemy 【世界で7万人が受講】Understanding TypeScript 日本語版 | Udemy 8. Git 目標レベル Gitの概念を理解できている 基本的な操作が一通り行える 複数人でのGit運用を経験している 学習項目 概念理解 基本的な操作方法 参考資料 Webサイト サル先生のGit入門〜バージョン管理を使いこなそう〜 9. Linux 目標レベル Linux の基本的なコマンドを利用できる 学習項目 基本的コマンド ファイル操作系 ssh 参考資料 Webサイト 【Linux入門】コマンドライン初心者のための基礎とサンプル | UX MILK オプション含めたsshコマンドの使い方 【Linuxコマンド集】 10. Web知識 目標レベル Web開発で必要な最低限の知識を持っている 学習項目 Web知識 HTTP REST セキュリティ知識 IT知識( 基本情報技術者 レベル) 参考資料 書籍 栢木先生の基本情報技術者教室 WEBを支える技術 体系的に学ぶ 安全なWebアプリケーションの作り方 第2版 Webサイト IT用語辞典 11. テスト 目標レベル テストの種類を把握している テストの種類ごとの目的を理解している 学習項目 テストの種類・目的 Jest 参考資料 Webサイト 【フロントエンド】コンポーネント指向(React, Vue)のテスト方針 - Qiita 12. ブラウザ 目標レベル ブラウザごとに動作に差異があることを理解している Chrome の開発者ツールの基本機能を利用できる 学習項目 ブラウザ差異 開発者ツールの使い方 Vue.js DevTools 参考資料 Webサイト Can I use... Support tables for HTML5, CSS3, etc Chrome DevTools の使い方 | murashun.jp Vue Devtools で快適なデバッグ - ROXX開発者ブログ 13. コーディングルール 目標レベル 良いコード、悪いコードの特徴を把握している 学習項目 特になし 参考資料 書籍 リーダブルコード ― より良いコードを書くためのシンプルで実践的なテクニック プリンシプル オブ プログラミング3年目までに身につけたい一生役立つ101の原理原 14. Docker(必須ではないが知っておくと良い) 目標レベル 仮想化の概念を理解している Dockerと VirtualBox の違いを理解している Dockerのメリットを説明できる 学習項目 仮想化 ホスト型 ハイパーバイザ型 コンテナ型 参考資料 Webサイト Dockerとは何か?初心者にもわかりやすく仕組みやメリットを解説 | キツネの惑星 15. 情報収集に使えるWebサイト(おまけ) JSer.info Qiita Zenn|エンジニアのための情報共有コミュニティ Github の リポジトリ の月間トレンド Trending JavaScript repositories on GitHub this month · GitHub Trending TypeScript repositories on GitHub this month · GitHub まとめ 機械的 にスキルを並べる形となってしまいましたが、参考になりましたでしょうか。 ざっくりまとめると、フロントエンド初学者の学習ロードマップとしては以下のような手順で学んでいくのが良さそうだと思いました。 HTML/ CSS / JavaScript を学ぶ JavaScript フレームワーク (Vue.jsまたはReact.js)を学ぶ パッケージ管理(node,npm)について学ぶ JavaScript のES2015以降の記法を学ぶ TypeScriptを学ぶ Web開発に必要な知識を身に付ける Web開発で必要なツールの使い方を学ぶ(Git, Linux ,ブラウザの開発者ツール等) フロントエンドのテストを理解する 一般的なコーディングルールを把握する おわりに 自分でまとめてみても「こんなに学習項目があるんだな・・・」と思ってしまったのですが、一つの指標というだけで、現場に入る前に全てを完璧に身に付けておかないといけない訳でもありません。 実際に自分自身も現場に入ってから少しずつ身に付けていった項目もありますし、業務で必要になったタイミングで学ぶ方が身に付くことも多かったりします。 ただ、このようなスキルマップを参考にすることでフロントエンド初学者の方が「この項目のスキルは自分に足りていないな」といった気付きが少しでもあれば嬉しく思います。 それでは、ここまで読んでいただきありがとうございました! エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 rakus.hubspotpagebuilder.com ラク スDevelopers登録フォーム https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/ イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! ◆TECH PLAY techplay.jp ◆connpass rakus.connpass.com
こんにちは ラク ス開発エンジニアのhyoshです。 今回は久しぶりのFirebaseシリーズを投稿いたします。 過去Firebaseの多様なソリューションからチャット(RealTime Database)、認証(Authentication)とご紹介してきましたが今回はプッシュ通知(Firebase Cloud Messaging)に携わる機会がありましたのでご紹介させていただきます。 過去の投稿はよろしければ以下もご覧ください。 tech-blog.rakus.co.jp tech-blog.rakus.co.jp Firebase Cloud Messagingについて できること 利用することによるメリット サンプル実装 Firebaseプロジェクトの作成 FCMをアプリに登録 クライアントの実装 メッセージを送ってみる フォアグラウントで通知を受け取れるようにする HTTPで通知を送ってみる まとめ Firebase Cloud Messagingについて できること Firebase Cloud Messaging(以降FCM)はFirebaseのソリューションの一つで、利用することでプッシュ通知を簡単に実現できます。 下図は 公式ガイド に記載されているFCM アーキテクチャ ですが、プッシュ通知は主にメッセージの作成(1)、転送(2,3)、受信(4)という流れで処理されます。 例えば特定タイミングでユーザーにキャンペーン情報を通知したいといったケースを考えた際、これらを全て自前で実装するとなるとかなり敷居が高くなる事は想像がつくでしょう。 一方でFCMを用いれば根幹となる1,2,3をバックエンドでFCMが実施してくれるため、開発者に必要なのはクライアントの軽微な実装のみ(※1)となり複雑な処理の中身を意識する必要がありません。 ※1…実際の開発現場では後述のように1に関しても個別実装するケースが多いかと思いますが、それでもFCMを利用することで自前で行うよりずっと簡易に行えます。 利用することによるメリット FCMを利用してプッシュ通知を実現するメリットですが、私は以下の点を感じています。 バックエンド処理の実装が不要 OSの違いを吸収してくれる( クロスプラットフォーム 対応) 導入・運用コストが低く経済的 1は先にお話した通りで、プッシュ通知を実現するにあたっての煩雑な処理は全てFCMが担当してくれるので、開発者はクライアント実装のみに注力すればよくなります。 2はFCM公式でも強く打ち出している特徴で、 iOS であっても Android であってもメッセージの作成、転送までは同じ処理を共有できるいわゆる 「 クロスプラットフォーム 」 な開発が行えます。 各端末のアプリで実施される受信処理はそれぞれのOSで実装は必要ですが、それでも各OS毎に行う作業はグッと減らせます。 そして3の特徴ですが、これだけ充実したサポートを誇っていながらFCMを利用することでかかるコストは下記の通り 無料 です。 firebase.google.com よくありがちな特 定量 から課金という事もなく、 何通送っても0円です (※2)。 後ほどサンプルで紹介もしますが、導入も非常に容易なのでありがたい限りですね。 これらのことから現在プッシュ通知実装に関し、まずFCMの名前が出てくるのも納得の理由かと感じていただけるかと思います。 ※2…2021年3月時点の情報であり、今後変更となる可能性はあります。 サンプル実装 FCMがバックエンドを担当してくれると言っても実態がイメージしづらいと思うので、ここからは実際に Android で簡単な通知を受け取れるサンプルアプリを作っていきたいと思います。 ※ 公式ガイド をベースとしているため、合わせて参照ください。 Firebaseプロジェクトの作成 まずは表示されているガイドに沿ってFirebaseにてプロジェクトを作成していきます。 1.一意となる任意のプロジェクト名称を入力します。 2. Google アナリティクスを利用することでより効果的にFirebaseのソリューションを使うことができますが、ここではひとまず利用しないでプロジェクトを作成します。 これでプロジェクト作成は完了となり、コンソール画面が表示されます。 FCMをアプリに登録 続いて作成したプロジェクトをアプリに登録し、互いをひもづけます。 1.コンソールの左メニューよりCloud Messagingをクリックし、 Android アイコンを押して設定画面に進みます。 設定は親切なガイドが表示されているので、それに従って進めていきます。 クライアントの実装 ここまででFCM側の作業は完了したので、次に通知を受信するためのクライアント側の実装を行っていきます。 1.FCMでは トーク ンという情報を用いて端末を一意に識別します。 トーク ンの実態は端末とアプリの組み合わせで一意となる文字列情報となり、これをFCMで認知できている事で狙った端末に通知が送れるという仕組みになっています。 もちろん重要情報なので取扱いには重々注意が必要となります。 トーク ンを発行するサービスがFCMから提供されているので次のように実装していきます。 // build.gradle(:app)にFCMライブラリを追加 dependencies { implementation platform( 'com.google.firebase:firebase-bom:26.7.0' ) implementation 'com.google.firebase:firebase-messaging-ktx' // この一行を追加 } package com.example.notifyapplication import android.util.Log import com.google.firebase.messaging.FirebaseMessagingService class MyMessageService : FirebaseMessagingService() { override fun onNewToken(token: String ) { Log.d( "MyMessageService" , "Refreshed token: $token " ) } } 新規にFirebaseMessagingServiceを継承したクラスを作成します。 onNewTokenはFirebaseMessagingServiceに用意されているメソッドでアプリが トーク ンを発行するためにアプリ起動時に呼び出されます。 ただし毎回呼び出される訳ではなくまだ一度も トーク ンを発行していない、何らかの要因で トーク ンを更新する必要がある(※3)といったケースで限定的に呼び出されます。 ここまでできたら一度アプリを動かしてみましょう。 ログに トーク ンが出力されているかと思います。 表示されていない場合は既に発行された後の可能性があります。 そのような場合に備えてガイドでは現在の トーク ンを取得する方法も開示されているので、以下のようにMainActivityに追加してみましょう。 override fun onCreate(savedInstanceState: Bundle?) { super .onCreate(savedInstanceState) setContentView(R.layout.activity_main) setSupportActionBar(findViewById(R.id.toolbar)) // ここから追加 FirebaseMessaging.getInstance().token.addOnCompleteListener(OnCompleteListener { task -> if ( ! task.isSuccessful) { Log.w( "MainActivity" , "Fetching FCM registration token failed" , task.exception) return @OnCompleteListener } // 現行のトークンを取得 val token = task.result // ログに出力 Log.d( "MainActivity" , "Current token: $token " ) }) } これでアプリ起動時に確実に トーク ンがログに出力されるようになったかと思います。 ※3…公式ではアプリを再インストールした、アプリが新しい端末で復元された、アプリのデータを消去したなどが挙げられています。 メッセージを送ってみる 実はFCMでは GUI ベースで通知を送信できる機能が用意されているので、ここまで行えばもう自分のアプリに通知を送る事が可能です。 1.FCMコンソールよりSend your first messageのボタンを押して先に進みます。 2.通知情報を入力します。ひとまず最低限必要なタイトルと本文を入力し「テストメッセージを送信」を押します。 3.ここで トーク ンの入力を求められるので、先ほどログに出した トーク ンを入力しテストボタンを押します。 トーク ンが間違っていなければアプリの動いている端末に先ほど登録した通知が届いたかと思います。 4.テスト通知でなく正式な通知として登録するために後続を進めます。 ターゲットではひとまずアプリとする事でアプリ利用者全員に通知を送信できます。スケジュール設定では送信時間や定期的に送るかを指定できます。 確認の後、公開を押します。 5.これで通知が登録できたので、スケジュール設定で指定した時間に通知が届きます。 別の通知を登録したい場合は「新しい通知」より再度登録します。 ちなみにレポートタブからは送った通知の 開封 率といったデータも見る事ができます。 このように最もシンプルなやり方でも、複数ユーザーに対して時間指定して一斉通知を送る程度であれば簡単に行える事が理解いただけたかと思います。 フォアグラウントで通知を受け取れるようにする ここまでで通知を受け取ることはできましたが、実は今の実装だけではアプリとしてはまだ不完全です。 というのも今の状態では通知を受け取る事ができるのはアプリがバックグラウンドにある状態のみで、フォアグラウンド(操作している時)の状態では通知を受け取る事ができません。 通知の目的を考えるとフォアグラウンドで受け取る必要性は低いとも言えますが、重要な通知で常にユーザーに認識してほしい局面もあるかと思いますので、少し改造してフォアグラウンドでも通知を受け取れるようにしてみましょう。 1.先ほど作成したMyMessageServiceにonNewToken同様、継承元のFirebaseMessagingServiceで用意されている onMessageReceived を実装します。 以下ではonMessageReceivedで通知を受け取り送られてきたタイトルとメッセージの通知を生成し、画面に表示します。 override fun onMessageReceived(remoteMessage: RemoteMessage) { remoteMessage.notification?.let { it -> sendNotification(it) } } private fun sendNotification(message: RemoteMessage.Notification) { val intent = Intent( this , MainActivity :: class .java) intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) val pendingIntent = PendingIntent.getActivity( this , 0 /* Request code */ , intent, PendingIntent.FLAG_ONE_SHOT) val channelId = getString(R.string.default_notification_channel_id) val defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION) val notificationBuilder = NotificationCompat.Builder( this , channelId) .setSmallIcon(R.mipmap.ic_launcher) .setContentTitle(message.title) .setContentText(message.body) .setAutoCancel( true ) .setSound(defaultSoundUri) .setContentIntent(pendingIntent) val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager // Android OS 8以降はチャネル指定が必須 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val channel = NotificationChannel(channelId, "チャネル説明" , NotificationManager.IMPORTANCE_DEFAULT) notificationManager.createNotificationChannel(channel) } notificationManager.notify( 0 /* ID of notification */ , notificationBuilder.build()) } -- strings.xml <resources> <string name= "app_name" >NotifyApplication< / string> //以下一行を追加 <string name= "default_notification_channel_id" >fcm_default_channel< / string> < / resources> 最後にAndroidManifest. xml にサービスを登録します。 <service android:name= ".MyMessageService" android:exported= "false" > <intent - filter> <action android:name= "com.google.firebase.MESSAGING_EVENT" / > < / intent - filter> < / service> 確認する為にコンソールよりテスト送信してみると、フォアグラウンドでも通知を受け取れるようになった事が分かるかと思います。 (分かりづらいですが左上の●が届いた通知です) このようにonMessageReceivedまで実装しておく事でバックグラウンドではFCMよりアプリを通さず直接通知を表示、フォアグラウンドではアプリ経由で通知を表示といったアプリ状態によらず常に表示が可能になります。 HTTPで通知を送ってみる 最後にオプションとしてHTTPリク エス トとして通知を送ってみましょう。 1.Firebaseコンソール−プロジェクトの設定−CloudMessagingからサーバーキーを確認します。 2.FCMの通知送信用として用意されている API は「 https://fcm.googleapis.com/fcm/send 」となるので、これに対し必要なパラメータを付与しPOSTリク エス トを投げます。 HeadersのAuthorizationのkeyには先ほど確認したサーバーキー、Content-Typeはapplication/ json を指定します。 そしてBodyにはtoに対象端末の トーク ン、notificationにタイトルと本文を指定します。 ツールなどで送信してみるとこのやり方でも端末に通知が届く事が分かるかと思います。 実際のシステムではタイミングが決まっておらず何らかの処理をトリガーに通知を送りたいといったシーンも多いかと思われますので、そのような時に活用できるでしょう。 なお今回は簡易なHTTPを用いましたが 公式ガイド で推奨されている後継の プロトコル が存在するため、可能であればそちらを使う事が望ましいです。 まとめ 今回はFirebaseを用いたプッシュ通知実装に関してご紹介させていただきましたがいかがだったでしょうか。 FCMを用いることで思ったよりも簡単に実現できることを理解いただけたのではないかと思います。 iOS との連携に関してはご紹介できませんでしたが、クライアント側の実装としてはほぼ同じとなりOS間の違いを意識することなく開発する事が可能なので、興味が湧きましたらぜひ試していただけますと幸いです。 最後までお読みいただきありがとうございました。 エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 rakus.hubspotpagebuilder.com ラク スDevelopers登録フォーム https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/ イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! ◆TECH PLAY techplay.jp ◆connpass rakus.connpass.com
はじめに こんにちは、takaramです。 今回はPHPDocについて、特に 型 の重要性と、応用的で便利な書き方をご紹介したいと思います! はじめに PHPの型を使いこなしたい PHPDocとは? PHPDocの仕様 多くのツールで有効なPHPDocの書き方 型の書き方 配列型 false型 @property ローカル変数の型 まとめ おまけ:配配メール開発チームの取り組み PHP の型を使いこなしたい PHP でも近年、静的型付け言語と同じようにメソッドの引数や戻り値、クラスのプロパティなどの型宣言を書くことができるようになってきています。型宣言はPHP5.0から部分的にサポートされていましたが、本格的に使えるようになったのは2015年リリースの7.0あたりからです。 PHP 7.0: スカラー型宣言、戻り値の型宣言 PHP 7.1: nullableな型 PHP 7.4: プロパティの型宣言 PHP 8.0: union型、mixed型の追加 多くの静的型付け言語にある ジェネリクス 機能が PHP にはないなど、まだ十分とは言えない部分もありますが、このように動的型付け言語に型宣言を追加するという試みはTypeScriptや Python 、 Ruby などでも行われていて、時代の流れと言えるかもしれません。 しかしそもそも、なぜ PHP にわざわざ型を書く必要があるのでしょう?メリットとしては、以下のようなものがあります。 実行時に型チェックされる(予期しない値が渡されることを防げる) IDE で補完が効きやすくなる 静的解析がより正確に実行できる このうち実行時型チェックは、新規コードでは安心・便利な一方、型宣言のない時代のコードを引き継ぐ現場では、動いている既存コードに下手に型宣言を追加するとかえって動かなくなるというリスクも考えられます。 そのため、既存コードへの型宣言追加には二の足を踏んでいる現場も多いのではないでしょうか。 しかし、そのために IDE のコード補完などのメリットを捨ててしまうのはもったいないです。 そこで、PHPDocによる型情報の補足が重要となってきます。PHPDocは基本的にはプログラムの動作に影響しないため、型宣言にくらべると既存コードにも追加しやすいと思います。 以下ではPHPDocとその書き方について、私が担当しているプロダクト「 配配メール 」の既存コードのPHPDoc整備を進めた経験も交えて紹介していこうと思います。 PHPDocとは? そもそもPHPDocとは何か、について説明しておきましょう。そんなの知ってるよ!という方は 次のセクション まで飛ばしてください。 PHPDocとは、 PHP で書かれた下記のものについてのドキュメントを、 ソースコード のコメント内に記述するための書式、およびその書式に従って書かれたコメントを指します。 関数 定数 クラス メソッド プロパティ etc. PHPDocは /** ... */ の Docコメント という形式のコメント内に記載します。 一部のツールやライブラリはDocコメントに特定の内容を書くことで動作を変えるものもあります *1 が、基本的には単なるコメントに過ぎないためプログラムの動作には影響しないと思っていいでしょう。 例として、 psr/container の ソースコード を見てみてください。インターフェースとメソッドの説明がPHPDocで書かれていて、メソッドの引数や戻り値の説明は @param , @return のような文字列( タグ といいます)に続いて書かれているのが分かると思います。 ここでは書式の詳細な説明はしませんが、雰囲気だけでも掴んでください。 PHPDocの書式に則ってコメントを書くことは、通常のコメントと同じくチームの他の開発者や、未来の自分に向けた説明としてももちろん有用です。ただそれだけではなく、PHPDocを活用したツールが多く存在していて、それらを活用できるメリットがあります。具体的には、以下のようなツールがあります。 phpDocumentor PHPDocを元にHTML形式のドキュメントを生成してくれるツール。ライブラリのリファレンスなどで使われる。 PhpStorm PHPDocから変数等の型を検出し、コード解析、自動補完に利用する。 PHPStan , Phan , Psalm 不具合の可能性があるコードを検知してくれる静的解析ツール。引数の型の不一致や存在しないメソッド呼び出しなどを検出する。 PHPDocとこれらのツールを使いこなすことができれば、 PHP での開発を効率的に進められます。 PHPDocの仕様 このように様々なツールで利用されているPHPDocですが、実は明確な仕様というものは決まっていないのです。 言語仕様以外の PHP の標準規格を確立するPSRというプロジェクトの一部として、PHPDocの仕様 "PSR-5" の草案が作成されてはいますが、議論が中々まとまらず、2021年現在未だに標準化されていません。 そのため各ツールで有効とされる書式が微妙に異なり、「あのツールで有効な書き方がこのツールではエラーになる」といったことが発生しうる状態です。実際にPHPDocを書く際には、自分が利用するツールの仕様書や実際の動作を確認して使うのがよいでしょう。 とはいえ、どのツールも大枠の書式は一致しています。基本的な書き方をご存じない方は、 phpDocumentorのドキュメント を読んでみてください。 多くのツールで有効なPHPDocの書き方 ここからは多くのツールで有効かつ便利な、PHPDocの応用的な書き方を紹介していきます。 型の書き方 PHPDocの @param タグや @return タグ、 @var タグ等で何気なく書いている型も、より適切な書き方があるかもしれません。 配列型 PHPDocで配列型といえば普通は array ですが、それ以外にも書き方があるのをご存知でしょうか? 数値キーの配列: int[] , Clazz[] 文字列キーの配列: array<string, int> , array<string, Clazz> キーごとに値の型が異なる配列: array{id: int, name: string} 例として、名前・年齢・住所を持つHumanクラスのサンプルを下に示しています。ここに書かれているPHPDocでは、 プロパティ $propertyKeys は文字列の配列 プロパティ $property はキーが文字列、値が文字列または整数の 連想配列 コンストラクタ の引数はキー name の値が文字列、キー age の値が整数、キー address (省略可能)の値が文字列の 連想配列 であることを表しています。 <?php class Human { /** @var string[] */ private $ propertyKeys = [ 'name' , 'age' , 'address' ] ; /** @var array<string, string|int> */ private $ property = [] ; /** * Human constructor. * * @param array{ * name: string, * age: int, * address?: string * } $property */ public function __construct ( $ property ) { foreach ( $ this -> propertyKeys as $ propertyKey ) { $ this -> property [ $ propertyKey ] = $ property [ $ propertyKey ] ; } } } こうした書き方をすることで、単にarrayと書くのに対していくつかメリットがあります。 new Human(['job' => 'engineer']) のような不正な引数を静的解析で検知できる foreach ($array as $key => $value) の $key , $value の型がわかるので、 IDE で補完が効いたり、静的解析の精度が上がったりする ドキュメントとしてもわかりやすくなる int[] 形式以外の2つは残念ながらPhpStormでは未対応なのですが、静的解析ツールを利用しているならこの書き方を採用してみてください。 false型 「true か false」を表す型は bool または boolean ですが、単に true , false と書くこともできるのはご存知でしょうか? PHP の組み込み関数では、例えば file_get_contents のように、失敗したらfalseを返すような関数があります。 自作関数でもそれにならって「成功時は処理結果を、失敗時はfalseを返す」ような関数にしている場合もあると思います。そのような関数のPHPDocは、 @return string|bool よりも @return string|false と書くべきです。 もし bool と書いてしまった場合、以下のようなコードを書くと静的解析ツールに怒られます。 <?php /** * @param string $fileName * * @return string|bool */ function getContents ( $ fileName ) { return file_get_contents ( $ fileName ) ; } $ contents = getContents ( '/tmp/foo.txt' ) ; if ( $ contents === false ) { exit ( 1 ) ; } // ファイルの内容を大文字にして出力 echo mb_strtoupper ( $ contents ) ; $contents === false のときの処理もきちんとしていて、実際の動作上エラーは出ないコードですが、 getContents() の戻り値を string|bool としてしまっているため、静的解析ツールは「trueの可能性がある値を mb_strtoupper() の引数に渡している」と判断してしまうのです。 上記のコードのPHPDocを @return string|false に書き換えることで、静的解析のエラーは解消されます。 PHPStanの例。string|boolだと エラーが出る が、string|falseにすると 解消する 。 @property @property は主にクラスのPHPDocに書くタグで、 __get() , __set() を使った動的なプロパティを記述することができます。 ……という説明はググれば他にいくらでも出てくるので、今回は少し変わった使い方を紹介したいと思います。 @property を使うことで、親クラスから継承したプロパティの型を、より厳密にすることができます。 例)親クラスでDateTimeInterfaceのプロパティを、子クラスではDateTimeImmutableにする <?php class Base { /** @var DateTimeInterface */ protected $ datetime ; public function __construct () { $ this -> datetime = new DateTime () ; } } /** * @property DateTimeImmutable $datetime */ class Child extends Base { public function __construct () { $ this -> datetime = new DateTimeImmutable () ; } /** * 明日の日付を返す * * @return DateTimeImmutable */ public function getTomorrow () { return $ this -> datetime -> modify ( 'tomorrow' ) ; } } 上記の例では、Childクラスに @property DateTimeImmutable $datetime をつけることによって、 getTomorrow() メソッドのところで静的解析に怒られないようにしています( DateTimeInterface::modify は存在しないため)。 私のチームで実際に行った例としては、Webフレームワークの各クラスを継承して独自拡張しているクラスのプロパティでこれを利用しました。以下にサンプルコードを示します。 <?php namespace Framework { class Controller { /** @var Session */ protected $ session ; } class Session { } } namespace App { /** * @property Session $session */ class BaseController extends \Framework\Controller { } class Session extends \Framework\Session { public function originalMethod () : void { echo 'I am \App\Session!' ; } } class SampleController extends BaseController { public function index () : void { $ this -> session -> originalMethod () ; } } } このように、 フレームワーク の \Framework\Controller , \Framework\Session クラスを拡張し、 \App\BaseController , \App\Session クラスを作成しています。一番下で SampleController から $this->session->originalMethod() を呼び出していますが、 @property を書かない場合だと $this->session は フレームワーク 側の Session クラスだと認識されてしまい、コード補完、静的解析とも上手くいきません。 注意点として、この方法には「 @property で書いたプロパティがpublicと認識されてしまう」という欠点があります。しかしながら、上記のようなコントローラークラス等であれば、 プログラマ が自分でnewして インスタンス を扱うことはほぼ無いためあまり問題にはならないかと思います。 ローカル変数の型 PHPDocを使えば、ローカル変数にも型をつけることができます。 <?php foreach ( $ iterable as $ item ) { /** @var DateTime $item */ echo $ item -> format ( 'Y/m/d' ) . PHP_EOL; } 上記のように、foreachのループ変数の型を示す以外に、「型定義上はmixedだが人間から見ると型が明らか」という場合に便利です。 例えば下の例のように、DBのカラムから値を取得する場合「 id は integer 型のカラムだから戻り値も int だ」というのは プログラマ はわかっていても、 IDE や静的解析ツールはそこまで汲み取ってはくれません。 そのような場合にPHPDocで変数の型を明示すると便利です。 例)DBから値を取得する場合、テーブル定義を知っていれば型もわかる <?php function getUserId ( string $ email ) : int { $ pdo = new PDO ( 'pgsql:host=localhost;dbname=postgres' , 'postgres' ) ; $ statement = $ pdo -> query ( 'SELECT id FROM users WHERE email = :email' ) ; $ statement -> execute ([ ':email' => $ email ]) ; /** @var int $id */ $ id = $ statement -> fetchColumn () ; return $ id ; } まとめ 今回は「型」に絞ってPHPDocの応用的な書き方をご紹介しました。このような方法で適切に型を記述することで、 IDE の自動補完 → 開発体験の向上 静的解析の精度向上 → バグを減らせる といったメリットを享受することができます。これからPHPDocを書く際に、この記事の内容が少しでもお役に立てれば幸いです。 おまけ:配配メール開発チームの取り組み 私が所属する 配配メール 開発チームでは現在、古くからあるコードのPHPDocの整備を進めており、今回紹介した内容も多くはその中で実際に利用したものです。 以前はPHPDocが書かれていなかったり、誤った型が書かれている箇所が多くあるなど問題となっていましたが、そこから整備を進め、現在は多くの箇所で利用している共通関数・クラスの対応を完了しました。その結果、 IDE の補完が効くようになったり、誤った不要な警告が出なくなったりといった効果が出始めています *2 。 今後、さらに範囲を広げてPHPDoc整備を進めながら、現在はできていない静的解析の導入も目指していきたいと考えています。 エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 rakus.hubspotpagebuilder.com ラク スDevelopers登録フォーム https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/ イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! ◆TECH PLAY techplay.jp ◆connpass rakus.connpass.com *1 : PHPUnit , PHP -DI など。この用途では、今後はDocコメントではなく、PHP8で導入された アトリビュート 機能に置き換わっていくかも。 *2 : なお、新規コードについてはコーディング規約とコードレビューでPHPDocが正しく書かれるようにしています。整備の対象となるコードを新たに生み出さないことも重要ですね。
はじめに こんにちは、 @rs_tukki です。 最近外に出られない日々が続いているので、自宅で完結できる趣味が増えた気がします。 さて、今は Android のWebViewがアツいみたいなので、それに関連してるようでしてない気がする話を少し。 はじめに 実装したかった仕様 実際の仕様 原因 どう修正したか まとめ 参考 実装したかった仕様 今回私が開発していた Android アプリは全体的にWebViewに依存しており、 まず API で認証処理を行ってから、取得したアクセス トーク ンを使ってWebサイトにログインするという流れを取っています。 アプリ( API )「君のサイトにログインしたいんだけど。まずIDとPASS投げるから認証して」 サーバ「オッケー、認証は問題ないね。君用のアクセス トーク ンあげるからこれくっつけてアクセスしてね」 API での通信を行う場合は、OkHttp3のCookiejarクラスを実装した独自の Cookie ストアを使用し、 レスポンスで受け取った Cookie を明示的にCookieManagerに保存する処理を行っていました。 class SharedCookieStore() : CookieJar { // CookieManagerはアプリ全体で共通のインスタンスとして定義する private val cookieManager = CookieManager.getInstance() // レスポンスの内容からCookieを永続化するメソッド override fun saveFromResponse(url: HttpUrl, cookies: MutableList <Cookie>) { val urlString = url.toString() cookies.map(Cookie :: toString) .forEach { cookieManager.setCookie(urlString, it) } sync() } private fun sync() { cookieManager.flush() } } val apiModule = module { single { SharedCookieStore() } bind CookieJar :: class // SharedCookieStoreクラスをインスタンス化 factory { buildHttpClient( get ()) } // 上記のSharedCookieStoreクラスを注入 private fun buildHttpClient(cookieJar: CookieJar): OkHttpClient { val dispatcher = Dispatcher() val builder = OkHttpClient.Builder() .dispatcher(dispatcher) .cookieJar(cookieJar) // SharedCookieStoreをOkHttpClientのcookieJarに指定(以下省略) これでCookieManagerに必要な Cookie が保存されましたので、あとはこれを使ってWebViewを開いてあげましょう。 アプリ(WebView)「今から君のサイトにアクセスするね。アクセス トーク ンはこれで っ Cookie 」 サーバ「はいはい、 トーク ン問題ないからアクセスしていいよー」 後は、このアクセス トーク ンの有効期限が切れるまでサイトへのアクセスが可能という仕様にするつもりでした。 実際の仕様 さて、このアクセスしたサイトですが、実は アクセス トーク ンの有効期限とKeyを保持したまま、定期的に Value だけ更新される という仕様が組み込まれています。 サーバ「ごめん、今君が使ってる Cookie 、値が変わったんだわ。次のアクセスからこっち使ってね」 アプリ(WebView)「えっ」 そして、この後再度アクセスしようとすると アプリ(WebView)「次このページにアクセスするね…えーっとアクセス トーク ンはこれかな? っ Cookie 」 サーバ「んー、その Cookie 知らない値だわ。申し訳ないけどログインからやり直して」 アプリ(WebView)「えっ」 というわけで、なぜかWebViewは新しい Cookie を使うことなく認証に失敗してしまいました。 原因 (サーバ側で既にある Cookie の Value を更新しただけなら、CookieManagerに自動的に反映されてそうなもんだけどな…) と思いながらWebViewの実装やらCookieManagerの仕様やらを見返していたのですが、ここに勘違いがありました。 spitfire-tree.hatenadiary.org 曰く、 Android アプリケーションで cookie を管理する際、デフォルトの動作はアプリとは非同期で揮発性のメモリに展開された値を最大数分(?)程度の遅れで不揮発な領域に書き込みに行くようです。 (中略) 実際には cookie に {user_id: 1} のような値を書き込むわけですが、それが不揮発性メモリと同期される前にアプリケーションを即時に終了させると、次回実行時に( cookie の expire が実行時より後に設定されていようと)不揮発性メモリに残った値を参照してアプリの状態を復元するため、今行ったログイン処理がなかったことにされてしまう可能性が存在する、ということです。 つまり、今貰った新しい Value を裏で永続化する前に次のアクセスを行ってしまったことで、 キャッシュとして残っていた古い Value を使ってしまい不整合が発生した…ということのようです。   どう修正したか Cookie を永続化できていなかったのが原因なわけですから、通信ごとに永続化してやればいいわけです。 WebViewのFragmentに内部クラスとしてWebViewClientを継承したクラスを作成し、 onPageFinishedのメソッドをオーバーライドして現在のアクセス トーク ンを明示的に永続化します。 class WebViewFragment() { private inner class MyWebViewClient( val isOtherWindow: Boolean ) : WebViewClient() { override fun onPageFinished(view: WebView?, url: String ?) { // Webviewでの遷移時に現在のアクセストークンを永続化する CookieManager.getInstance().flush() super .onPageFinished(view, url) } } 最初に書いた通り、 API 通信の時にはきっちり永続化しているんですけどね… WebViewでも同じ対応が必要という認識がすっぽり抜け落ちていました。反省。 class SharedCookieStore() : CookieJar { // CookieManagerはアプリ全体で共通のインスタンスとして定義する private val cookieManager = CookieManager.getInstance() // レスポンスの内容からCookieを永続化するメソッド override fun saveFromResponse(url: HttpUrl, cookies: MutableList <Cookie>) { val urlString = url.toString() cookies.map(Cookie :: toString) .forEach { cookieManager.setCookie(urlString, it) } sync() } // Cookieを永続化する private fun sync() { cookieManager.flush() } } まとめ さて、今回は Android における Cookie の仕様についてお話ししました。 CookieManager.flush() 、忘れるなよ!絶対だぞ! 参考 CookieJar - OkHttp - OkHttp AndroidのWebViewにクッキーを設定したい Android の cookie 管理と CookieManager と flush() - happy lie, happy life エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 rakus.hubspotpagebuilder.com ラク スDevelopers登録フォーム https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/ イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! ◆TECH PLAY techplay.jp ◆connpass rakus.connpass.com
はじめに こんにちは。インフラエンジニアの gumamon です! 〇aaS、コンテナ、 オーケストレーション 。インフラ界隈でも便利なツールが普及しつつある昨今ですが、根強く使われ続けているのが シェルスクリプト です。 シェルスクリプト の用途は 主に「シェルコマンドの自動実行(とエラーハンドリング等)」であり、インフラエンジニアとしては業務効率化のツールとして重宝する一方、事故(バグ)が発生してしまうと重大な障害につながる危険なものでもあります。 今回は、これを(初心者でも)比較的安全に書けるようになる。 ひいては シェルスクリプト の品質管理が楽になる「2つの VSCode のアドオン」を紹介します。 VSCode = Visual Studio Code 、 Microsoft 製の ソースコード エディタです。 インフラエンジニアの場合「 vim で書くし」という方も多いと思うのですが、慣れてしまうと超便利です。 インテリジェントなコード補完や校正機能は、コードレビューの 工数 も削減出来てGood。 はじめに アドオンの紹介 さっそく使ってみる( その1: ShellCheck ) さっそく使ってみる( その2: ShellFormat ) まとめ チーム/組織 でlintを導入するメリット アドオンの紹介 順次2つ、ご紹介します。 インストール方法/使い方についてはリンク先に動画がありますのでそちらをご参照ください。 ShellCheck (検証ver = v0.14.0) (エディタで表示された)シェルの記述に対し、問題が無いかをチェックします。 問題のある記述について、なぜダメなのか、どう直せば良いのかを事細かに説明してくれます。 チェック内容はリアルタイムに更新されるので、問題の有無を確認しながらコーディングが出来るのも良いポイントです。 ShellFormat (検証ver = v7.0.1) インデントや改行など、構文のフォーマットを修正してくれます 。 例:if構文の;doをどこで改行するか。 これにより ソースコード のフォーマットが一意になり、複数名で書いている場合は非常に読みやすくなります。 リンク先の動画についての補足 VSCode を初めて使われる方は、コマンドパレット(動画でコマンドを実行している部分)の呼出しで 少し迷われるかもしれません。コマンドパレットはショートカット(Ctrl+Shift+P)を使うか、メニューから[表示]→[コマンドパレット]で呼び出せます) さっそく使ってみる( その1: ShellCheck ) まず、ShellCheck をOFFにした状態がこちら。 ShellCheck=OFF ファイルの一覧を読み込み、日付付きのファイル名としてコピーするだけの スクリプト です。 何か気になるところはありますか? 気になるところを探しつつ次へ。 ↓ ↓ ↓ ↓ ↓ ShellCheck=ON わんさか指摘点が出てきました。 波線の色は深刻度を表しています(黄色 > 青色)。今回は発生していませんが、Syntax Errorレベルだと赤の波線で表示されます。 各波線部分は、マウスオーバーすると QuickFix というリンクが出て来て、なぜダメなのかを説明してくれます。 それによると・・ SC2006 ` `囲みはいくつかの問題があるレガシーコード。$( ) を推奨 SC2086 変数は "" で囲むこと。split防止 SC2153 スペルミスの可能性。Variableが割り当てられていない。 SC2162 while read は -r オプションを付けるべき ⇒全部直してみます。 ShellCheck=ON(fixed) 綺麗になりました! 今回指摘された部分は何れも Bash の記法上、実行してもエラー扱いになりません。問題無く動いて exit 0 してしまいます。 つまり 「ShellCheckを通していないと 検証/レビュー時に見落とすリスクがある」 と言うことです。 こういう呪われたシェルは、 「いざ本番というタイミングで発火する」 ものなので、ShellCheckを入れておいて損は無い(というか積極的に使った方が良い)と個人的には思います。 さっそく使ってみる( その2: ShellFormat ) まず、ShellFormat を実行する前のコードをご覧ください。 (ShellFormatは コードを開いて Shift+Alt+F を実行することで瞬時に校正を行います。) ShellFormat (before) style1~3 は別々の人が書いたと思ってください。 何れも問題のない記述ですが、読みづらい。インデントもそろっていないので、レビュー時にロジックを誤認してしまうかもしれません。 ShellFormat を実行してみます。 ↓ ↓ ↓ ↓ ↓ ShellFormat (after) 全て同じ書式に統一されました! 見やすい! ShellFormatは既に書き終わったコードにも適用することができます。 つまり、いつ誰が書いたのかわからないグチャグチャのコードに対してShellFormatを実行すると、 論理を変えることなく見やすい状態にすることが出来る訳です。 こちらのアドオンも使わない理由は特に見あたりません。 まとめ 今回紹介したアドオンは、いわゆる「lint」の一種です。 lint= ソースコード を読み込んで内容を分析し、問題点を指摘してくれる静的解析ツール。 ※ IT用語辞典 e-words より lintを導入するメリットは個人単位でも享受出来ますが、チーム/組織の運用に組み込んで導入するとより大きなメリットを享受することができます。 チーム/組織 でlintを導入するメリット 品質向上 ソースコード の品質を一定レベル保証出来る 潜在的 なバグを事前に潰すことが出来る コスト削減 教育をする側もされる側も楽になる レビューをする側もされる側も楽になる 組織単位で統一されたコードの書式であれば、ジョブローテ時の立ち上げコストが削減できる 納品速度の向上 レビューがサッと終わる ソースコード の品質管理は常に頭を悩ませる課題の一つです。 その中でも、構文のチェックは 本来注力すべき開発の本質ではないので、自動化するに越したことはありません。 やってみようかな?と思われた方、10分で導入出来ますのでお試し頂ければ幸いです! 以上、最後までお読み頂きありがとうございました。 エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 rakus.hubspotpagebuilder.com ラク スDevelopers登録フォーム https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/ イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! ◆TECH PLAY techplay.jp ◆connpass rakus.connpass.com
こんにちは、株式会社 ラク スで先行技術検証を行っている技術推進課の @t_okkan です。 技術推進課では、新サービス立ち上げ時の開発速度アップを目的に、現在 ラク スでは採用されていない新しい技術の検証を行う、技術推進プロジェクトがあります。 今回はその技術推進プロジェクトで、GraphQLについて検証を行いましたので、その結果の報告を行います。 なお、別テーマの取り組みや、過去の取り組みに関しては、こちらからご覧ください。 tech-blog.rakus.co.jp GraphQL GraphQLの言語仕様 クエリ言語 スキーマ言語 GraphQLのアーキテクチャ スキーマファーストな開発 検証してわかったこと フロントエンドとバックエンドの実装を切り離せ、お互いを疎結合にできる 共通の定義ファイルを元に実装を行うため、APIの仕様が明確になり実装の手戻りの防止や開発速度の向上が見込める その他わかったこと REST APIとGraphQLのAPIを1つのアプリケーションで運用できる DDDとGraphQLの相性が良い マイクロサービスアーキテクチャとの相性が良い GraphQLが向いているケース GraphQLが向いていないケース GraphQLのエコシステム GraphQLサーバー GraphQLクライアント その他エコシステム GraphiQL・GraphQL Playground GraphQL Code Generator GraphQL Multipart Request dataloader GraphQL Depth Limit Apollo Studio まとめ GraphQL GraphQLとは、 Facebook が2015年の React.js Conf 2015 で発表した、クライアントとサーバーの通信のための言語仕様になります。 グラフ理論 を用いて複雑なデータを階層構造にしてデータの取得を行います。元々は2012年に Facebook がネイティブモバイルアプリで使用するために開発しましたが、現在は OSS となっており GraphQL Foundation によってエコシステムの開発などが進められています。 graphql.org GraphQLを導入している企業としては、 GitHub 、 Twitter 、Yelp、Shopify、 Netflix などがあげられます。 GraphQLの仕様は2017年まで頻繁にリリースされたいましたが、2018年以降はリリースがなく安定していると言えます。 spec.graphql.org 実装言語や通信方式には縛りはありませんが、通信方式に関しては プロトコル はHTTP、HTTPメソッドはPOST、 データ形式 は JSON が推奨されています。 対応言語は公式ドキュメントで公開されています。 graphql.org GraphQLの言語仕様 クエリ言語 データの取得や更新の操作を指定する言語です。基本的にはクライアント側でクエリ言語を構築し、GraphQLサーバーにリク エス トとして送信します。 基本的なオペレーションとして、Query、Mutation、Subscriptionが存在します。その他にもFragementやVariables、Directivesなどの重要な言語仕様がありますが、詳細は 公式ドキュメント もしくは GraphQL Spec で確認してください。 Query データの取得、読み込み系の処理を担当します。 REST API のGETに相当します。 Mutation データの書き込み、更新系の処理を担当します。 REST API のPOST、PUT、DELETEに相当します。 Subscription WebSocketを利用した双方向のリアルタイムデータ通信を行います。サーバー側からクライアント側にリアルタイムで通知を送信できます。 スキーマ言語 GraphQLサーバーの API の仕様を定義します。サーバー側に スキーマ ファイルを配置し、GraphQLの型システムと API の操作を定義します。 ここでは主な仕様のみ紹介しますので、その他の仕様に関しては 公式ドキュメント と GraphQL Spec で確認してください。 Schema GraphQLサーバーで実行可能な操作(Query, Mutation, Subscription)を定義します。Schemaで宣言した操作がクライアントで実行できる操作となります。 Scalars GraphQL型システムのプリミティブ型を宣言します。GraphQL型システムには組み込み型として、String( UTF-8 の文字列)、Int(符号付き32ビットの整数)、Float(符号付き不動小数点)、Boolean(true, falseの論理値)、ID(一意の識別子でStringと同じ文字列)が存在します。開発者が独自にCustom Scalar型を定義できます。 Types GraphQL型システムの型で開発者が独自で定義できます。型は固有のオブジェクトで対応する複数のフィールドを持つことができます。アプリケーションの特性を反映できます。 GraphQLの アーキテクチャ GraphQLクライアント クエリ言語を構築し、データの取得やデータの更新操作を指定し、GraphQLサーバーにリク エス トを送信します。 GraphQLサーバー GraphQLクライアントから送信されたクエリを解析し、クエリで指定されたオペレーションを実行しデータを返却します。Query Parser、Query Validation、Resolverで構築されています。 Query Parser GraphQLサーバーでクライアントからリク エス トされたクエリ言語がGraphQLの仕様を満たしているか 構文解析 を行います。 構文解析 後にクエリ言語を 機械語 に変換します。基本的にはGraphQLサーバーのライブラリによって実装されています。 Query Validation Query ParserでGraphQLの 構文解析 をした後に、 スキーマ ファイルで定義されたGraphQLサーバーの仕様とクエリ言語の検証を行います。クエリ言語で宣言されている各オペレーションとResolverのナビゲーションを行います。基本的にはGraphQLサーバーのライブラリによって実装されています。 Resolver スキーマ ファイルで定義した型と操作に基づいて、サーバー側にて各 プログラミング言語 で実装を行います。GraphQLサーバーの ビジネスロジック やデータベースなどの永続化データ、またはバックエンドの API にアクセスし必要なデータを取得しレスポンスとして返却します。開発者が各 プログラミング言語 で実装します。 GraphQLの アーキテクチャ スキーマ ファーストな開発 GraphQLを導入することで スキーマ ファーストな開発が可能になります。 開発初期に API の仕様からGraphQLの スキーマ ファイルを定義し、その スキーマ ファイルを元にフロントエンドとバックエンドがそれぞれ実装します。 API の仕様が実装前に明確となるため、フロントエンドとバックエンドの実装を並行に行え、かつ結合した際にエラーが起きづらくなることを期待できます。 検証してわかったこと GraphQLの導入を検証し スキーマ ファーストな開発を行うことにより以下のようなことがわかりました。 フロントエンドとバックエンドの実装を切り離せ、お互いを 疎結合 にできる 共通の定義ファイルを元に実装を行うため、 API の仕様が明確になり実装の手戻りの防止や開発速度の向上が見込める フロントエンドとバックエンドの実装を切り離せ、お互いを 疎結合 にできる 従来の REST API ではSPAなどでフロントエンドとバックエンドを分離をしたい場合、開発の担当を分離することはできてもフロントエンドがバックエンドの API と直接通信していたため、実装を分離できずお互いのI/Fを確認しながら開発を行っていました。そのため、開発速度の低下やでき上がった API が想定とは異なる場合は手戻りが発生していました。 そこでGraphQLを導入することで スキーマ ファイルの存在により、バックエンドとフロントエンドの実装が隠蔽され、 疎結合 に分離できることがわかりました。 実装の分離 バックエンドは スキーマ ファイルで定義されているクエリと型に合わせたデータを提供し、フロントエンドは スキーマ ファイルにて定義されているクエリを実行し定義されているデータを取得することになります。お互い確認するのが スキーマ ファイルになり、それ以降の実装について関心がなくなるため実装を分離できます。実装を分離できることにより、バックエンドの実装や アーキテクチャ の変更による影響を受けづらくなります。 また、お互いの実装が分離されることにより各開発チームの最適な開発手法を取り入れることができます。 バックエンドは画面を意識せずビジネス ドメイン 知識に基づいた API を構築でき、フロントエンドは API の仕様を意識せず画面で必要なデータのみを取得できます。 共通の定義ファイルを元に実装を行うため、 API の仕様が明確になり実装の手戻りの防止や開発速度の向上が見込める GraphQLを導入することで、バックエンドは スキーマ ファイルに定義されているクエリと型を実装し、フロントエンドは スキーマ ファイルに定義されているクエリを実行して型の値を取得します。お互い スキーマ ファイルで定義されていない実装をすると、Query Validationでエラーが発生します。そのため、フロントエンド・バックエンドのどちらかが主導で仕様を変更できなくなります。仕様を変更する場合は、 スキーマ ファイルを変更してからお互いの実装を変更する必要があります。 スキーマ ファイルが API の仕様を明確にするので、仕様の齟齬による手戻りを防ぎ、それぞれの実装に集中できるので開発速度の向上が期待できるかと思います。 その他わかったこと 上述のこと以外で検証を通じてわかったことは以下になります。 REST API とGraphQLの API を1つのアプリケーションで運用できる GraphQLの仕様を実装しているライブラリには、Spring BootやLaravel、Expressとと言ったFrameworkと統合するためのライブラリが存在します。そのため、既存の REST API を一気にGraphQLに移行するのではなく、新機能や一部の機能のみ先行してGraphQLに移行できます。 DDDとGraphQLの相性が良い 先述したビジネス ドメイン をGraphQLの型システムに変換しやすいことから、DDDとGraphQLの相性が良いと考えられます。 AWS のreInvent:2019のBuilding modern APIs with GraphQLのセッションでもDDDとGraphQLとの親和性について紹介されています。(35:39あたりからです) www.youtube.com 登壇者の方のブログでもDDDとGraphQLの相性がよいと紹介されています。 GraphQL and DDD: the Missing Link | HackerNoon マイクロサービス アーキテクチャ との相性が良い Netflix では モノリス な API からマイクロサービスへ移行する際にGraphQLを導入しました。GraphQLを導入後にさらにサービスが増加しGraphQLのResolverの実装が複雑化し ボトルネック になっていたため、 Apollo 社が提唱している Apollo Federationを導入しました。 Apollo Federationは複数のGraphQLサービスを1つのGraphQL Gateway で集約しGraphQLサーバーとして提供する仕組みです。またその際に自社で開発したGraphQLをSpring Bootに統合するための実装を、Domain GraphQL Spring Boot Frameworkという フレームワーク として OSS で公開しています。 Home - DGS Framework GraphQLが向いているケース GraphQLの導入が向いているケースとして、適切に ドメイン を分割できるアプリケーションが考えられます。 GraphQLは導入すれば複雑な仕様を解消するツールではありません。同じクエリを扱う SQL ではデータベースの正規化で関係を整理するのと同様に、GraphQLにおいてもまずはアプリケーションの仕様を適切に分割できるかを検討してください。仕様を分割できる場合は、その分割の粒度に合わせてGraphQLの型を設計します。 例えば、ビジネス ドメイン 単位でGraphQLの型を設計することでバックエンドの実装をシンプルにできるのではないかと思います。 画面駆動から ドメイン 駆動の API 開発になり、画面に合わせた実装がなくなるためController層やView層といったフロントエンドと会話する層の実装が楽になるかと思います。 ドメイン とGraphQLの型 ビジネス ドメイン をGraphQLの型に変換することで、「検証でわかったこと」欄に上げたようなGraphQLの特徴を活かしながら導入できると思います。 この条件を満たしていれば、後述する向いていないケースに該当しない限り、基本的にはどのWeb API にも導入できるかと思います。 GraphQLが向いていないケース GraphQLの導入が向いていないケースとしては、 メディアファイルを扱う API データベースのテーブルを直接操作する API が考えられます。 GraphQLの データ形式 は JSON 形式が推奨されています。そのため周辺のエコシステムも JSON 形式を前提として実装されています。ファイルアップロードの機能を提供しているライブラリもありますが、メディアファイルを扱う場合は、GraphQLとは別の API を提供すべきかと思います。 また管理型のシステムなど、データベースのテーブルを直接操作するようなアプリケーションにも向いていないのではと考えています。GraphQLを導入するためのオーバーヘッドが高いため、 モノリス な構成か REST API で構築すべきかと思います。ただ Prisma や Hasura などテーブルとGraphQLの型が1対1になるようなツールを導入する場合は、検討の余地があるかと思います。 GraphQLのエコシステム GraphQLのエコシステムやその他のツールは公式のドキュメントでまとめられています。今回は検証で利用したツールを中心に紹介します。 GraphQL Landscape GraphQLサーバー 今回は Java とNodeでGraphQLサーバーを構築しました。それぞれで有効なライブラリを紹介します。 Java graphql- java GraphQLの仕様を Java で実装しているライブラリです。クライアントからのクエリのパーサーや、 スキーマ ファイルとの検証を実装しています。 Java でWebフレームワークを利用しない場合はもっとも有効な選択肢となります。 Hello from GraphQL Java | GraphQL Java graphql- java -spring Spring BootとGraphQLを統合するためのライブラリです。内部でgraphql- java を参照しているため、GraphQLの仕様はカバーできています。 @SpringBootTest に相当する @GraphQLTest を提供しています。 About GraphQL Spring Boot - GraphQL Java Kickstart Domain GraphQL Spring Boot Framework( DGS ) 先述した Netflix がGraphQLを導入し、 Apollo Federationを実現させた際に利用した フレームワーク です。Spring BootとGraphQLを統合し、テストライブラリの提供やSpring Securityとの統合をサポートしています。 Apollo Federationで必要な機能をサポートしているため、マイクロサービスとGraphQLの組み合わせを検討している場合は有効なライブラリであると考えられます。 Home - DGS Framework Node GraphQL.js GraphQL Foundationが実装しているライブラリです。GraphQLの仕様を準拠しており信頼性は高いが Apollo Serverに比べると低機能なため検証などで使用できると考えられます。 Getting Started With GraphQL.js | GraphQL Apollo Server Apollo 社によって開発されている フレームワーク です。NodeでGraphQLサーバーを構築する際の デファクト 的存在となっています。dataloaderやサーバー側のキャッシュなど機能が豊富で、 Apollo Federationを利用することでマイクロサービス アーキテクチャ との相性もよいです。 Introduction to Apollo Server - Apollo GraphQL Docs Express GraphQL Expressで構築されているアプリケーションに ミドルウェア としてGraphQL API を追加できます。 GitHub - graphql/express-graphql: Create a GraphQL HTTP server with Express. Apollo Server Plugin Expressで構築されているアプリケーションに ミドルウェア として Apollo Serverの機能を統合できます。 Choosing an Apollo Server package - Apollo GraphQL Docs GraphQLクライアント Apollo Client Apollo 社が開発しているライブラリです。React, Vue.js, Angular, iOS , Android に対応しています。機能が充実しており、ドキュメントや2次情報も充実しています。基本的には Apollo Clientを利用するべきであると思います。 Introduction to Apollo Client - Apollo GraphQL Docs Relay Facebook が開発している フレームワーク です。ReactとReact Nativeに対応しており、事前にクエリを コンパイル できるなどより安全にGraphQLのクライアントを構築できます。しかし、 フレームワーク であり学習コストが高いことや、React Hooksに未対応であることが懸念点として上げられます。 Relay その他 その他の選択肢として GraphQL Request や urql 、 graphqurl などがあります。 JavaScript フレームワーク との統合はサポートされていません。使用範囲が限られる場合は選択してもよいと思います。 また プロトコル にHTTP、HTTPメソッドにPOST、 データ形式 に JSON を指定すれば、クライアントツールを利用せずにクエリをリク エス トできます。ただしその場合はクエリの解析や、レスポンスの マッピング などの機能を独自で実装する必要があります。 その他エコシステム GraphiQL・GraphQL Playground GraphQL API のクライアントツールです。クエリの実行や、 スキーマ ファイルからドキュメントを自動生成することもできます。基本的にGraphQLサーバーのライブラリには開発環境用として内包されています。 github.com github.com GraphQL Code Generator スキーマ ファイルから自動で型定義ファイルを生成するツールです。TypeScriptや Java などの静的型付け言語に対応しています。 CLI も提供しており、ローカル環境で自動生成することもできます。GraphQLサーバーとクライアントのどちらの型定義ファイルを生成できます。 Home – GraphQL Code Generator GraphQL Multipart Request multipart/form-dataを利用したGraphQLでファイルアップロードを行う場合の仕様を定めています。この仕様を各GraphQLサーバーとクライアントのライブラリが実装をしています。GraphQLでファイルアップロードを行う場合はまずこの方法を選択すべきと思います。またライブラリを選定する場合もこの仕様を実装しているかを判断軸にすべきだと思います。GraphQLでファイルアップロードを行う他の手段としては、別 API を用意して登録先の識別子をGraphQLで登録するか、ファイルデータを Base64 で エンコード してGraphQLで登録する方法があります。 github.com dataloader GraphQLでは型同士が接続している場合にN+1問題が発生しやすいです。その回避策としてdataloaderを活用する方法があります。dataloaderは Facebook が提供している JavaScript のライブラリで、このライブラリを基に各 プログラミング言語 で移植されています。一意のIDを集合型でdataloaderに集約し、その集合体を利用して登録されている処理によって一括でデータを取得する仕組みになっています。そのため バッチ処理 とも言われています。N+1問題以外でも利用されています。 github.com また、dataloaderを利用することでバックエンドの負荷が高くなることもあるので、N+1問題を受け入れる選択もあるそうです。 Apollo Serverではサーバー側のキャッシュを利用する方法を推奨しています。 Data sources - Apollo GraphQL Docs GraphQL Depth Limit GraphQLの アンチパターン に循環参照があります。GraphQLの方がお互いに結合している場合に発生し、多段にデータが紐づいてしまいバックエンドの負荷が高くなります。対策としてクエリの深さを制限する方法があります。 graphql-depth-limit というライブラリや、各GraphQLサーバーのライブラリでも提供されています。 Apollo Studio GraphQLは単一のエンドポイントで API を提供するため、エンドポイント単位でのメトリクスの計測ができません。 Apollo Studioを利用すると、クエリごとに実行数や処理時間を計測でき、バックエンドの ボトルネック を発見できます。しかし、 Apollo Serverか 対応しているライブラリ でGraphQLサーバーを構築する必要があります。 Introduction to Apollo Studio - Apollo GraphQL Docs Apollo Studio以外でメトリクスを計測する方法として、マイクロサービスで利用されている Open Tracing を利用する方法もあります。 まとめ GraphQLの検証で得た知見のまとめは以下のようになります。 GraphQLを導入することで、フロントエンドとバックエンドでお互いの実装が切り離せ 疎結合 にできる 共通の定義ファイルを元に実装を行うため、 API の仕様が明確になり開発速度の向上が期待できる GraphQLを導入する前にアプリケーションの仕様を分割できるかを検討する必要がある DDDやマイクロサービス アーキテクチャ との相性が良い GraphQLと REST API は同じアプリケーションで共存できるため、段階的に移行することができる 検証のご紹介は以上になります。GraphQLの導入事例が徐々に増えてきており、エコシステムの強化も進んでいる印象です。 既存の REST API をGraphQLに移行する場合や、新規の API をGraphQLで開発することを検討されている方は是非参考にしてください。 エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 rakus.hubspotpagebuilder.com ラク スDevelopers登録フォーム https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/ イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! ◆TECH PLAY techplay.jp ◆connpass rakus.connpass.com
こんにちは。 株式会社 ラク スで先行技術検証を行っている「技術推進課」の堀内( id:yhoriuchi )です。 ラク スの スマホ アプリ開発 ではCI/CD環境にBitriseを採用しているのですが、 iOS アプリ開発 の一部で使っているだけで十分に活用できているという状況ではありませんでした。 今回、技術推進プロジェクトの一貫として スマホ のCI/CDに取り組み、その中でBitriseを熟知し、最大限活用できるようにしました。 情報量が多くてその全てをお伝えすることはできませんが、調べても情報があまり出てこなかった"Bitrise CLI "をご紹介してみようと思います。 目次 目次 Bitriseとラクスが採用した理由 Bitrise CLIとは インストール方法 ローカルでの実行方法について Bitrise CLI実行時の環境変数 スゴイと叫ばずにはいられなかった機能 最後に Bitriseと ラク スが採用した理由 Bitriseについては多くの記事でも紹介されていますが、概要を理解してもらうには 公式サイトの説明 が分かりやすいので引用しておきます。 Bitriseはモバイルアプリ開発(iOS, Android, Xamarin, …)における 継続的インテグレーション・デリバリー(CI/CD) プラットフォームをサービス(PaaS)として提供しています。 ソフトウェアプロジェクトの開発・自動化を手助けするためのツール・サービスの集合体です。 数回のクリックであなたのアプリのテストやデプロイ作業を自動化しましょう! ラク スでBitriseを採用した理由は、 スマホ 対応に特化していることと、ワークフローと呼ばれる可視化されたフロー図でビルドプロセスが分かりやすくなっていることが大きいです。 また、 スマホ に特化していることもあって iOS 、 Android だけでなく、FlutterやReact Nativeなどにも対応しています。 プラットフォームを問わず スマホ アプリ全般をサポートし、充実した機能があるので、Bitriseを使っていて開発で困ることは無さそうに思います。 しかし、Bitriseが万能とはいえ、なんらかの理由でBitriseが停止した際にビルドやデリバリーに影響が出てしまっては困ります。 そんな時にも備えて対策を検討した際に出てきたのがBitrise CLI でした。 Bitrise CLI とは Bitrise CLI は読んで字の如く、Bitrise Command Line Interfaceのことです。 ローカル実行が可能で、コードが GitHub で公開されているため誰でも利用することができます。当然Bitriseのアカウントも不要です。 これを使えば懸念しているBitrise停止といった不慮の事故に見舞われたとしてもなんとかできそうです。 インストール方法 オープンソース なのでコードをビルドしないといけないのかと考えてしまうかもしれませんが、下記のコマンド一発です。 brew update && brew install bitrise Homebrewを使いたくない、あるいは Linux 環境にインストールしたい場合は下記。(v1.45.1の場合) curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.45.1/bitrise-$(uname -s)-$(uname -m) > /usr/local/bin/bitrise 環境構築にまで気を配っているところが素敵ですね。 ローカルでの実行方法について 実行方法も至って簡単なのですが、その前にワークフローの設定が記載された bitrise.yml を用意する必要があります。 これはイチから作る必要は無く、Bitriseにログインしてワークフローエディタを開くとダウンロードできるようになっているので心配無用です。 具体的には下記のbitrise.ymlメニューからダウンロードできます。 bitrise.ymlの詳細をお見せすることはできませんが、内容は 公式サイトが参考になると思います 。 ここまで来れば実行は簡単で、bitrise.ymlを保存した ディレクト リで下記を実行するだけ。(下記の"SimpleBuild"はワークフロー名) bitrise run SimpleBuild すると下記のように アスキーアート で"BITRISE"のロゴが表示されたあと、ステップが実行されているのが分かります。 bitrise run ちなみに下記は同じワークフローをbitrise.ioで実行したログです。若干見た目が異なりますが、出力される内容はほぼ同じです。 bitrise.io Bitrise CLI 実行時の 環境変数 実行が簡単なことがお分かり頂けたと思いますが、通常BitriseでGit リポジトリ から ソースコード を取得する場合、 リポジトリ のURLとブランチ名はワークフローの設定ではなく、1つ上の階層にあるアプリケーションの設定に記載し、ステップの中では用意された 環境変数 を参照することになると思います。 残念ながらこの設定( 環境変数 )はbitrise.ymlに記載されないため、自前で 環境変数 としてexportしておく必要があります。 具体的な 環境変数 名は GIT_REPOSITORY_URL と BITRISE_GIT_BRANCH の2つです。 他にもbitrise.ioが使う環境変数 はいくつもありますが、弊社では今のところこの2つだけで足りています。 これを毎回exportするのが手間なので弊社では スクリプト を作って運用することにしました。 参考までに弊社で作成した スクリプト (一部抜粋版)を載せておきます。 #!/bin/sh WORKFLOW=$1 if [ -z "${WORKFLOW}" ]; then echo "第1引数でビルド対象のワークフローを指定してください" exit 1 fi export GIT_REPOSITORY_URL="{{リポジトリのURL}}" export BITRISE_GIT_BRANCH="{{ブランチ名}}" ssh-add ~/.ssh/id_rsa bitrise run ${WORKFLOW} スゴイと叫ばずにはいられなかった機能 最後に1つだけBitrise CLI でスゴイ!と思った機能を紹介したいと思います。 それがワークフローエディタです。とりあえず下記のコマンドを実行してみてください。 bitrise :workflow-editor すると、なんということでしょう、ブラウザが立ち上がってワークフローエディタが使えるじゃないですか! (下記のような画面が表示されます) bitrise workflow-editor スタックと呼ばれる 仮想マシン の管理機能が無いので言い過ぎになってしまいますが、これってBitriseの機能そのものだったりします。 オープンソース でここまで使えることに驚きを隠せませんでした。万が一に備えようと調べ始めた CLI 環境ですが、十分すぎる機能が提供されているので安心してBitriseを使えると思いました。 最後に いかがだったでしょうか。Bitrise CLI の情報でお役に立つ内容があったでしょうか。 実のところ、 スマホ アプリ開発 に着手した直後はビルドサーバーを社内に構築しようと考えていました。しかし、ビルドマシンのメンテコスト(機器、OS、 SDK 、その他ソフトウェアなどのメンテの手間)を考えると割りに合わないと判断し、CI/CD環境を提供してくれる クラウド サービスを利用することにしました。 今回、Bitriseには十分な機能と柔軟な環境が用意されていると改めて知ることができたので、この先もBitriseを活用していくことで開発を効率よく進めていくことができそうです。
目次 目次 はじめに styled-componentsとは 使用環境 環境構築 styled-componentsでの装飾 リンクの装飾 propsを使ったリンクの装飾 リンク以外の装飾 VSCodeの拡張機能 おわりに 参考 はじめに 初めまして。フロントエンドチーム 新卒1年目のhy094です。 業務でstyled-componentsを使う機会があったのでまとめたいと思います。 styled-componentsとは CSS in JSの一つです。 JSの中にスタイルを記述できるので、 CSS ファイルが必要なくなることや typo をエラーで教えてくれたりするメリットがあります。 また、 コンポーネント 単位で装飾するためReactとの親和性が高いです。 使用環境 上記の通り、Reactとの親和性が高いためReactを使用します。 折角なので弊社のブログ記事に書かれている内容を装飾していきます。 使用する環境・ ソースコード などはこちら↓ tech-blog.rakus.co.jp Reactの入門記事になります。ぜひご覧ください。 ※本記事は上の記事の内容を実施した直後という状態を想定して進めていきます。 環境構築 上記の記事だけだとstyled-componentsがインストールされていないので、 追加でインストールします。 npm install styled-components package. json に "styled-components": "^5.2.1", のような記述があれば問題なく成功しています。 styled-componentsでの装飾 リンクの装飾 元記事をそのまま実施していただいている場合、 フォルダ構成は以下のようになっていると思います。 ├── node_modules/ ├── package.json ├── public/ ├── README.md ├── src/ │ ├── page │ │ ├── one.js │ │ └── two.js │ └── routes.js └── yarn.lock まずは コンポーネント を配置するためのフォルダをsrc以下に作成します。 src/components 次に、src/components以下にLink コンポーネント を作成します。 src/components/Link.js import React from "react" ; import styled from 'styled-components' export const Link = ( { to,children } ) => { return <StyledLink href= { to } > { children } </StyledLink> } ; const StyledLink =styled.a` //ここにスタイルを記述 outline: none; text-decoration: none; display: inline-block; width: 19.5%; margin-right: 0.625%; text-align: center; line-height: 3; color: black; background: yellow; &:hover { background: orange; } &:active { background: red; color: white; } ` //ここにスタイルを記述 以降にお好きなスタイルを記述してください。 今回は MDNのリンクの装飾 をお借りしたいと思います。 次に作成したLink コンポーネント をアプリの方でimportします。 src/page/one.js import React from 'react'; - import { Link } from 'react-router-dom'; + import { Link } from '../components/Link'; import Two from './two'; class One extends React.Component{ render(){ return ( <div> test_one<br/> <Link to='/two'>twoへ</Link> </div> ) } } export default One; src/page/two.js import React from 'react'; - import { Link } from 'react-router-dom'; + import { Link } from '../components/Link'; import One from './one'; class Two extends React.Component{ render(){ return ( <div> test_two<br/> <Link to='/'>oneへ</Link> </div> ) } } export default Two; 見ていただくと分かる通り、差分は2行目のimport文のみです。 ここまでの作業でリンクのスタイルが変わっています。 propsを使ったリンクの装飾 「リンクといえばこのスタイル」という規則が決まっていれば、 今後は <Link>○○</Link> を使うことでclassなどを指定する必要がなくなります。 しかし今の表示だと自分がoneにいるのかtwoにいるのか分かりにくいですね。 そこでoneとtwoでスタイルを変えたいと思います。 src/components/Link.js import React from "react" ; import styled, { css } from 'styled-components' export const Link = ( { to,children,fontColor,isBluePattern } ) => { return <StyledLink href= { to } fontColor= { fontColor } isBluePattern= { isBluePattern } > { children } </StyledLink> } ; const StyledLink =styled.a` //ここにスタイルを記述 outline: none; text-decoration: none; display: inline-block; width: 19.5%; margin-right: 0.625%; text-align: center; line-height: 3; color:$ { (props) => props.fontColor } ; $ { (props) => props.isBluePattern ? (css` background: aquamarine; &:hover { background: aqua; } &:active { background: blue; color: white; } `): (css` background: yellow; &:hover { background: orange; } &:active { background: red; color: white; } `) } ` src/page/one.js import React from 'react' ; import { Link } from '../components/Link' ; import Two from './two' ; class One extends React.Component { render() { return ( <div> test_one<br/> <Link to= '/two' fontColor= "blue" isBluePattern= { false } >twoへ</Link> </div> ) } } export default One; src/page/two.js import React from 'react' ; import { Link } from '../components/Link' ; import One from './one' ; class Two extends React.Component { render() { return ( <div> test_two<br/> <Link to= '/' fontColor= "red" isBluePattern= { true } >oneへ</Link> </div> ) } } export default Two; 少し無理矢理感もありますが、これでページごとにスタイルが変わります。 color:${(props) => props.fontColor}; で、 <Link> に指定した fontColor の色が文字色となります。 oneは青、twoは赤ですね。 また、 $ { (props) => props.isBluePattern ? (css`    //以下略 三項演算子 と css によってまとめてスタイルを指定できます。 isBluePatternがtrueの時、 青背 景のボタン風リンクになります。 リンク以外の装飾 const StyledLink = styled.a でリンクの装飾ができました。 ではリンク以外の装飾はどうするのでしょうか。 答えは単純で h1 なら styled.h1 、 div なら styled.div です。 VSCode の 拡張機能 ここまで作業するとLink.jsは以下のような表示になっているのではないでしょうか。 見にくいですね。また、 CSS の補完も効きません。 そこで便利なのが VSCode の 拡張機能 vscode-styled-components です。 この 拡張機能 を使うと・・・ 非常に見やすくなり、補完も効くようになります。 styled-componentsを書くならぜひ抑えておきたい 拡張機能 だと思います。 おわりに 今回はstyled-componentsのさわりについて紹介させていただきました。 スタイルの話やデザイン関連の話はあまりできませんでしたが もしそちらにご興味ある方は弊社デザイナーチームのブログがあるので、 よかったらご覧ください!↓ note.com 現在はチームの話などが多いですが、今後記事数はどんどん増えていくと思います。 styled-componentsと親和性の高いatomic-designについて記事ができることを密かに楽しみにしています。 参考 https://qiita.com/snishinon/items/9ea9c2f981ef081e825f https://zenn.dev/syu/articles/0f92abf7f0b5c5 https://qiita.com/taneba/items/4547830b461d11a69a20 https://www.webprofessional.jp/style-react-components-styled-components/ エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 rakus.hubspotpagebuilder.com ラク スDevelopers登録フォーム https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/ イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! ◆TECH PLAY techplay.jp ◆connpass rakus.connpass.com
はじめに こんにちは、開発エンジニアの amdaba_sk( ペンネ ーム未定)です。 今回は PHP のお話です。例えば以下のような配列があったとしましょう。 <?php $ target = [ 'ほげ' , 'ふが' , 'ぴよ' , ] ; これをソートしたいとします。ただそれだけなら、 <?php sort ( $ target ) でおしまい、 Q.E.D. ! でもいいのですが、 PHP には他にもいろいろな配列のソート方法が用意されていますよね。 この記事は、それらいろいろなソート方法を紹介し、特に日本語文字列を値に持つ配列の場合もっと「イイ感じ」にする方法がありますよというお話です。 なお、ソートに限らず PHP の配列機能について知りたいという方は、弊社ブログのこちらの記事も併せてチェックしてみてくださいね。 tech-blog.rakus.co.jp もくじ はじめに もくじ PHP の配列ソート関数いろいろ 標準の配列ソート関数 - sort SORT_REGULAR SORT_STRING 日本語文字列をイイ感じに並べる sort 関数に SORT_LOCALE_STRING フラグを指定する intl 拡張を使う - Collator::sort または collator_sort 独自ルールを実装する - usort 実装例:いろは順 おわりに PHP の配列ソート関数いろいろ お決まりですが、こういうときまずは公式の「 PHP マニュアル」を見に行くようにしましょう。 PHP は公式マニュアルがとても充実していますよね。配列のソートについても、それについてまとめた専用のページがあります。 www.php.net 関数名 ソートの基準 キーと値の相関関係 ソート順 関連する関数 array_multisort() 値 連想配列 の場合は維持し、数値添字配列の場合は維持しない 最初の配列、あるいはソートオプション array_walk() asort() 値 維持する 昇順 arsort() arsort() 値 維持する 降順 asort() krsort() キー 維持する 降順 ksort() ksort() キー 維持する 昇順 asort() natcasesort() 値 維持する 大文字小文字を区別しない自然順 natsort() natsort() 値 維持する 自然順 natcasesort() rsort() 値 維持しない 降順 sort() shuffle() 値 維持しない ランダム array_rand() sort() 値 維持しない 昇順 rsort() uasort() 値 維持する ユーザー定義 uksort() uksort() キー 維持する ユーザー定義 uasort() usort() 値 維持しない ユーザー定義 uasort() 配列をソートする関数が表でまとめられ、それぞれの関数のソートの基準(キー or 値)、キーと値の相関、ソート順、関連する関数が一覧できます。各関数ごとの詳細ページへのリンクもあって、勉強にも役立ちます。 余談ですが、ソートの基準とキーと値の相関が情報として入ってくるのは、すべての配列が 連想配列 な PHP の特徴的なところですね。 さて、マニュアルは PHP の一般の配列に関するものですが、本記事では文字列を値として持つ、一次元の連番整数値添字の配列について考えます。例えばいくつかの静的型付け言語だと、対象配列が次のように型付けされる場合です 1 。 Java : List<String> , C# : List<string> , TypeScript: string[] , Scala : List[String] , F#: string list , Haskell : [Text] また、すでに暗黙的に前提としていますが、特に日本語の文字を含む文字列を想定しています。 このような問題設定の下で、ここでは sort 関数について詳しく見ていくことにしましょう。 標準の配列ソート関数 - sort www.php.net sort ( array &$array , int $flags = SORT_REGULAR ) : bool sort は配列を昇順にソートする関数です。 第一引数 $array にソートしたい配列への参照を受け取って、ソートに成功したかどうかを真偽値で返します。引数で渡した配列は、この関数の実行によって変更されます。破壊的な作用を持つ関数ということです。 またオプションの第二引数 $flags に整数で表されたフラグを取り、要素の大小関係の判定方法を切り替えることが出来ます。使える値は以下の通りです。 SORT_REGULAR - 通常通りに項目を比較します。 詳細は 比較 演算子 で説明されています。 SORT_NUMERIC - 数値として項目を比較します。 SORT_STRING - 文字列として項目を比較します。 SORT_LOCALE_STRING - 現在の ロケール に基づいて、文字列として項目を比較します。 比較に使う ロケール は、setlocale() 関数で変更できます。 SORT_NATURAL - 要素の比較を文字列として行い、 natsort() と同様の「自然順」で比較します。 SORT_FLAG_CASE - SORT_STRING や SORT_NATURAL と (ビットORで) 組み合わせて使い、 文字列のソートで大文字小文字を区別しないようにします。 これらはすべて、 PHP のグローバルな定数として定義されています。いくつかピックアップしてみましょう。 SORT_REGULAR SORT_REGULAR は sort の第二引数 $flags のデフォルト値です。この値が指定された場合、要素の大小関係の判定には宇宙船 演算子 <=> (と同等の処理)が使われます。 宇宙船 演算子 の詳細については 公式マニュアルの該当するページ を参照してほしいのですが、 SORT_REGULAR が指定された場合の sort は、すべての要素が文字列である前提であれば、次に取り上げる SORT_STRING と変わりません。 ただしマニュアル上でも以下のような警告があるように、要素に複数の型が混在する場合は注意が必要です。 警告 flags が SORT_REGULAR の場合に 複数の型が混在する配列をソートする場合には、注意してください。 sort() が期待しない結果を出力することがあります。 この件に関する詳細は、かなり古い記事ではありますが、以下が参考になります。 hnw.hatenablog.com SORT_STRING SORT_STRING が指定された場合 sort 関数は各要素を文字列であるとして大小関係を比較します。 文字列であるとして比較するとはつまり、文字列を文字の配列であるとして、 文字コード の数値的大小関係に基づく辞書式順序で比較する 2 ということです。 例えば 'abc' と 'abd' を比較する場合、以下のように先頭から一文字ずつ文字の大小を比較していきます。 文字位置 'abc' 'abd' 比較 1 a a a = a 2 b b b = b 3 c d c < d 全体 'abc' 'abd' 'abc' < 'abd' 初めて違いの出た文字位置の文字の大小で、文字列全体の大小が決まります。ちなみに文字列の終端は、どんな文字よりも小さいと判定されます。 PHP においては、最終的に C 言語の memcmp 関数を用いる実装がされています 3 。 日本語文字列をイイ感じに並べる ここで以下のような配列をソートすることを考えてみましょう。 順位 文字列 読み UTF-8 文字コード 値 1 あぶりだし アブリダシ E38182 E381B6 E3828A E381A0 E38197 2 あまガミ アマガミ E38182 E381BE E382AC E3839F 3 ういーん ウイーン E38186 E38184 E383BC E38293 4 アフロディテ アフロディテ E382A2 E38395 E383AD E38387 E382A3 E38386 5 アフロディーテ アフロディーテ E382A2 E38395 E383AD E38387 E382A3 E383BC E38386 6 アプロディテ アプロディテ E382A2 E38397 E383AD E38387 E382A3 E38386 7 アマガミ アマガミ E382A2 E3839E E382AC E3839F 8 ウィーン ウィーン E382A6 E382A3 E383BC E383B3 9 一遍上人 イッペンショウニン E4B880 E9818D E4B88A E4BABA 10 悪人正機 アクニンショウキ E682AA E4BABA E6ADA3 E6A99F 11 阿闍梨 アジャリ E998BF E9978D E6A2A8 上表は sort 関数に SORT_STRING を指定して並べ替えた順に記載しています。各文字列の UTF-8 文字コード 値を 16 進数表示で付けました。これを見てもらうと、前節で説明した通りの順序に並んでいることが分かります。 一方で、ぱっと見てこの順序は「イイ感じ」でしょうか? 少し考えれば納得できそうですが、おそらくぱっと見だと「なんでこんな順序になっているの?」と思ってしまうのではないでしょうか。 世界にはいろいろな言語があり、使用している文字も違います。それぞれの言語での最適な文字の並び順が、 文字コード の大小とは一致しないこともあります。日本語だと、文字列は基本「読み」の順に並べるのが普通ですよね。 さて、ようやく本題にたどり着きました。日本語文字列を PHP で「イイ感じ」にソートするにはどのようにすればいいでしょうか? 先に結論をいうと、以下の 3 つの方法があります。 sort 関数に SORT_LOCALE_STRING フラグを指定する intl 拡張を使う - Collator::sort または collator_sort 独自ルールを実装する - usort 一つずつ見ていきましょう。 sort 関数に SORT_LOCALE_STRING フラグを指定する 標準のソート関数である sort は、先にも説明した通りオプションの第二引数 $flags によって要素の大小関係の判定方法を切り替えることが出来ます。 $flags に SORT_LOCALE_STRING が指定された場合、 SORT_STRING と同様各要素を文字列であるとして大小関係を比較しますが、その際、現在の ロケール に基づいて地域化された比較ルールを用います。 文字列を文字の配列であるとして先頭から一文字ずつ文字の大小を比較するという点は SORT_STRING の時と同様なのですが、文字同士の大小比較ルールが単純な 文字コード の大小比較ではありません。 SORT_LOCALE_STRING を使った場合、 PHP は内部的には C 言語の strcoll 関数を使って文字列の比較します 4 。これによって ロケール が示す地域の言語に適した方法で文字の大小比較をしてくれるというわけです。 先ほどのサンプル配列を ロケール が ja_JP.UTF-8 の環境下で、 SORT_STRING と SORT_LOCALE_STRING のそれぞれでソートした結果を比較すると、下のようになります。 --- SORT_STRING +++ SORT_LOCALE_STRING @@ @@ Array ( 0 => 'あぶりだし' 1 => 'あまガミ' 2 => 'ういーん' - 3 => 'アフロディテ' - 4 => 'アフロディーテ' + 3 => 'アフロディーテ' + 4 => 'アフロディテ' 5 => 'アプロディテ' 6 => 'アマガミ' 7 => 'ウィーン' - 8 => '一遍上人' + 8 => '阿闍梨' 9 => '悪人正機' - 10 => '阿闍梨' + 10 => '一遍上人' ) 比較結果を見ると、「ひらがな → カタカナ → 漢字」という文字種ごとの並び順は SORT_STRING の時と同様です。しかし漢字文字列だけを見ると、「ア → アク → イチ」と読みの順に並んでいて、より日本語の文字列として自然と思える並び順になっていると言えます。 より詳細にどのような比較ルールになっているのかというと、申し訳ないのですがにわか仕込みの C 言語ぢからが足りず、分かりませんでした。おそらく PHP を コンパイル した際の処理系に依存するものと考えているのですが、実際のところどうなのでしょう。教えてつよつよな人……! intl 拡張を使う - Collator::sort または collator_sort www.php.net オブジェクト指向 型 public Collator::sort ( array &$arr , int $sort_flag = ? ) : bool 手続き型 collator_sort ( Collator $coll , array &$arr , int $sort_flag = ? ) : bool PHP をビルドする際に、国際化用拡張モジュール (intl 拡張) を有効にすることで、 ICU ライブラリのラッパーを使うことが出来るようになります。ライブラリで提供される機能はいくつもありますが、ここで注目するのは Collator です。 Collator は、 ロケール に応じた適切な並び順を考慮した文字列比較機能を提供するクラスです。並び順のルールは UCA に準拠しています。 Collator を使って配列を並べ替えるには、 インスタンス メソッドの Collator::sort か、あるいは collator_sort 関数を使います。 使い方は標準の sort 関数とほぼ同じで、引数 $arr にソートしたい配列への参照を受け取って、ソートに成功したかどうかを真偽値で返します。引数で渡した配列は、この関数の実行によって変更されます。破壊的な作用を持つ関数というところも同じです。 またオプションの引数 $sort_flag に整数で表されたフラグを取り、要素の大小関係の判定方法を切り替えることが出来ます。使える値は以下の通りです。 Collator::SORT_REGULAR - 通常の比較 (型を変更しない) Collator::SORT_NUMERIC - 数値としての比較 Collator::SORT_STRING  - 文字列としての比較 省略した場合のデフォルトは Collator::SORT_REGULAR ですが、要素がすべて文字列である場合は Collator::SORT_STRING を指定した場合と結局のところ同じになります 5 。そして Collator::SORT_STRING が指定された場合、 Collator::sort は内部的に ICU ライブラリで提供される C 言語の関数 ucol_strcoll 6 を使用して文字列の比較をします 7 。 ロケール ja_JP.UTF-8 が有効な環境下で、サンプル配列を標準の sort + SORT_LOCALE_STRING と Collator::sort + Collator::SORT_STRING のそれぞれでソートして比較してみましょう。 <?php namespace Tests\Unit; use PHPUnit\Framework\TestCase; use Collator; class CollatorSortTest extends TestCase { /** * ロケールの設定(SORT_LOCALE_STRING 用) */ public static function setUpBeforeClass () : void { parent :: setUpBeforeClass () ; setlocale ( LC_COLLATE, 'ja_JP.UTF-8' ) ; } /** * sort + SORT_LOCALE_STRING と Collator::sort + Collator::SORT_STRING の * ソート結果を比較するテストケース * @return void */ public function testCollatorSort () { $ sample = [ 'あぶりだし' , 'あまガミ' , 'ういーん' , 'アフロディテ' , 'アフロディーテ' , 'アプロディテ' , 'アマガミ' , 'ウィーン' , '一遍上人' , '悪人正機' , '阿闍梨' , ] ; $ sortLocalString = $ this -> sortAndReturn ( fn ( &$ arr ) => sort ( $ arr , SORT_LOCALE_STRING ) , $ sample ) ; $ sortCollator = $ this -> sortAndReturn ( fn ( &$ arr ) => Collator :: create ( 'ja_JP.utf8' ) -> sort ( $ arr ) , $ sample ) ; $ this -> assertEquals ( $ sortLocalString , $ sortCollator ) ; } /** * 配列にソート関数を適用して返す * @param callable $sortFunc ソート関数 * @param array $arr 対象配列 * @return array */ private function sortAndReturn ( callable $ sortFunc , array $ arr ) : array { $ sortFunc ( $ arr ) ; return $ arr ; } } 実行するとこのテストケースは失敗して、以下の差分出力が出て来ます。 --- Expected (sort + SORT_LOCALE_STRING) +++ Actual (Collator::sort + Collator::SORT_STRING) @@ @@ Array ( 0 => 'あぶりだし' - 1 => 'あまガミ' - 2 => 'ういーん' - 3 => 'アフロディーテ' - 4 => 'アフロディテ' - 5 => 'アプロディテ' - 6 => 'アマガミ' - 7 => 'ウィーン' + 1 => 'アフロディーテ' + 2 => 'アフロディテ' + 3 => 'アプロディテ' + 4 => 'アマガミ' + 5 => 'あまガミ' + 6 => 'ウィーン' + 7 => 'ういーん' 8 => '阿闍梨' 9 => '悪人正機' 10 => '一遍上人' ) 比較結果を見ると、「かな文字 → 漢字」という並び順は相変わらずですが、 Collator::sort では sort + SORT_LOCALE_STRING とは違って、ひらがなとカタカナの区別がなくなり、音の順で並べられるようになりました。また漢字文字列同士であればやはり読みの順に並んでいて、 sort + SORT_LOCALE_STRING よりもさらに日本語の文字列として自然と思える並び順になっていると言えます。 独自ルールを実装する - usort www.php.net usort (array &$array , callable $callback ): bool sort + SORT_LOCAL_STRING や Collator::sort でも満足できないこだわり派の方には、独自に比較関数を実装する方法をご紹介しましょう。 usort は第一引数 $array にソートしたい配列への参照を受け取って、ソートに成功したかどうかを真偽値で返します。引数で渡した配列は、この関数の実行によって変更されます。破壊的な作用を持つ関数ということです。 sort と異なるのは第二引数がフラグではなく、要素を比較するための関数であり、かつ必須であるという点です。無理やり シグネチャ を上と同じように書くならば、第二引数のコールバック関数は以下のようになるでしょうか。 $callback (mixed $element1 , mixed $element2 ): int 配列の要素を引数として二つ取り、整数値を返す関数です。返り値の int は、最初の引数 $element1 のほうが二番目の引数 $element2 より大きい場合は正の数を、等しい場合はゼロを、そして小さい場合は負の数を返すように実装します。 このコールバック関数によって、好きなルールでソートを実現することが出来るという寸法です。要件に合わせて適切なルールを実装しましょう。ちなみに昇順降順の切替も、返り値の正負を反転させることで実現することが出来ます( ursort が無いのはそのためでしょう)。 実装例:いろは順 せっかくなので ursort を使った独自ソート関数の実装をやってみましょう。 ursort を使うのであれば、配列の各要素文字列からソートキー(よみがな等)を別途作成して付け加え、それを使ってソート、なんてこともできるでしょうが、ここでは以下の簡単な条件で実装してみます。 ソート対象配列の要素文字列はすべて「ひらがな」で構成される ただし濁音、半濁音、長音、拗音、促音に相当する文字は含まれないものとする 各文字列は辞書式順序で順序比較する 各文字の順序は「 いろは順 」に従う できあがりはこちらです。 <?php class IrohaOrder { /** @var array $iroha いろは順マスタ */ private $ iroha = [ 'い' , 'ろ' , 'は' , 'に' , 'ほ' , 'へ' , 'と' , 'ち' , 'り' , 'ぬ' , 'る' , 'を' , 'わ' , 'か' , 'よ' , 'た' , 'れ' , 'そ' , 'つ' , 'ね' , 'な' , 'ら' , 'む' , 'う' , 'ゐ' , 'の' , 'お' , 'く' , 'や' , 'ま' , 'け' , 'ふ' , 'こ' , 'え' , 'て' , 'あ' , 'さ' , 'き' , 'ゆ' , 'め' , 'み' , 'し' , 'ゑ' , 'ひ' , 'も' , 'せ' , 'す' , 'ん' , ] ; /** * 文字列配列をいろは順に並べる * @param array $arr * @return bool ソートできたかどうか */ public function sort ( array &$ arr ) : bool { try { usort ( $ arr , fn ( $ a , $ b ) => $ this -> compare ( $ a , $ b )) ; return true ; } catch ( RangeException $ e ) { return false ; } } /** * 文字列を二つ受け取っていろは順に従って順序を決める * @param string $a * @param string $b * @return int <=> の仕様に沿った整数値 * @throws RangeException 引数の文字列にいろは順が付けられない場合 */ public function compare ( string $ a , string $ b ) : int { $ lenA = mb_strlen ( $ a ) ; $ lenB = mb_strlen ( $ b ) ; $ ret = 0 ; for ( $ i = 0 ; $ i < min ([ $ lenA , $ lenB ]) ; $ i ++ ) { $ c = $ this -> getCharIndex ( mb_substr ( $ a , $ i , 1 )) ; $ d = $ this -> getCharIndex ( mb_substr ( $ b , $ i , 1 )) ; $ ret = $ c <=> $ d ; if ( $ ret !== 0 ) { break ; } } return $ ret ?: $ lenA <=> $ lenB ; } /** * ひらがな一文字を取っていろは順での順位を返す * @param string $c ひらがな一文字 * @return int いろは順で何番目か * @throws RangeException 引数の文字列にいろは順が付けられない場合 */ private function getCharIndex ( string $ c ) : int { $ ret = array_search ( $ c , $ this -> iroha, true ) ; if ( $ ret === false ) { throw new RangeException ( "Character ${$c} is not supported!" ) ; } return $ ret ; } } 使い方のサンプルも兼ねて、テストも簡単に作ってみました。 <?php namespace Tests\Unit; use PHPUnit\Framework\TestCase; use IrohaOrder; class IrohaOrderTest extends TestCase { /** @var IrohaOrder $irohaOrder */ private $ irohaOrder ; public function setUp () : void { $ this -> irohaOrder = new IrohaOrder () ; } /** * ひらがな文字列配列がいろは順に並ぶことの確認 * @return void */ public function testIrohaOrderSort () { $ expected = [ 'いしき' , 'いんかん' , 'ろうか' , 'はにわ' , 'におう' , 'におうもん' , 'ほとけ' , 'ほんき' , 'へいし' , 'へんろ' , 'とうき' , ] ; $ sorted = $ this -> shuffleAndSort ( fn ( &$ arr ) => $ this -> irohaOrder -> sort ( $ arr ) , $ expected ) ; $ this -> assertEquals ( $ expected , $ sorted ) ; } /** * 配列をシャッフルしてソート関数を適用して返す * @param callable $sortFunc ソート関数 * @param array $arr 対象配列 * @return array */ private function shuffleAndSort ( callable $ sortFunc , array $ arr ) : array { shuffle ( $ arr ) ; $ sortFunc ( $ arr ) ; return $ arr ; } } 実行してみましょう。 PHPUnit 9.5.2 by Sebastian Bergmann and contributors. . 1 / 1 (100%) Time: 00:00.004, Memory: 4.00 MB OK (1 test, 1 assertion) うまく動いているようです。 おわりに いかがでしたでしょうか? 本記事では、日本語文字列を値として持つ一次元の連番整数値添字の配列を対象として、 PHP で利用可能なソート方法をいくつか取り上げて具体例とともに紹介しました。ポイントをまとめると以下のようになります。 PHP で配列をソートする方法はいろいろある シンプルな sort 関数の結果は時に「イイ感じ」にならない PHP で日本語文字列を「イイ感じ」の順序で並べる方法は 3 通り その 3 通りの方法は以下の通りです。 sort 関数で SORT_LOCAL_STRING フラグを指定する 最も低コスト 実際の並び順は 処理系依存 (?) intl 拡張の Collator::sort または collator_sort を使う 拡張の有効化が必要で少し面倒 UCA に準拠した順序でのソートが可能 独自ルールを実装する 最も高コスト 並び順ルールを自由に決められる それでどの方法が一番「イイ感じ」なのかというと、結局要件次第にはなってしまうものの、個人的には 2 番の intl 拡張の Collator::sort または collator_sort を使う方法が以下の点で「イイ感じ」だと思います。 Unicode の技術標準として定められた仕様に準拠できる 外部のよくテストされたライブラリを使用できる 仕様の標準さと実装コストのバランスがよい 以上、 PHP で日本語の文字列配列をイイ感じにソートする話でした。少しでもみなさんの PHP ライフの参考にしていただけたら幸いです。 ※ 本記事に記載したサンプルコードは php 7.4.15 ( cli ) で動作を確認しています。 エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 rakus.hubspotpagebuilder.com ラク スDevelopers登録フォーム https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/ イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! ◆TECH PLAY techplay.jp ◆connpass rakus.connpass.com 言語によっては他にもっといい型があるかもしれませんが、その辺はスルーでお願いします。 ↩ https://ja.wikipedia.org/wiki/ 辞書式順序 ↩ https://github.com/php/php-src/blob/94d96b3c979a60e47abe209506c3947a16abff40/Zend/zend_operators.c#L2684 ↩ https://github.com/php/php-src/blob/94d96b3c979a60e47abe209506c3947a16abff40/Zend/zend_operators.c#L1976 ↩ https://github.com/php/php-src/blob/94d96b3c979a60e47abe209506c3947a16abff40/ext/intl/collator/collator_sort.c#L51 ↩ https://unicode-org.github.io/icu/userguide/collation/api.html#compare ↩ https://github.com/php/php-src/blob/94d96b3c979a60e47abe209506c3947a16abff40/ext/intl/collator/collator_sort.c#L168 ↩