TECH PLAY

株式会社ラクス

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

941

はじめに 9/29(水)に行われました 『UI/UXデザイナーLT会 #uiuxdesignerslt』 のイベントレポートを発信させていただきます! 申し込みは300名の満枠、当日もたくさんの方にご参加いただけたイベントになりました! rakus.connpass.com はじめに イベント概要 発表の紹介 1. 伝わらない UI UX の 正しい伝え方 / 木村圭 2. UIを止めるな! ~UIデザインのコンサルティングで話すこと~ / yumemiさん 3. デザイナーとして入社した私がフロントエンド領域から届ける体験価値とは / Three4Cさん 4. 三次元的アクセシビリティ解釈。価値、多様性、そして時間 / masuP9さん 5. 新卒2年目のデザイナーが大事にすること / yuriさん 6. TypeScriptで解説する催眠術のかけ方 / Ouji Miyaharaさん 7. Adobe XDでつくるVUIプロトタイピング / Makiko Odaniさん 8. デザイナー採用のUXデザインに取り組み始めた話 / TomoyoHirokawaさん 9. UIデザインの呪術性 - 覚書- / yuki_sasakiさん 10. デザイナー 1 年目のわたしが考えるデザイン / uknmrさん 11. 生産性が上がる文章の書き方と、デザインと文章の関係性 / ShinyaSatoさん 12. 技術とデザインの間 / takanoripさん 13. TypeScriptではじめるUIデザイン / kgsiさん 14. Web UIの実装で考えていることと気をつけたいこと / yamanokuさん 15.3次元のカラーピッカー的なものを作ってみた / chooさん 16. BtoB SaaSのUIリニューアルの苦労 / 小林肇 おわりに イベント概要 今回はUI/UXデザイナー16名による怒涛のLT会を開催させていただきました。 UI/UXデザインをテーマとしたイベントはconnpassを探しても多くなく、16名という大人数でもあっという間にLT枠が埋まりました。 また今回が登壇へ初チャレンジでした方々には、登壇機会をご提供できた形となったことを大変嬉しく思います。 当日は各登壇者16名の個性的な発表により、最後までコメントやツイートが止まりませんでした。 Twitter のタイムラインも大変賑わい、催眠術とカレーという謎の文言の他、ご参加の方からも便利情報のリンクなどをシェアいただけました。 タイムラインを眺めるだけでも学べる情報に溢れていますので、ぜひご覧になっていただき、当日の盛り上がりを感じていただければと思います。 twitter.com ちなみに当日イベントに参加いただいた方の職種は、以下の割合でした。 UI/UXデザイナー:41% エンジニア:39% 学生:5% その他デザイン関連職種:5% その他ビジネス職種:10% 発表の紹介 それでは今回ご登壇いただいた16名の方のコンテンツを紹介させていただきます。 本当に幅広いキャリアの方々に発表をいただき、ありがとうございました! 1. 伝わらない UI UX の 正しい伝え方 / 木村圭 まずは ラク スのマネージャーである木村から、UI/UXデザイナーの立場からのプロジェクトメンバーとのコミュニケーション(説明力)について、発表させていただきました。 関係するメンバーに思うように意図を伝えられない、UI/UXの重要性が浸透しない、などの悩みを抱えている方には必見の内容です! speakerdeck.com 2. UIを止めるな! ~UIデザインの コンサルティング で話すこと~ / yumemiさん UIデザインと コンサルティング に取り組まれている yumemiさんより、日々のお仕事で考えている「デザインの使いやすさ、デザインの本来あるべき姿」を オブジェクト指向 UIの例などを交えつつ、ご紹介いただきました。 「行動」と「結果」が直接結びつかなくなった歴史を踏まえると、「UIを止めるな」の意図が深く理解できた気がします。 エンジニア目線でもUXに関わる上で必要な視点のように思えますね。 speakerdeck.com 3. デザイナーとして入社した私がフロントエンド領域から届ける体験価値とは / Three4Cさん 新卒2年目のデザイナーであるThree4Cさんが考える、デザイナーとしてのフロントエンド領域への関わり方を発表いただきました。 ご自身の経験をもとに企画・分析・開発チームと関わる上での姿勢や知識の深め方、今後のありたい姿を語っていただきました。 ご自身の現在地と置かれている状況をしっかり客観視されており、初登壇とは思えない素晴らしい発表でした。 speakerdeck.com 4. 三次元的 アクセシビリティ 解釈。価値、多様性、そして時間 / masuP9さん masuP9さんは非常に個性的に登場し、なんと その場で説明資料を作成される斬新な発表 でした! 発表は アクセシビリティ と「価値」に対するお考えが中心でしたが、その発表方法はとても衝撃的でした。 発表動画を公開できないことが残念です! また ラク スの主催イベントでのご登壇を楽しみにしています! 5. 新卒2年目のデザイナーが大事にすること / yuriさん デザイナー新卒2年目のyuriさんより、freeeさんの共通の価値観である「マジ価値」に基づき、 組織での動き方でデザイナーとして大事と考えられている点、そして B2B サービスのUIを作っていく上で大事と考えられている点を話していただきました。 司会者も話していましたが、新卒2年目とは思えない堂々としたお姿に尊敬のまなざしでした。 日々の思考と「マジ価値」への共感の積み重ねによる成果ではと感じていました。 初のLT挑戦の場として ラク スを選んでいただき、ありがとうございました。 t.co 6. TypeScriptで解説する催眠術のかけ方 / Ouji Miyaharaさん 事前に伺っていたタイトルとは一切関係ない 催眠術のかけ方 について、TypeScriptを交えながらご紹介いただきました! イベントの空気が一変したタメになるご発表、ありがとうございました! 催眠術にご興味をお持ちのデザイナーの方、ぜひこちらのスライドをご覧ください! t.co 7. Adobe XDでつくるVUIプロトタイピング / Makiko Odaniさん Makiko Odaniさんも初のLT挑戦として、 Google Home や Amazon Echo によって注目されているVUI(Voice User Interface 、音声を使って操作するインターフェース)をご紹介いただきました。 発表中には実際に作成されたプロトタイプでのデモも実施いただきました。 デザイナーの方には馴染みのある Adobe XDで手軽に作成できるそうですので、ご興味のある方はぜひお試しいただければと思います! 8. デザイナー採用のUXデザインに取り組み始めた話 / TomoyoHirokawaさん プロダクトデザイナー のTomoyoHirokawaさんには勢いでイベントにご参加いただき(直前の欠員を埋めていただき、ありがとうございました!)、初のデザイナー採用での体験設計に取り組まれた際のエピソードを語っていただきました。 初の取り組みとのことでしたが、仮説検証 → 修正 → 実行 → 振り返りのステップを着実に踏めていらっしゃる印象を受けました。 特に経営層や他職種を巻き込んでアクションが体現された点、素晴らしいです! 2周目以降の取り組みも応援しています!! 9. UIデザインの呪術性 - 覚書- / yuki _sasakiさん 呪術的思考 とUIデザインの関連について、以下の2つの補助線を用いてご説明いただきました! 一体どのようにUIデザインにつながるのか謎に感じておりましたが、最後は絶妙にUIデザインへと結び付けていただきました! 非常に深く、まるで催眠術にかかったような気分でした! クロード・ レヴィ・ストロース 『野生の思考』 ジャック・ラカン 『光点としてのまなざし』 speakerdeck.com 10. デザイナー 1 年目のわたしが考えるデザイン / uknmrさん エンジニアからデザイナーの道へと進まれたuknmrさんより、 プロダクトデザイナー 半年のご経験を踏まえてデザインとは何なのかを語っていただきました。 スライドに出てきます 『”デザイン”はデザイナーだけのものではない』 に込められた深い洞察はとても参考になりました。 あと催眠術にかかったのか、なぜか焼きそばが食べたくなりました! www.notion.so 11. 生産性が上がる文章の書き方と、デザインと文章の関係性 / ShinyaSatoさん ShinyaSatoさんからはあえてデザインをテーマとした発表ではなく、「文章の書き方」を中心にしたノウハウをご紹介いただきました。 「書く力」はデザイナーにとっても重要度が高く、とはいえ他職種よりも機会が少ないものということで、ご参加の方々からは大好評の内容でした。 在宅ワーク が本格化した今のタイミングでは、デザイナー以外のどの職種でも学ぶことばかりの貴重なお話だったと思います。 speakerdeck.com 最後にご紹介いただきましたShinyaSatoさんの個人ブログはこちらになります。 学びの宝庫です…!! https://shinya-it.com/work/sentence_of_import shinya-it.com 12. 技術とデザインの間 / takanoripさん デザイナーとエンジニアが分業体制である歴史を踏まえつつ、デザイナーが技術の仕組みを理解する重要性をご説明いただきました。 デザイナーにとって技術への理解が「なぜ必要なのか?」が明確となり、技術とデザインが切り離せないものであることを認識できた方も多いと思います。 エンジニア・デザイナー両方のご経験があるtakanoripさんの実感が強く感じられる内容でした。 speakerdeck.com 13. TypeScriptではじめるUIデザイン / kgsiさん UIの情報整理の難しさと理想を踏まえ、TypeScriptを使った方法を説明いただきました。 TypeScriptの言語仕様を踏まえ、実際にトライされたケースへの所感がとても参考になりました。 あと催眠術にかかったのか、なぜかカレーが食べたくなりました! speakerdeck.com 14. Web UIの実装で考えていることと気をつけたいこと / yamanokuさん 普段取り組まれている業務でのご経験を踏まえ、UI実装に対する深いお考えを発表いただきました。 ボタンUI1つをとっても思慮がとても深く、ぜひ全てのデザイナーの方にご覧いただきたい内容です。 LTへのご挑戦、ありがとうございました! scrapbox.io 15.3次元のカラーピッカー的なものを作ってみた / chooさん 「色は本質的に三次元(の要素で表現される)だから、二次元のUIはムリがあるのでは」という仮説に基づき、 三次元のColor Picker を紹介いただきました。 これは凄いです…私の説明よりも、ぜひこちらにアクセスいただいてお試しください!! t.co docs.google.com 16. BtoB SaaS のUIリニューアルの苦労 / 小林肇 最後は ラク スのUI開発チームの小林より、 ラク スでの17年間のサービスデザインでの歴史を語らせていただきました。 およそ半分が10年以上の歴史を持つ ラク スのサービスですが、現在に至るまで、実に多くのお客様からのお声をいただきながら成長させていただきました。 厳しいご意見からお褒めの言葉まで、お客様からの貴重なフィードバックが重要であることをあらためて考えるきっかけとなりました。 お客様にとって使いやすく、課題解決につながるサービスを目指していきたいという小林からの声で今回のLTを締めさせていただきました。 おわりに ラク ス初のUI/UXデザイナー向けのイベントでしたが、いかがでしたでしょうか? 個性的な16名の登壇者の方々、イベントを盛り上げていただいて誠にありがとうございました! また次回開催でのご登壇を楽しみにお待ちしています! さて、今月 ラク スでは フロントエンド技術への取り組み を発信させていただく予定です。 ご都合のよろしい方は是非、こちらのイベントに気軽にご参加いただけますと幸いです! rakus.connpass.com また プロジェクトマネジメント にも関わっている方には、こちらのイベントがお勧めです! アウトプットに飢えているPMの方、お待ちしています! rakus.connpass.com それではまた次回のブログで。 長文をご覧いただき、ありがとうございました! 技術広報のitoken1013でした! エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 forms.gle イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! rakus.connpass.com
こんにちは。技術推進課のt_okkanです。 最近業務でFlutterを使用することになり、少しずつですがFlutterを学習しています。 そこで今回はFlutterで、実機( iPhone )のカメラを利用してみました。カメラを使用するための プラグイン の追加から、カメラでの画像の撮影と保存、保存した画像の表示までを行います。 環境 Flutterについて アプリ作成〜プラグインのインストール 実装 使用できるカメラの取得 カメラ制御の初期化 カメラのプレビューの実装 画像の撮影と保存の実装 撮影した画像を表示する 全体のソースコード まとめ 参考 環境 使用した環境は以下になります。 Flutter 1.22.0 Visual Studio Code macOS Catalina 10.15.6 iOS 13.7 Flutterの環境構築は、公式HPに詳しく記載されています。 https://flutter.dev/docs/get-started/install 上記を参考に、 macOS + VSCode の環境を構築してください。 Flutterについて Flutterについて少しだけ紹介します。 Google が提供しているUIツールキットで、主に iOS ・ Android アプリの クロスプラットフォーム ツールとして利用されています。最近はWeb対応が進み、モバイル・ウェブ・デスクトップと真の クロスプラットフォーム を目指している注目のツールです。 Flutterは Dart という言語で実装されています。カメラなどのデ バイス 固有の機能にアクセスするには、各プラットフォームのネイティブの実装を必要とします。 Flutterではそのような各ネイティブの実装を必要とする プラグイン をPluginパッケージとして提供しています。 プラグイン は pub.dev で公開されています。 アプリ作成〜 プラグイン のインストール Flutterのプロジェクトを作成して、 camera の プラグイン と、カメラとマイクへのアクセス許可の確認までを設定します。また、画像を保存するために各プラットフォームのパスを取得する必要があるので、 path_provider と path プラグイン もインストールします。 まずはコマンドでFlutterのプロジェクトを作成します。 $ flutter create camera_app その後 プラグイン を追加します。Flutterでは pubspec.yaml ファイルに使用する プラグイン を記載します。今回は以下のようにします。 dependencies : camera : ^0.5.8+5 path_provider : ^1.6.14 path : ^1.7.0 flutter : sdk : flutter 次に、カメラを使用の許可を促すメッセージを表示するための設定を行います。 ios/Runner/Info.plist ファイルに以下を追加します。 <key> NSCameraUsageDescription </key> <string> Can I use the camera please? </string> <key> NSMicrophoneUsageDescription </key> <string> Can I use the mic please? </string> また Android で使用する場合は、 SDK の最低バージョンを 21 を指定する必要があります。 android/app/build.gradle に以下を追加します。 defaultConfig { // TODO : Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.example.camera_app" minSdkVersion 21 targetSdkVersion 29 versionCode flutterVersionCode. toInteger () versionName flutterVersionName } 準備は以上です。では実際にコードを書いていきます。 実装 まずは camera プラグイン で使用するクラスを紹介します。 CameraController 端末のカメラを制御するクラス。このクラスに制御したいカメラを渡すことで、プレビューの表示や、写真・動画の撮影を行うことができます。 CameraDescription カメラの情報(フロント・バック)を管理しているクラス。 CameraController クラスにこのクラスを渡すことで、カメラの制御を行うことができます。 CameraPreview カメラが取得している映像(静止画の連続)を画面に表示する ウィジェット 。 今回はカメラで画像を撮影・保存し表示するまでの手順を順番に説明します。最後に必要なコードを全て載せますので、先に見たい方はそちらからどうぞ。 では、以下の手順で実装していきます。 使用できるカメラの取得 カメラ制御の初期化 カメラのプレビューの実装 画像の撮影と保存の実装 撮影した画像を表示する 使用できるカメラの取得 まずは端末から使用できるカメラを取得します。 今回は取得したカメラから、背面のカメラを使用します。 // runAppが実行される前に、cameraプラグインを初期化 WidgetsFlutterBinding . ensureInitialized (); // デバイスで使用可能なカメラの一覧を取得する final cameras = await availableCameras (); // 利用可能なカメラの一覧から、指定のカメラを取得する final firstCamera = cameras.first; プラグイン の availableCameras メソッドで、端末で使用可能なカメラを配列で取得できます。 ここで取得したカメラを、表示する ウィジェット に渡すことで、カメラを使用できます。 では次に、取得したカメラを制御するための準備をします。 カメラ制御の初期化 取得したカメラの制御を行うために、 CameraController クラスを初期化する必要があります。 ここではカメラ用の画面 CameraHome の構築から、 CameraController クラスの初期化までを実装します。 // ① class CameraHome extends StatefulWidget { final CameraDescription camera; const CameraHome ({ Key key, @required this .camera}) : super (key : key); @override State < StatefulWidget > createState () => CameraHomeState (); } class CameraHomeState extends State < CameraHome > { // デバイスのカメラを制御するコントローラ CameraController _cameraController; // コントローラーに設定されたカメラを初期化する関数 Future < void > _initializeCameraController; @override void initState () { super . initState (); // ② // コントローラを初期化 _cameraController = CameraController ( // 使用するカメラをコントローラに設定 widget.camera, // 使用する解像度を設定 // low : 352x288 on iOS, 240p (320x240) on Android // medium : 480p (640x480 on iOS, 720x480 on Android) // high : 720p (1280x720) // veryHigh : 1080p (1920x1080) // ultraHigh : 2160p (3840x2160) // max : 利用可能な最大の解像度 ResolutionPreset .max); // ③ // コントローラーに設定されたカメラを初期化 _initializeCameraController = _cameraController. initialize (); } // ④ @override void dispose () { // ウィジェットが破棄されたタイミングで、カメラのコントローラを破棄する _cameraController. dispose (); super . dispose (); } @override Widget build ( BuildContext context) {} } カメラを起動する画面を構築する カメラを起動する画面として StatefulWidget を使用し、画面を構築します。 コンストラクター で CameraDescription クラスを受け取り、使用するカメラを設定します。 カメラコントローラーを初期化します CameraController の コンストラクター の第一引数に CameraDescription を指定し、制御対象のカメラを設定します。第二引数に、列挙型 ResolutionPreset からカメラの解像度を指定します。今回は使用できる最大の解像度を指定します。 カメラコントローラーに設定されたカメラを初期化します CameraController クラスに設定された端末のカメラを、 initialize メソッドを実行して初期化します。 initialize メソッドは非同期処理を行うメソッドのため、 Feature オブジェクトを返します。 ウィジェット 破棄のメソッドの追加 StatefulWidget の dispose メソッド内で、 CameraController クラスの dispose メソッドを呼び出します。 ウィジェット が破棄されたタイミングで、 CameraController クラスも破棄する用になります。 以上がカメラの初期設定になります。次は、カメラが取得した画像を画面に表示する プレビュー機能 を実装します。 カメラのプレビューの実装 camera プラグイン の CameraPreview クラスを利用して、カメラが取得した画像をプレビューとして画面に表示します。先ほどの CameraHomeState クラスの、 build メソッドに以下のコードを追加します。 @override Widget build ( BuildContext context) { return Scaffold ( appBar : AppBar ( title : const Text ( '' ), ), // FutureBuilderを実装 body : FutureBuilder < void > ( future : _initializeCameraController, builder : (context, snapshot) { if (snapshot.connectionState == ConnectionState .done) { // カメラの初期化が完了したら、プレビューを表示 return CameraPreview (_cameraController); } else { // カメラの初期化中はインジケーターを表示 return const Center (child : CircularProgressIndicator ()); } }, ), ); } FutureBuilder クラスは、非同期で画面を構築できます。引数の future で非同期処理を設定し、 builder で非同期処理が完了した場合と完了していない場合の処理を実装します。今回は、非同期処理としてカメラの初期化を行う initialize メソッドを実行し、カメラの初期化が完了するとプレビュー画面を、完了していない場合はインジケーターを表示するようにします。 以上がカメラのプレビューを表示するための実装です。 画像の撮影と保存の実装 次は、カメラのシャッターボタンを実装し、画像の撮影と保存を行います。カメラのシャッターボタンは FloatingActionButton で実装します。 FloatingActionButton の onPressed 引数でボタンが押下された場合の実装をします。 @override Widget build ( BuildContext context) { return Scaffold ( appBar : AppBar ( title : const Text ( '' ), ), body : FutureBuilder < void > ( // 省略 ), floatingActionButton : FloatingActionButton ( child : const Icon ( Icons .camera_alt), // ボタンが押下された際の処理 onPressed : () async { try { // ①画像を保存するパスを作成する final path = join ( ( await getApplicationDocumentsDirectory ()).path, ' ${DateTime.now()} .png' , ); // ②カメラで画像を撮影する await _cameraController. takePicture (path); // ③画像を表示する画面に遷移 Navigator . push ( context, MaterialPageRoute ( builder : (context) => CameraDisplay (imgPath : path), ), ); } catch (e) { print (e); } }, ), } 画像を保存するパスを作成 path プラグイン の join メソッドを使用して、画像を保存するパスを作成します。 path_provider プラグイン の getApplicationDocumentsDirectory メソッドを使用することで、アプリ専用の ディレクト リを取得できます。 写真を撮影し、作成したパスに保存 CameraController クラスの takePicture メソッドで画像の撮影を行うことができます。引数に指定したパスに画像を保存できます。 撮影した画像を表示する画面に遷移 画像を表示する画面は次で実装します。ここでは、画面の コンストラクター に先ほど画像を保存したパスを渡します。 以上が画像の撮影から保存までの実装です。 ここまでの実装で、以下のようなカメラから取得した画像を表示し、撮影用のボタンを表示する画面が出来上がります。 カメラ撮影画面 最後に撮影ボタンを押下後、保存した画像を表示する画面を作成します。 撮影した画像を表示する 最後に、撮影し保存した画像を表示する画面を作成します。 画像を表示するためには、 Image ウィジェット を使用します。 コンストラクター に表示したい画像のパスを指定することで画像を表示できます。 class CameraDisplay extends StatelessWidget { // 表示する画像のパス final String imgPath; // 画面のコンストラクタ const CameraDisplay ({ Key key, this .imgPath}) : super (key : key); @override Widget build ( BuildContext context) { return Scaffold ( appBar : AppBar ( title : const Text ( 'Picture' ), ), body : Column ( // Imageウィジェットで画像を表示する children : [ Expanded (child : Image . file ( File (imgPath)))], ) ); } } これで以下のような画像を表示する画面を作成できます。 画像表示 実装は以上になります。全体の ソースコード は以下になります。 全体の ソースコード 全体のソースコード import 'dart:async' ; import 'dart:io' ; import 'package:camera/camera.dart' ; import 'package:flutter/material.dart' ; import 'package:path/path.dart' show join; import 'package:path_provider/path_provider.dart' ; Future < void > main () async { // runAppが実行される前に、cameraプラグインを初期化 WidgetsFlutterBinding . ensureInitialized (); // デバイスで使用可能なカメラの一覧を取得する final cameras = await availableCameras (); // 利用可能なカメラの一覧から、指定のカメラを取得する final firstCamera = cameras.first; runApp ( MyApp (camera : firstCamera)); } class MyApp extends StatelessWidget { final CameraDescription camera; const MyApp ({ Key key, @required this .camera}) : super (key : key); // This widget is the root of your application. @override Widget build ( BuildContext context) { return MaterialApp ( title : 'Flutter Demo' , theme : ThemeData ( primarySwatch : Colors .blue, visualDensity : VisualDensity .adaptivePlatformDensity, ), home : MyHomePage ( camera : camera, ), ); } } class MyHomePage extends StatelessWidget { final CameraDescription camera; const MyHomePage ({ Key key, @required this .camera}) : super (key : key); @override Widget build ( BuildContext context) { return Scaffold ( appBar : AppBar ( title : const Text ( 'Camera Example' ), ), body : Wrap ( children : [ RaisedButton ( child : const Text ( 'Camera' ), onPressed : () { Navigator . push (context, MaterialPageRoute (builder : (context) { return CameraHome (camera : camera,); })); }), ], ), ); } } class CameraHome extends StatefulWidget { final CameraDescription camera; const CameraHome ({ Key key, @required this .camera}) : super (key : key); @override State < StatefulWidget > createState () => CameraHomeState (); } class CameraHomeState extends State < CameraHome > { // デバイスのカメラを制御するコントローラ CameraController _cameraController; // コントローラーに設定されたカメラを初期化する関数 Future < void > _initializeCameraController; @override void initState () { super . initState (); // コントローラを初期化 _cameraController = CameraController ( // 使用するカメラをコントローラに設定 widget.camera, // 使用する解像度を設定 // low : 352x288 on iOS, 240p (320x240) on Android // medium : 480p (640x480 on iOS, 720x480 on Android) // high : 720p (1280x720) // veryHigh : 1080p (1920x1080) // ultraHigh : 2160p (3840x2160) // max : 利用可能な最大の解像度 ResolutionPreset .max); // コントローラーに設定されたカメラを初期化 _initializeCameraController = _cameraController. initialize (); } @override void dispose () { // ウィジェットが破棄されたタイミングで、カメラのコントローラを破棄する _cameraController. dispose (); super . dispose (); } @override Widget build ( BuildContext context) { return Scaffold ( appBar : AppBar ( title : const Text ( '' ), ), // FutureBuilderを実装 body : FutureBuilder < void > ( future : _initializeCameraController, builder : (context, snapshot) { if (snapshot.connectionState == ConnectionState .done) { // カメラの初期化が完了したら、プレビューを表示 return CameraPreview (_cameraController); } else { // カメラの初期化中はインジケーターを表示 return const Center (child : CircularProgressIndicator ()); } }, ), floatingActionButton : FloatingActionButton ( child : const Icon ( Icons .camera_alt), // ボタンが押下された際の処理 onPressed : () async { try { // 画像を保存するパスを作成する final path = join ( ( await getApplicationDocumentsDirectory ()).path, ' ${DateTime.now()} .png' , ); // カメラで画像を撮影する await _cameraController. takePicture (path); // 画像を表示する画面に遷移 Navigator . push ( context, MaterialPageRoute ( builder : (context) => CameraDisplay (imgPath : path), ), ); } catch (e) { print (e); } }, ), ); } } class CameraDisplay extends StatelessWidget { // 表示する画像のパス final String imgPath; // 画面のコンストラクタ const CameraDisplay ({ Key key, this .imgPath}) : super (key : key); @override Widget build ( BuildContext context) { return Scaffold ( appBar : AppBar ( title : const Text ( 'Picture' ), ), body : Column ( // Imageウィジェットで画像を表示する children : [ Expanded (child : Image . file ( File (imgPath)))], ) ); } } まとめ Flutterでカメラ機能を実装してみました。以前、 Swiftでカメラ機能を実装したこと があるのですが、その時と比べ必要なクラスが プラグイン ですでに実装されているので、シンプルに実装することができました。 またFlutterの特徴として宣言的にUIを構築できるので、楽に開発できます。 今回はシンプルなカメラ機能だけの紹介でしたが、 GitHub にインカメの切り替えのできるコードを置いています。興味があれば確認してみてください。 参考 Take a picture using the camera | Flutter エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 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です。 私が担当しているサービスでは、 RDBMS に PostgreSQL を利用しています。今回は業務で行った デッドロック の調査で知った、 PostgreSQL の仕様に関して書いていきます。 ここでは デッドロック や パーティショニング といった用語が登場しますが、今回これらの説明は割愛します。 パーティショニングについてご存じでない方は、まずはこちらの記事をお読みください。 tech-blog.rakus.co.jp qiita.com なお、この記事の内容は PostgreSQL 12.3で確認したものです。 テーブルのロック 子テーブルの作成 パーティションの追加 デッドロックが起こりそうな例 前提 デッドロックの発生 対策 まとめ テーブルのロック PostgreSQL のロックについては、以下のドキュメントに詳しく書かれています。 www.postgresql.jp これによると、テーブルレベルのロックだけで8種類が存在します。 LOCK TABLE 文でロックが獲得されるのは言うまでもありませんが、INSERT文やUPDATE文、SELECT文ですら対象のテーブルのロックを獲得します。 さらに、実は以下のような操作でもテーブルのロックが獲得されます。 子テーブルの作成 PostgreSQL の継承機能を利用し子テーブルを作成すると、親テーブルのロックが獲得されます。例として、 psql コマンドで以下のような SQL を実行してみます。 partition_test=# CREATE TABLE parent (); partition_test=# BEGIN; BEGIN partition_test=# CREATE TABLE child () INHERITS (parent); CREATE TABLE ここで別のコンソールからロックの状態を確認してみましょう。 PostgreSQL の場合、 pg_locks を参照することでロック状態が確認できます。結果を見てみると、 parent テーブルのSHARE UPDATE EXCLUSIVEロックが獲得されているのがわかります。 partition_test=# SELECT d.datname, l.locktype, l.relation::regclass, l.mode FROM pg_locks l LEFT JOIN pg_database d ON l.database = d.oid WHERE l.pid != pg_backend_pid(); datname | locktype | relation | mode ----------------+---------------+----------+-------------------------- | virtualxid | | ExclusiveLock partition_test | object | | AccessShareLock partition_test | relation | 16428 | AccessExclusiveLock | transactionid | | ExclusiveLock partition_test | relation | parent | ShareUpdateExclusiveLock (5 行) パーティション の追加 PostgreSQL 10以降では宣言的パーティショニングがサポートされていますが、 パーティション テーブルに パーティション を追加する際に、 パーティション テーブルのロックが獲得されます。これも psql コマンドで確認してみます。 partition_test=# CREATE TABLE partition (id integer) PARTITION BY LIST (id); CREATE TABLE partition_test=# BEGIN; BEGIN partition_test=# CREATE TABLE partition_1 PARTITION OF partition FOR VALUES IN (1); CREATE TABLE 別コンソールの psql で確認すると、 パーティション テーブルの ACCESS EXCLUSIVEロックが獲得されているのが確認できます。 partition_test=# SELECT d.datname, l.locktype, l.relation::regclass, l.mode FROM pg_locks l LEFT JOIN pg_database d ON l.database = d.oid WHERE l.pid != pg_backend_pid(); datname | locktype | relation | mode ----------------+---------------+-----------+--------------------- | virtualxid | | ExclusiveLock partition_test | object | | AccessShareLock partition_test | relation | 16434 | AccessExclusiveLock partition_test | relation | partition | AccessExclusiveLock | transactionid | | ExclusiveLock (5 行) デッドロック が起こりそうな例 上記のように PostgreSQL では パーティション の追加などの操作でもロックが発生します。こうした挙動を把握、意識していないと、うっかり デッドロック を引き起こしてしまう場合があります。 前提 あるアプリケーションにユーザとグループが存在し、一人のユーザは複数のグループに所属することができるとします。 これをデータベース上で表現しようとすると、ユーザとグループは多対多の関係なので、ユーザテーブル、グループテーブルと、この2つを結ぶ中間テーブルを作成することになると思います。 ユーザとグループのER図 この中間テーブルはレコード数がとても多くなりそうなので、グループごとにパーティショニングすることにしました。グループテーブルにレコードを挿入するたびに、トリガーで新しい パーティション を追加します。 以上を SQL で表現すると、以下のようになります。 CREATE TABLE users ( id serial PRIMARY KEY, name text ); CREATE TABLE groups ( id serial PRIMARY KEY, name text ); CREATE TABLE user_group ( user_id integer , group_id integer ) PARTITION BY LIST ( group_id ); CREATE FUNCTION groups_insert_trigger_func() RETURNS trigger AS $$ BEGIN EXECUTE ' CREATE TABLE user_group_g ' || NEW.id || ' PARTITION OF user_group FOR VALUES IN ( ' || NEW.id || ' ) ' ; RETURN NEW; END ; $$ LANGUAGE plpgsql; CREATE TRIGGER groups_insert_trigger AFTER INSERT ON groups FOR EACH ROW EXECUTE FUNCTION groups_insert_trigger_func(); デッドロック の発生 上記のようなテーブル、トリガーを作成した状態で、2つの トランザクション A, Bで以下のような順序で操作を行うと デッドロック が発生します。 トランザクション SQL A LOCK TABLE user_group; B INSERT INTO groups (name) VALUES ('group 1'); A LOCK TABLE groups; トランザクション BでINSERT文を実行すると、 groupsテーブルをロックし行挿入 トリガーでuser_groupの パーティション を作成 user_groupのロック待ち となり、その後で トランザクション Aがgroupsのロックを獲得しようとすると デッドロック 状態になってしまいます。 トランザクション Bでは見た目上はgroupsテーブルに挿入しているだけなので、これで デッドロック が発生するとは気づきにくそうです。 ここでは例として宣言的パーティショニングを使ったケースを挙げていますが、子テーブルの作成でもロックが発生するため、継承によるパーティショニングでも同様に デッドロック が起こり得ます。ただし宣言的パーティショニングの場合、 トランザクション Bが獲得しようとするuser_groupのロックはもっとも厳しい ACCESS EXCLUSIVEロックなので、 LOCK TABLE user_group; を SELECT * FROM user_group; などに置き換えても同じく デッドロック してしまいます。 対策 このような デッドロック を回避する方法の一つは、テーブルのロック順序に気をつけることです。 上の例でいうと、 トランザクション Aでuser_groupよりも先にgroupsをロックすると、 デッドロック が発生しなくなります。 まとめ 子テーブルの作成( CREATE TABLE ... INHERITS ... )の際、親テーブルがSHARE UPDATE EXCLUSIVEロックされる パーティション の作成( CREATE TABLE ... PARTITION OF ... )の際、大元の パーティション テーブルが ACCESS EXCLUSIVEロックされる エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 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
こんにちは!akiponxといいます。 さて、今回のブログはメールのあれこれについて書きます。 メールというと、いつでも送信できて、いつでも受信できる。 これが当たり前ですよね。 まれにメールが届かない。という声を聴くことがあります。 今回はメールが届かない原因について触れてみたいと思います。 まずはRakusのブログではメール関連の記事があまりないので今回は仕組みの部分。イメージを付けていただければと思います。 当たり前にメールの送受信ができる裏側で何が起きているのか見ていきましょう! 1. メールとは ①手紙を作る ②郵便配達の人が頑張って宛先の住所に手紙を届けます。 ③家のポストを確認して手紙を受け取る 2. メールの送信・受信 ①手紙を作る部分:メールを作成して送信ボタンをクリック ②郵便配達の人が頑張る部分:メール送信サーバが受信サーバへメール送信 ③郵便ポストにメールを取りに行く部分:受信者がメーラで受信メールサーバに届いているか確認しに行く メール送信 メール受信 全部をマージするとこんな感じ 3. メールが届かない原因 スパムメールとは スパムメール対策 4. SPF 5. DKIM 6. DMARC 7. まとめ 1. メールとは メールとは手紙のやり取りと似たようなものです。 画像①を使って説明します。 ①手紙を作る 封筒に下記を書く 宛先の住所 差出人の名前 手紙の本文を書く ポストへ投函 ②郵便配達の人が頑張って宛先の住所に手紙を届けます。 ③家のポストを確認して手紙を受け取る メールの送信・受信もイメージは同じです。 2. メールの送信・受信 こちらについてもメールの仕組みで使った図と見比べて説明していこうと思います。 ①手紙を作る部分:メールを作成して送信ボタンをクリック メーラ( gmail など)でメールを作成 (図1の封筒にあたる部分) 宛先メールアドレスを記載 (例: hoge @ example.com ) 自動で入力されていることが多いですが、差出人アドレスを記載 (例:akiponx@rakus.co.jp) 本文を入力 (図1の本文にあたる部分) 送信ボタンを押す (図1のポストに投函にあたる部分) ②郵便配達の人が頑張る部分:メール送信サーバが受信サーバへメール送信 この時にメール送信サーバが行っている処理をざっくり説明。 宛先メールアドレスの ドメイン ( example.com )を名前解決してメール受信サーバのインターネット上の住所である IPアドレス を取得。 メール受信サーバへメールを送信 ③郵便ポストにメールを取りに行く部分:受信者がメーラで受信メールサーバに届いているか確認しに行く メール受信はメーラによってメール受信サーバにメールが届いているか確認しに行く作業。 さて。ざっくりとした流れは説明した通りですが、送信・受信についてもう少し細かく見ていきます。 メール送信 ①の部分を切り取った画像がこちら。さらに処理の内容を記載しています。後で全部マージします。 1-1. 差出人アドレスがメールを送信していいかどうか認証する( SMTP 認証と呼ばれているもの。) 1-2. 認証が通れば送信する準備に入る 続いて②を切り取った画像。 ②の中身を細かく分割するとこんな感じです。では説明していきましょう。 2-1. どこに送ればいいのか確認するため、 送信先 メールアドレスの ドメイン 部分を確認。 2-2. 確認した ドメイン の住所がどこなのか DNS レコードにMXレコードを問合せ 2-3. DNS から 送信先 アドレスの住所をもらう。 2-4. DNS からもらった住所宛にメールを送信する。 ※MXレコードとはメールをどこに送ればいいのかを教えるための、 DNS レコードの一種です。 メール受信 さて、続いてメール受信ですが、メールが受信できるようになるまでもいくつか過程があります。 メール送信の段階で②の処理が全て終わっているように思われますが、実はまだ終わっていません。 続きはメール受信サーバ側で処理がされているためこちらに続きを記載します。 2-5. 届いたメールの差出人アドレスの ドメイン を確認 2-6. DNS に SPF (または DKIM の公開鍵)を問合せ 2-7. SPF (または DKIM の公開鍵)の評価を実施 ※1 2-8. 問題なければメールを受け入れる処理を実施 2-9. メー ルボックス (イメージ的にはポスト)にメールを置く。 3-1. メーラが POP3 or IMAP でメールがあるかどうか確認しに行く ※2 3-2. 新着メールが存在していればダウンロードして表示する。 ※1  SPF (または DKIM )の評価によってはメールが拒否されることがある。 ※2  POP3 や IMAP はメールを受信する用の 通信プロトコル 。    また、POP3SやIMAPSといった SSL証明書 を利用した プロトコル もある。 SPF や DKIM については後程説明します。 全部をマージするとこんな感じ 3. メールが届かない原因 さて、メールの送信・受信についてはある程度説明できたかと思います。 続いては当たり前に届くはずのメールが届かないことがある原因ついて、考えられることを書いていきます。 スパムメール 判定がされている メールアドレスが間違っている etc... メールが届かない原因として スパムメール と評価されてしまい、拒否されているケースが多いです。 スパムメール とは スパムメール とは迷惑メールの一つで、主に広告や宣伝、ウィルス付きメールなどに使われることが多いです。 広告や宣伝に使われるならいいんじゃない?と思う方もいるかと思いますが、そうではありません。 主に詐欺やデータを取る目的で送られていることが多いです。 そもそもメール配信などはオプトイン(メルマガ送ってもいいですよ。という意志表明)が無いと送ってはいけないのがルール。 そのオプトインがない人にメールを大量に送る。迷惑ですよね。 スパムメール 対策 スパムメール を受信しないようにする対策として、 スパムフィルター を実装する 逆引きが設定されていない IPアドレス からは受信しない SPF チェックが通らないものを受信しない DKIM 署名がないものを受信しない ブラックリスト に登録されている ドメイン ( IPアドレス )からのメールは拒否する などがありますが、「メールが届かない!」ということは大きなクレームにもつながるため、 なかなか SPF や DKIM を設定していないメールは受信しない。という強気な対応に踏み切れないところがあります。 そんなこんなで続いては SPF とか何か、 DKIM とは何か。について書いていきます。 4. SPF SPF とは、 DNS のTXTレコードにあたります。 送信元 ドメイン の名前解決を実施しTXTレコードに記載がある SPF の値を確認します。 例を出すと基本的にこんな感じの SPF が設定されています。 # SPFの例 rakus.co.jp descriptive text "v=spf1 include:spf.rakus.co.jp ip4:xxx.xxx.xxx.xxx ~all" SPF の値には IPアドレス などが記載されており、送信サーバの IPアドレス が SPF に記されていればOK、記載されていなければ基本的にNGとなります。 受信メールサーバ側の設定によりますが、この SPF のチェックを通らないとメールを受信しない。という設定もできます。 もし「メールが届かない…orz」となった方は SPF をチェックしてみるのもいいかもしれないですね。 5. DKIM DKIM とは、これも DNS のTXTレコードを利用した技術です。 SPF より少し複雑。 ざっくりとした流れとしては メール送信サーバがメールヘッダーに 電子署名 を実施 メール受信サーバが DNS サーバに問い合わせを実施し、公開鍵を入手 入手した公開鍵で 電子署名 が複合化し、認証を実施。 こんな感じで DKIM は評価されます。 DKIM 署名を実装する方法などなどは今回は書きません。 6. DMARC DMARCとは SPF と DKIM の合わせ技です。 基本的にメール受信については SPF が設定されていなくても、 DKIM が設定されていなくても、 そのメールを受信するかどうかは受信メールサーバ側が決めています。 DMARCを設定することによって、メール送信者側が挙動を指定できます。 例えば、 SPF が通らなければメールを拒否(迷惑メールに振り分け)する DKIM の署名がなければメールを拒否(迷惑メールに振り分け)する 上記の設定をすることによって、自分の ドメイン を語ったメールを受信させないようにできます。 また、DMARCを設定することによって、自分の ドメイン を語ったメール( SPF や DKIM の評価が通らなかったもの)が何通送られているのか というレポートメールを受け取ることができます。 これを受け取ったから何ができるのかといわれると難しいですが、DMARCの運用を考えていかないといけないですね。 7. まとめ メールが届かない原因は今回記載した以外にもありますが、 本記事に記載されている内容を踏まえてメールが届かなった原因の調査や対策をしてみていただければと思います。 大手フリーメールの会社ではセキュリティが厳しくなってきており、迷惑メールフォルダに振り分けられてしまうこともしばしばあります。 迷惑メールフォルダに振り分けられないように、 SPF ・ DKIM の対策をしっかりしていきましょう! そのうち別のメール関連の記事も書きます。 参考にさせていただいたページ DMARK エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 forms.gle イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! rakus.connpass.com
はじめに 技術広報のitoken1013です。 いつも ラク スのエンジニアブログのご購読、そしてエンジニアイベントへのご参加、ありがとうございます! 今回は9/16(水)に行われた ラク スMeetup『持続可能な大規模 SaaS 企業の開発戦略』を紹介させていただきます。 イベントにご参加いただきました方も初めてご覧になる方も、4名のエンジニアの発表内容をご覧いただけますと幸いです! rakus.connpass.com イベントテーマ概要 本日は以下の計5つの「中小企業を強くする」ための大規模 クラウド サービスに関わるアプリケーションエンジニア、インフラエンジニアから発表をさせていただきました。 メールディーラー 楽楽販売 配配メール Curumeru チャットディーラー 上記どのプロダクトもおかげさまで拡大を続けていますが、裏側ではエンジニアがプロダクトを持続的に開発・運用するために日々工夫を重ねています。 今回は ラク スの開発組織に根付く 費用対効果を考え、やるべき最適な対策をとる文化 をアウトプットすべく、各プロダクトでの取り組みを発信させていただきました。 発表の紹介 長期的に考える技術的負債マネジメント戦略 「負債を返すチャンスは来るので、そのチャンスを逃さないために 諦めずに負債の局所化や新しく作り込まないなど準備をしておくのが大事 」 今回のMeetupのコンセプトを象徴するようなコメントですが、トップバッターであるやなせ(メールディーラー担当)から出てきた言葉でした。 これまでのMeetupでも度々登場してきたメールディーラーは歴史が深く、いわゆる「技術的負債」と向き合う機会も度々あります。 今回はあらためて技術的負債とは何なのか、そしてどのように立ち向うべきかを、やなせの経験と理論を踏まえて語らせていただきました。 会計的な負債と比較した場合の見えにくさ、またビジネスの側面も踏まえての方法論は多くのエンジニアに参考にしていただけると思います。 speakerdeck.com DB容量肥大化への取り組み 次の発表は 「データの肥大化問題」 がテーマです。 Webデータベースである楽楽販売を担当する山内より、その製品の特長上、深刻となったデータ量の問題に 「Cloudian HyperStore」 というオブジェクトストレージを導入して対処したエピソードを発表させていただきました。 アプリ・インフラどちらにも多大な影響を与える一大プロジェクトでしたが、今後も持続的に楽楽販売を利用いただける環境を作り上げるため、山内をはじめとするチームは主に3つの課題と対峙しました。 データ量に関する問題は長くプロダクトを運用していく上で避けられない問題です。 現在同じ問題を抱える方には、今回の発表からヒントを得ていただけると幸いです。 speakerdeck.com 4年間の レガシーシステム 運用から学んだトラブル対策の取り組み方 運用の スペシャ リストとして配配メールとCurumeruの2プロダクトに関わる西尾より、運用面からプロダクトを支えるためのプ ラク ティスを発表させていただきました。 どんなに緻密に作ったプロダクトでもトラブルを全て未然に防ぐことは難しいものです。 特に西尾が関わるプロダクトはメール配信サービスであるが故、以下の3フェーズそれぞれでプロダクト固有の問題、またメール プロトコル や業界固有の問題を抱えています。 メールを「作る」 メールを「送る」 メールを「届く」 今回の発表では未然にトラブルを防ぐことに全力を注ぐのではなく、起こってしまったトラブルに如何に迅速に対応するかを語っています。 サービスの外側・内側それぞれで学びとなるプ ラク ティスが提供できる内容です。 speakerdeck.com Infrastructure as Codeによるインフラ構築の自動化により、オペレーションミスを防ぐ仕組みづくり 最後にインフラエンジニアである山村よりインフラエンジニア目線での技術的負債とは何か、そしてInfrastructure as Codeを中心とした技術的負債への対処方法を発表させていただきました。 チャットディーラーの運用・保守をメインに担当する山村ですが、着任より現在に至るまで様々な技術的負債に着目する中、特に「共有不足で実装内容の ブラックボックス 化による負債」には強い問題意識を持っていました。 解決策として編み出したプ ラク ティスは属人性の解消のみならず、いまや作業 工数 としても大きく寄与するソリューションとなっています。 インフラ・アプリに関わらず、どのエンジニアにも是非ご参考にしていただきたいプ ラク ティスばかりです。 speakerdeck.com おわりに 持続可能な大規模 SaaS 企業の開発戦略、いかがでしたでしょうか? ラク スは多様な SaaS を長く開発し続けることで、 費用対効果を考え、やるべき最適な対策をとるための戦略と思考 が根付いています。 今回の4名のエンジニアの発表より、その一端を感じ取っていただければ幸いです。 次回はそんな多様な SaaS から フロントエンド に特化したテーマで、10月にMeetupを開催予定です! Vue.js、Typescriptなどのモダンなフロントエンドにご興味のある方は、ぜひconnpassをウォッチいただければと思います! rakus.connpass.com rakus.connpass.com
こんにちは。 やなせたかし です。 今回は「繰り返し」について掘り下げてみようと思います。 PHP に限ったことではないですが、繰り返しはプログラミングでは基本的な操作です。たとえば、 while for など、処理を繰り返す構文です。その中でも利用頻度が高いのは for でしょうか?サンプルコードでも配列を繰り返す時に使われたりと目にすることが多いと思います。 PHP であれば、 foreach という構文もあります。これも繰り返しのようです。 この中でも、 for と foreach どちらを使うのか?が議論されたり、どちらのほうが性能がいいだの悪いだの、過去にも比較されてきました。2020年の現在、(あくまで PHP 上での)両者にどんな差があるのでしょうか。 forを眺める 以下のコードを例に考えてみます。何の変哲もない PHP コードです。 <?php $ a = [ 1 , 2 , 3 ] ; $ b = 0 ; for ( $ i = 0 ; $ i < count ( $ a ) ; $ i ++ ){ $ b += $ a [ $ i ] ; } 配列の和を計算しています。この for の終了条件は $i < count($a) です。配列の大きさまで $i が増加すると終了します。 とくに何かあるわけではないコードのようにも見えますが、よく見るとコードの関心ごとがバラバラなことに気づきます。 たとえば、 $i はただの変数で、配列そのものとの関係はない ループ内でアクセスしている配列が $a であるかはループの条件に絡まない 和をとる配列は $a でなくても $aa にしても構わない そう、 for はただのループで、「配列の操作」とは関係がないのです。 この「関係がない」というのは、コードを見てもわかることですが、どこまで関係がないのか? 改めて for を振り返ってみましょう。 まず、 for という構文について。 さきほども書いた通り、ループを表現する構文です。では構文としてはどのようなルールになっているでしょうか? ルールを正確に知るため、 PHP のパーザーを呼んで文法そのものを見てみましょう。 PHP のコードを理解している部分ですから、正確な文法がわかるはずです。 PHP そのもののソースは、以下から手に入ります。 https://github.com/php/php-src ということで PHP のパーザーでは、 for の構文は以下のように定義されています。 %token T_FOR "for (T_FOR)" statement: | T_FOR '(' for_exprs ';' for_exprs ';' for_exprs ')' for_statement { $$ = zend_ast_create(ZEND_AST_FOR, $3 , $5 , $7 , $9 ); }; for() の中に for_exprs というのが並んでいるのがわかります。そして、最後に for_statement というものがあります。 この for_exprs というものは何を表しているでしょうか。これは何もない(NULLと表します)か、カンマ区切りの expr のリストのようです。 ちなみに、 expr は式を表します。メソッド呼び出しのほか、 $a だけでも式ですし、 $i = 0 なども式にあたります。 そして zend_ast_create というものを呼び出しています。ここで ZEND_AST_FOR というものを渡しているので、ここで for文 を作っているようです。 $3,$5... はこのパーザーにマッチした要素の位置っぽいですね。 正規表現 のマッチっぽくも見えます。 さらに追っていくと、 PHP の for の定義が見えてきます。 void zend_compile_for(zend_ast *ast) { zend_ast *init_ast = ast->child[ 0 ]; // ← 1つ目のfor_exprs zend_ast *cond_ast = ast->child[ 1 ]; // ← 2つ目のfor_exprs zend_ast *loop_ast = ast->child[ 2 ]; // ← 3つ目のfor_exprs zend_ast *stmt_ast = ast->child[ 3 ]; // ← for_statement //.... } ということで、パーザーは for をこんな風に理解しています。 <?php for ( 開始状態の式; 終了条件の式; ループ式 ) for内部の文 となります。当たり前のことですが、調べてみるとしっかり書かれているものです。では、 for の命令を確認してみましょう。 以下のソースのopcodeをダンプします。 opcodeとは、 PHP で書かれたコードを コンパイル して得られるもので、Zendエンジンで直接実行されます。 <?php $ a = [ 1 , 2 , 3 ] ; $ b = 0 ; for ( $ i = 0 ; $ v = count ( $ a ) ; $ i ++ ) { $ b += $ a [ $ i ] ; } すると、以下のようなダンプが出力されます。 L0 (3): EXT_STMT L1 (3): ASSIGN CV0($a) array(...) L2 (4): EXT_STMT L3 (4): ASSIGN CV1($b) int(0) L4 (6): EXT_STMT L5 (6): ASSIGN CV2($i) int(0) L6 (6): JMP L12 L7 (7): EXT_STMT L8 (7): T6 = FETCH_DIM_R CV0($a) CV2($i) L9 (7): ASSIGN_ADD CV1($b) T6 L10 (6): T8 = POST_INC CV2($i) L11 (6): FREE T8 L12 (6): T9 = COUNT CV0($a) L13 (6): T10 = IS_SMALLER CV2($i) T9 L14 (6): EXT_STMT L15 (6): JMPNZ T10 L7 L16 (10): RETURN int(1) なんだか アセンブリ っぽいですね。 処理を見ていきましょう。L3までは for と関係ない処理です。ということで、それ以降を確認していきましょう。ループに関する個所は以下のような感じです。わりとイメージ通りの命令になっているのではないでしょうか。 行 処理 L5 $i に 0 を アサイ ン L6 L12 に飛ぶ L7 - L9 $b += $a[$i] の処理 L10 - L11 $i++ L12 T9 に count($a) の結果を保存 L13 T10 に $i < T9 の結果を保存 L15 T10 が 0 でなければ L7に飛ぶ さきほどまとめた文法と照らし合わせると、それぞれの式・文が評価されている命令の並びがわかります。初めに終了条件の式に飛ぶ以外は、普通にループしているだけですね。 要素 行 開始状態の式 L5 for内部の文 L7 - L9 ループ式 L10 - L11 終了条件の式 L12 - L15 ここで注目したいのが、 for内部の文 です。命令の L8 を見てみましょう。配列から値を取り出す処理です。 L8 (7): T6 = FETCH_DIM_R CV0($a) CV2($i) ここで使われている命令は、 FETCH_DIM_R となっています。これは配列にインデックスアクセスするときの命令です。当たり前のことを確認しているようですが、結構大事なところです。 このように実際の命令まで追っていくと、やはり for というのは「配列の操作」とは直接関係のない構文である、と言えるでしょう。 foreachを眺める 続いて、 foreach を見てみましょう。ソースは以下の通りです。やりたいことは変わりません。 <?php $ a = [ 1 , 2 , 3 ] ; $ b = 0 ; foreach ( $ a as $ v ) { $ b += $ v ; } では、まずは foreach の文法を確認しましょう。さきほどと同じように、 PHP のパーザーにある定義を確認します。 %token T_FOREACH "foreach (T_FOREACH)" statement: | T_FOREACH '(' expr T_AS foreach_variable ')' foreach_statement { $$ = zend_ast_create(ZEND_AST_FOREACH, $3 , $5 , NULL, $7 ); } | T_FOREACH '(' expr T_AS foreach_variable T_DOUBLE_ARROW foreach_variable ')' foreach_statement { $$ = zend_ast_create(ZEND_AST_FOREACH, $3 , $7 , $5 , $9 ); } どうやら foreach は2つの文法が定義されているようです。 上側は配列の value だけを、下側は T_DOUBLE_ARROW とあるように配列の key, value 両方使用するタイプですね。 ここで出てくるそれぞれの要素も、なんとなくイメージできそうです。 そして、さらに追いかけると、 foreach の定義にたどり着きます。 void zend_compile_foreach(zend_ast *ast) /* {{{ */ { zend_ast *expr_ast = ast->child[ 0 ]; // foreachで回すもの zend_ast *value_ast = ast->child[ 1 ]; // foreachで回される「値」 zend_ast *key_ast = ast->child[ 2 ]; // foreachで回される「キー」 zend_ast *stmt_ast = ast->child[ 3 ]; // 逐次処理される文 //・・・ } PHP パーザーはこのように理解しています。余談ですが、読み進めていくと foreach は「値」が参照かどうかで処理を切り替えている、ということもここから読み取れます。 今回は値を渡した時の挙動に絞って見ていきましょう。 ということで、実行されている様子を調べます。 調べるのは以下のコードです。 <?php $ a = [ 1 , 2 , 3 ] ; $ b = 0 ; foreach ( $ a as $ v ) { $ b += $ v ; } このopcodeのダンプは以下の通りです。 L0 (3): EXT_STMT L1 (3): ASSIGN CV0($a) array(...) L2 (4): EXT_STMT L3 (4): ASSIGN CV1($b) int(0) L4 (6): EXT_STMT L5 (6): V5 = FE_RESET_R CV0($a) L10 L6 (6): FE_FETCH_R V5 CV2($v) L10 L7 (7): EXT_STMT L8 (7): ASSIGN_ADD CV1($b) CV2($v) L9 (6): JMP L6 L10 (6): FE_FREE V5 L11 (10): RETURN int(1) LIVE RANGES: 5: L6 - L10 (loop) 見ての通り、 for と比べてかなり異なります。 L3までは飛ばして、 foreach 内部を見ていきましょう。 行 処理 L5 V5 に $a の イテレータ ーを保存 無ければL10へ L6 $v に V5 から値を取得し アサイ ン 終端ならL10へ L8 $b += $v L9 L6に飛ぶ L10 V5 を解放 opcodeレベルではとてもシンプルになっています。ここで注目したいのは、 L5 - L6 の部分です。 FE_RESET_R , FE_FETCH_R は、 foreach で使用する命令です。配列 $a からの値の取得自体が foreach の機能である、ということですね。 逆に for は普通の配列アクセス命令でした。こういったところにも単純な繰り返しの for と、配列を逐次処理する foreach の違いがあります。 まとめ for と foreach の両者を比較してみると、構文ごとの目的の違いがわかりました。 for は繰り返し処理のための構文 foreach は逐次処理のための構文 一見どちらも繰り返しを目的としているようですが、 foreach のほうが用途を限定されています。 foreach はしばしば for をシンプルに書けるようになったと表現されたりしますが、実際にopcodeで比較してみると、 for の シンタックス シュガーではなく、内部ではしっかり別の処理です。 何気なく使っている構文でも、調べてみると意外と奥深いものです。
はじめに はじめまして、新卒1年目のyykaoruです。 今回は私事ですが、引っ越しをした際に実家にキーボード・マウスをおいてきてしまい、キーボード・マウス・モニターがない状態でなんとかして Raspberry Pi にアクセスするお話です。 やってみたこと概要 Raspberry Pi を購入しウキウキで 開封 していた際に、あることに気が付きました。 「あれ、ノートPCはあるけど、 Raspberry Pi 用のキーボードとマウスない・・・?」 しかし、どうにかして早くさわりたい...しばらく考えた結果 「せや!ネットワークに接続された Raspberry Pi の IPアドレス (プライベート)特定して SSH 接続でなんとかしたらええんや!」 今回は Raspberry Pi をセットアップする際にノートPCはあるのですが、キーボード・マウス・モニターがない場合に Raspberry Pi の IPアドレス を特定し、 SSH を利用して Raspberry Pi にアクセスする方法を紹介しようと思います。 今回の使用機器&環境 Raspberry Pi 4 Model B(8GB) Raspberry Pi 4 Model B用電源(USB Type-c 5V 3A) ノートPC(windows10) microSD (32GB) microSD をPCに接続するためのアダプタ(USBタイプ、SDカードタイプなどお好みで!) 有線LANケーブル ネットワーク環境 環境構築 Raspberry Pi の OS インストールは ラズベリー パイ財団公式が提供している Raspberry Pi Imager を利用します。 今回は Raspberry Pi で使用するOSとして Ubuntu 20.04.1 LTSを使用します。 OSのインストールが完了した後にSDカードをもう一度マウントするとSDストレージが出現し、中身はboot ディレクト リになっています。 ここの中に新たに ssh というファイル名のファイルを作成しておきます。 これにより、初期起動時に ssh 機能が有効になります。 Raspberry Pi に電源とネットワークルータにつながった有線LANケーブルを接続します。 2~3分くらい待ちます。 これで準備は完了です。 ノートPCから Raspberry Pi に SSH で接続 ノートPCで「 windowsキー + R」をし、入力欄に「cmd」と入力します。 すると コマンドプロンプト が起動します。 そこに「 arp -a」を打ち込みます。 すると同じネットワークに接続されている IPアドレス と MACアドレス が表示されます。 表示された一覧の情報で MACアドレス が「B8:27:EB:」か「DC:A6:32:」か「E4:5F:01:」で始まるものを探します。 これは Raspberry Pi の MACアドレス のレンジがこの3つに割り当てられているのでそれを利用して Raspberry Pi を特定する為です。 詳細は以下のサイトで確認できます。 udger.com 今回紹介した MACアドレス で始まる MACアドレス が書かれた行の IPアドレス をメモしておきます。 今回私の環境では「192.168.10.110」が割り当てられていました。 これで Raspberry Pi の IPアドレス が特定することができたので、 SSH 接続を試みます。 「 ssh ubunntu@192.168.10.110」を コマンドプロンプト に入力します。 すると以下の画像のように Raspberry Pi の Ubuntu に接続することができました。 ログインの初期パスワードはOSが Ubuntu の場合「 ubuntu 」となっているので、これを入力し、 新たにログ インパス ワードの設定を行うように要求されるのでパスワードを設定します。 これで、もう一度 SSH 接続をし、設定したパスワードでログインすると、 Raspberry Pi に接続することができます。 後は SSH のセキュリティ設定をするなり、お好きなツールのインストールを行ったり好きなようにできます! 所感 今回は休日で Raspberry Pi で遊ぼうとしたところ、肝心な入力系の機器を用意し忘れてしまったことから 新たな方法でのセットアップ方法を習得することができました。 始まりはとても初歩的なミスでしたが、結果として新しい知識を得ることができたのでとても満足しています。 セットアップした Raspberry Pi はせっかくなのでお家環境モニタリングシステムにでも活用しようと思います。 そのお話はまたいつかの機会にできたらと思います! では! エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 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:eichisanden です。 社内の有志とDDD( ドメイン 駆動設計)関連の2冊の書籍の読書会を開催したので、ふりかえって書いていきたいと思います。 1冊目:「エリック・ エヴァ ンスの ドメイン 駆動設計」 エリック・エヴァンスのドメイン駆動設計 作者: Eric Evans 発売日: 2013/11/20 メディア: Kindle 版 最初の題材として選んだのは、DDDの原典「エリック・ エヴァ ンスの ドメイン 駆動設計」です。 長いので以後は「 エヴァ ンス本」と呼びます。 今年の1月〜6月に掛けて、計17回開催しました。 読書会をやるきっかけ ある日の社内チャットで、 エヴァ ンス本に挫折したことで盛り上がったのがきっかけでした。 章と書いてますが部の間違いです かく言う自分も Tweet をさかのぼると3年前に挫折していて、いつかリベンジしたいと思っていました。 第4部の途中まで読んだけど、途中から字面を追ってるだけになったので再読する前提で最後までキーワードだけ拾い読みした。普通に読んだら1ヶ月じゃ終わらんなこれ。 — A1 (@EichiSanden) 2017年1月31日 良い機会なのでみんなで読書会しようと言うことになり、社内で声をかけたところ10人のメンバーが参加してくれました。 本は会社経費で購入させてもらいました。技術書としても高い部類の本に入るので非常にありがたいです。 壮観ですが本当はもっとあります 読書会の形式 週に1回 各回1.5時間 エクストリームリーディング 形式 そもそも私自身が読書会を主催したことが無いため色々調べた結果、この方式に決めました。 なぜこの形式にしたかと言うと、下記のような仮説があったからです。 事前準備が必要だと参加者の負担が大きいのでは? 誰かが講師になるスタイルだとその人に掛かる負担が大きいのでは? 参加者の負担が大きいと参加が離脱していってしまうのではないか? 特に事前準備はせず(と言っても主催者の私は不安なので読みましたが)、読書会当日は数ページ程度のパートに切って、読んでは議論を数セット繰り返しました。 分からなかったことや他の人の意見を聞きたいことは付箋に書いおいて、読み終わったらホワイトボードに貼って議論しました。 やってみてどうだったか? みんなで理解を補いながら読めるから理解しやすい 参加者の中にはDDDを実践しているチームの人や、以前にこの本やIDDD本を読んで知識がある人がいたりして、 意味が分からなかったところは他の人の解釈を聞くことで理解の助けになりました。 ただし難しいところはみんなで読んでも難しい 内容の難しさに輪をかけて文章の構造が分かりにくくて意味が掴めない箇所については、 みんなで束になって掛かっても分からないこともありました。 10章しなやかな設計の「概念の輪郭」が意味わからなすぎて辛い。頭が割れるー — A1 (@EichiSanden) 2020年3月2日 理解できた方が良いんですが、みんなも分からないのだから自分だけ理解力がない訳ではないと言う変な安心感もありました(笑) これについては他の本も含め行ったりきたり何度も読むことでブレイクスルーがあるかもしれないと思いました。 もしかすると分からない文は英語の原書も併せて読むことで理解できる物もあるかもと思いました(これは実践できてませんが) 事前に読んできてもらうスタイルの方が良かったかもしれない 参加者の負担を下げたことで完走率は高くなったとも思いますが、 この本のように難易度が高い場合は、事前に読んできてもらい当日は議論に集中した方が良かったかもしれないとも思っています。 これは読書会に求める期待効果によって実施スタイルは今後は使い分ようと終わってみて思いました。 技術書を読む習慣がない人にもっと本を読んで欲しい  → 本を読む機会を作ることが目的なので当日読めば良い 本の内容を深く理解するため他の人と議論したい  → 事前に読んできてもらって議論を中心にする 2冊目:「 ドメイン 駆動設計 モデリング /実装ガイド」 booth.pm 今年の7月〜8月に掛けて、計8回開催しました。 読書会をやるきっかけ エヴァ ンス本でモデルや ユビキタス 言語など戦略的な設計はなんとなくは理解できたが、具体的なイメージが湧かなかったため評判が良かったこの本を読んでみたところ良い本でしたので読書会の題材にしました。 コロナの影響でオンラインでの開催になり場所の制約がなくなったため、他拠点のメンバーにも声をかけたところ18名ものメンバーが参加してくれました。 読書会の形式 オンライン形式(ZOOM) エクストリームリーディング形式 事前に Github のIssueに持ち回りで概要をまとめておく 当日は疑問点などをSli.doに書いてもらって読み終わったら議論した やってみてどうだったか? エヴァ ンス本に比べて本の内容がスッと頭に入ってくるので、当日読むスタイルでも無理なくやれました。 本を読んでもらった後、 Github のIssueにまとめた意訳と自分の理解のDiffをとることで更に理解が進みました。 メンバーが拡大したことでDDDを実践している人も増えて、他サービスでDDDを実践して困ってることなど生きた情報が共有されて良い読書会になりました。 番外編: モデリング 大会 参加者から発案で、去年にネット上で流行っていたチケット料金 モデリング を読書会の集大成として皆んなでやってみました。 実際に手を動かしてみると自分の理解度合いが分かりますし、コードも書いてみないとモデルを完成させるのが難しいと言うのを肌で感じることができました。 拙作ですが、モデルには「振る舞い」まで書かない方が良いかもね、と他の参加者にフィードバックもらいました。 github.com チケット料金 モデリング はお題としてちょっと難しかった気がするので、 もっとシンプルなお題でまたやってみたいと思っています。 最後に 読書会の題材としてDDDの本は向いていて、 エヴァ ンス本は複数人で助け合いながら読むのが良かったです。 エヴァ ンス本で モデリング や ユビキタス 言語の重要性を理解した後で、噛み砕いた本で理解を深めるのは順番としては悪くなかったのではと思っています。 読書会を開催する時の参考になれば幸いです。
はじめに PHP8で導入されるmatch式が導入されます。 プログラマ としてはどういった場面で使いやすいのか、バグが入りやすさはどうなのかといった点が気になるのではないかと思います。 この記事では、match式についてswitch文との違いを述べながら、構文の性質からどういった場面で役立ちそうかを私なりに考えまとめました。 はじめに match式とは matchは式。値を代入する場面で真価を発揮する 文とは 式とは breakが不要。バグが入りにくくシンプルな記述ができる switch match 条件の記入忘れにエラーで気付ける switch match 厳密な比較 switch match おまけ おわりに match式とは match式の基本的な構文と、switchとの相違点を大まかに記載します。 簡単に言ってしまうとswitchのような分岐の構文です。 $param = 1; $result = match($param) { 1 = > "1の結果", 2 = > "2の結果", } // 与えられた引数($param)に対して一致するケース(1または2)の結果(= > の右側)を返す // 結果:$result = "1の結果" 下記にswitch文との大まかな違いを記載します。 switch match 返り値 なし あり breakの記述 必要 不要 各条件の処理 ブロックで記述可能 1行でしか書けない 比較 緩やか(==) 厳密(===) どの条件にも当てはまらない場合 そのまま実行される エラーになる 次の章以降では、PHP8で導入されるmatch式がどのような場面で生きてきそうかを紹介します。 match式の詳しい仕様は下記もご参照して頂くとより分かりやすいかと思います。 参考: wiki.php.net qiita.com matchは式。値を代入する場面で真価を発揮する 比較表で返り値が、matchは「あり」、switchは「なし」と記載しました。 これは、matchが 式 であり、ifやswitchが 文 であるためです。 この章では、式と文の違いを見た上で、match 式 が役立ちそうな場面を紹介します。 文とは ざっくり説明すると、返り値を返さず、右辺に配置できないものです。 例:ifやswitchがこれにあたります。右辺に配置できないため直接値の代入はできません。 // 下記のような直接値を代入するような書き方は「文」ではできない $result = if($param == 1){… $result = switch($param){… このため、各条件での代入が必要であり、代入忘れの恐れがあります。 // ifやswitch文では各条件で代入が必要。ケースが増えると代入忘れが怖い /** if **/ if($param == 1){ $result= "1の結果"; } elseif($param == 2){ $result = "2の結果"; } /** switch **/ switch ($param) { case 1: $result = "1の結果"; break; default: $result = "defaultの結果"; } 式とは ざっくり説明すると、返り値を持ち「=」の右辺に配置できるもの。また文としても使えます 例:matchがこれにあたります。直接値の代入が可能なので、代入を各条件に記述する手間がなく、代入忘れもなくなります。 $result = match($param) { 1 = > "1の結果", 2 = > "2の結果", }; もちろん不要であれば代入しない書き方もできます。 match($param) { // $result = の部分は取り外し可能 1 = > func_hoge(), 2 = > func_huga(), }; 参考: jsprimer.net breakが不要。バグが入りにくくシンプルな記述ができる switchでは明示的にbreakの記述が必要です。breakを毎回書く必要があり書き忘れのチェックが必要なので、労力がかかります。ただし、そのまま次の条件の処理を実行(フォールスルー)する記述は簡単に書けます。 switch breakは明示的に記述が必要。フォールスルーがデフォルトであり、フォールスルーを想定した記述は簡単にできますが、その代わりにbreak忘れによるバグが入りやすい構造になっています。 switch ($param) { case1: func_hoge();// break忘れ case2: func_fuga();// case 1でヒットしても処理fugaも実行される } ただし、case1の場合はcase1,2を、case2のときはcase2のみを実行したい場合は上記のコードであっています。 breakを書かないことによりフォールスルーを簡単に実現できますが、その分「break忘れ」というバグが入り込む副作用を持っています match フォールスルーできない。その代わりbreakを明示的に記載する必要がなく、break忘れを気にしなくてよいです。 match ($param) { 1 = > func_hoge(), 2 = > func_fuga(), }; フォールスルー(case1の場合はcase1,2を、case2のときはcase2のみを実行)したい場合はmatchには向きません。 というのも、基本的にフォールスルーしない仕組みであることに加え、matchは条件の右辺に1行しか記載ができないからです。 このため、上記のswitchと同じことをしようとすると下記のようになります。 match ($param) { 1 = > func_hogefuga(), // ここに2行以上の記述はできない 2 = > func_fuga(), }; function func_hogefuga() {// 関数切り出しが必要になる func_hoge(); func_fuga(); } 条件の記入忘れにエラーで気付ける 分岐に限らず、予期せぬ値が代入される場合 や 意図せず何も代入されなかった場合は、エラーやログで気付ける方がよいです。 match式ではどの条件にもあてはまらない場合エラーになるため、誤りに気づきやすいです。 switch 条件が抜けていてもそのまま実行されます。 switchのようなエラーがでない仕組みの場合、バグが発生した時にケース忘れの可能性を考えることが必要となります。数行ならよいですが、ケースや処理が多い時は確認が大変です。 foreach ($users as $user) { switch ($user- > score) { case "good": $command = "upgrade"; break; case "bad": $command = "BAN"; break; // $user- > scoreがnormalの場合、どのケースも通らず値の代入が行われない。 // このため、次のループ処理にそのまま$commandの値が引き継がれる } $commandQue[$user- > id] = $command; } // batchキューに入れる // 日時で実行されるバッチで普通のユーザがBANされてしまう match 条件が抜けているとエラーになります。 matchのようにエラーとなる仕組みの場合、ケースの記述忘れを気にする必要がりません。 foreach ($users as $user) { $command = match ($user- > score) { "good" = > "upgrade", "bad" = > "BAN", // normalの場合はケースが無いので、エラーとなり、処理が中断される } $commandQue[$user- > id] = $command; } 厳密な比較 matchは厳密な比較のため「数値だと思っていたら文字列であり思わぬ分岐に入った」といったことががなくなります。 ただし、 PHP はそもそも厳密な比較を前提として作られているとは言えないので、厳密な比較にすることでエラーになることもあるので注意が必要です。(おまけで記載します) switch $hoge = "hoge"; switch ($hoge) { case 1: echo "1の結果"; break; case "hoge": echo "hogeの結果"; break; } // "1の結果" match $hoge = "hoge"; match ($hoge) { 1 => "1の結果", "hoge" => "hogeの結果", } // "hogeの結果" 一見すると厳密な比較の方が良さそうです。ただし、元々型を気にせず気軽に書けることが特徴でもあった PHP 。 古いコードでmatchを導入する際にはまずは、型をしっかり整備するなど対策が必要そうです。 おまけ PHP の関数が厳密な型を意識した作りでないため、思わぬエラーが発生する例を紹介します。 例:拡張子でケースを分けたい場合 下記の例では画像ファイル名の拡張子から処理を分岐させるコードです。 $img_name = "img.jpg"; /** switch_正規表現 **/ switch (true) { case preg_match('/(\.jpg)$/', $img_name): echo "jpgだよ"; break; case preg_match('/(\.png)$/', $img_name): echo "pngだよ"; break; // gif等その他の分岐が続く default: echo "defaultだよ"; } // = > "jpgだよ" /** match_正規表現 **/ echo match (true) { preg_match('/(\.jpg)$/', $img_name) = > "jpgだよ", preg_match('/(\.png)$/', $img_name) = > "pngだよ", // gif等その他の分岐が続く default = > "defaultだよ" }; // = > "defaultだよ" 実はpreg_matchは該当する場合trueでなく、1を返します。このためmatch式ではtrue===1となり、 正規表現 に該当してもそのケースに入ることはありません。 このように既存の関数の性質を正しく理解していないと思わぬバグを生む可能性があります。 おわりに この記事ではPHP8で導入されるmatch式について、switchと比較しながら、有効そうな場面やバグの入りにくさについて記載しました。 今後導入を検討している方やmatch式について知りたい方の一助になっていれば幸いです。 エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 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
はじめに こんにちは!技術広報チームの itoken1013です。 2020年に入ってから、 ラク スはオンラインイベント開催に積極的に取り組んでいます! 今回はその中でも多くの方に参加をいただき大好評である、 エンジニアの勉強法ハックLT を紹介します。 rakus.connpass.com はじめに イベント概要 LTの紹介 1. ゲームで学ぶマネジメント/白柳隆司さん 2. 私にとっての学び/ariakiさん 3. esaを使ったインクリメンタル勉強法/Eiichi Horiuchiさん 4. 面倒臭がりだってキャッチアップしたい - RE:Bot から始めるものぐさ生活/みのるさん 5. 三日坊主でも勉強がしたい/脱脂綿さん 6. 自己学習を支える Inoreader + Notion/Logyさん 7. やはり俺のLT登壇はまちがっている。/moomooyaさん おわりに & 次回予告 イベント概要 先日9/9(水)で2回目の開催となりました『エンジニアの勉強法ハックLT』は名前の通り、エンジニアの勉強法や情報収集のノウハウを気軽に5分間のLTで紹介しあうイベントです。 今回は計8名からLT枠のご参加、またエンジニア志望の学生さん、新人・ベテランエンジニア、デザイナーさんと幅広い層の方々にご来場いただきました。 Twitter もZoomのチャットも賑やかにしていただき、中の人である私も大忙しのイベントでした…! togetter.com LTの紹介 1. ゲームで学ぶマネジメント/白柳隆司さん いつも ラク スの主催イベントにご参加いただいている白柳さんより、 5種類のゲームから学べるマネジ メントス キルをご紹介いただきました。 おざなりにしがちなマネジメントを、気楽に楽しめるゲームの中で学ぶ発想に驚きました。 動画化も密かに楽しみにしています。 speakerdeck.com 2. 私にとっての学び/ariakiさん Zoomフル活用の双方向LTを展開いただいたariakiさんには、「学ぶこと」のそもそも論を語っていただきました。 原動力となる欲求に素直に従うことで、「やらされている感」で学習をしている人は悪循環から脱却できるのではと共感していました。 私もいつか日本酒の席に同席したいです。 docs.google.com 3. esa を使ったインクリメンタル勉強法/Eiichi Horiuchiさん 時間のない中で、どのように学習を継続的に実施していくかを esa を使った事例と共に語っていただきました。 学習計画をマークダウンで手軽に作成したスケジュールに落とし込み、週次で消化していくというものでしたが、同じく esa を使っている私からは驚きのクオリティでした。 そして編み出した方法論もさることながら、継続を支える知識欲への情熱とセルフマネジメント力には感服でした! esa.io 4. 面倒臭がりだってキャッチアップしたい - RE: Bot から始めるものぐさ生活/みのるさん みのるさんが取り組んだ情報収集の カイゼン をご紹介いただききました。 特徴的だったのは、自発的に探すのではなく Bot で自動化を目指した点、そして自動化を実現するまでの作業を楽しんで取り組まれた点でした。 日常生活を改善している感覚、楽しいですよね。 面倒臭がりでもキャッチアップしたい- RE:Bot から始めるものぐさ生活 - from MinoruIto3 www.slideshare.net 5. 三日坊主でも勉強がしたい/脱脂綿さん 脱脂綿さんには「やる気に頼らずに」続けるための勉強方法の実践例を3つ教えていただきました。 個人的にずっと気になっていたお風呂用の防水カバーを紹介いただき、 私も購入して湯舟読書に取り組みたいと思いました。 読書家の多い ラク スの社員でも、欲しいと思ってくれる人がいるはず…! speakerdeck.com 6. 自己学習を支える Inoreader + Notion/Logyさん 膨大な情報を収集して学習に活用するためのフローを、Logyさんから紹介いただきました。 噂に聞いていた2つのツールを組み合わせており、私もさっそくアプリをインストールしてみました。 チラッと映りましたタグがとても緻密で驚きました…! 取得漏れの少ない RSSリーダー Inoreader biz-notion.northsand.co.jp メモ、プロジェクト管理、タスク管理の All-in-One ツール Notion www.notion.so speakerdeck.com 7. やはり俺のLT登壇はまちがっている。/moomooyaさん 主催者のmoomooyaさんには今回のイベントも含めたアウトプット駆動学習とLT中毒について熱く語っていただきました。 全国の登壇者を喚起し、アウトプットに役立つ内容に溢れています。 moomooyaさんには今後も適度なボリュームのLT会をどんどんドライブしていっていただきたいです。 speakerdeck.com おわりに & 次回予告 今までになく Twitter にはコメントが多く、私たちも楽しく学ばせていただきながら運営を行うことができました。 LTにご登録いただきました方々、賑やかしていただいた皆様、ご参加ありがとうございました! 3回目の開催も近々行われるかもしれませんので、どうぞお楽しみに。 さて、10月以降も ラク スではイベント開催を加速させていきます! ITエンジニア的はじめての経験 を1分で語りたい方、お早めにご応募ください! rakus.connpass.com その他のイベントへのご参加もお待ちしています! rakus.connpass.com
こんにちは、west-cです。 業務にて要件定義を行う機会があり、その成果物である要求仕様書の書き方を学ぶために『 【改訂第2版】[入門+実践]要求を仕様化する技術・表現する技術 』という書籍を読みました。今回はその内容をご紹介します。 【改訂第2版】[入門+実践]要求を仕様化する技術・表現する技術 ~仕様が書けていますか? 作者: 清水 吉男 技術評論社 Amazon ◆ 関連ブログ 仕様書 とは 【まとめ】 おすすめの読者層 要求仕様書の目的・ゴールが曖昧な方 自身が作成した仕様書において、仕様漏れや仕様の衝突が後工程で発生したことがある or 発生しないか不安を抱えている方 依頼者から要求を引き出す方法の糸口を掴みたい方 要求仕様書とは 書籍では、要求仕様書を「 要求について、関係者がその内容について認識を特定できている文章 」と定義しています。 要求 (今回の機能で実現したいこと)は曖昧なものを含んでいるため、具体的な振る舞いや制限として 仕様 化することが要求仕様書の役割となります。 要求仕様にまつわる問題 多くの現場における要求仕様にまつわる問題として以下が挙げられています。 多くの現場では、設計の様子が見えるような具体的なレベルの仕様は、設計工程の中で掘り下げるものと考えられている。 そのような考えで書かれた要求仕様書は設計や実装のイメージにはつながらず、設計工程や実装工程で具体化するなかで仕様の漏れや衝突に気づくことになる。 これは私にとっては耳の痛い話で、はじめて仕様書作成を担当した機能において具体的な仕様に落とし込めていない仕様書を作成し、実装工程になってから実装担当者から質問が頻発する、という苦い経験がありました。 要求仕様書というと上流工程に位置することから細かい仕様は後工程で決める印象を持ちがちですが、少なくとも仕様の衝突が発生しない程度には具体化する必要があると改めて感じました。 仕様化のテクニック 要求仕様書では、依頼者から引き出した要求を仕様に落とし込む必要があります *1 。 仕様へ落とし込む方法について、書籍では以下に基づいたテクニックが紹介されています。 要求という振る舞いの中に含まれている動き(=動詞)をすべて表現してしまえば、それぞれの動詞および目的語について必要な仕様をもれなく記述できる。 例えば、以下のような要求があったとします。 印刷途中で、用紙やトナーの不足が分かった時点で担当者にメールで知らせる。 この文章において直接見えている動詞は以下の通りです。 不足が"分かる" メールで"知らせる" しかし、この要求に含まれる動詞はこれだけではなく、よく読むと以下の動詞も見えてきます。 用紙やトナーの量を"知る" メールを"組み立てる" 担当者のメールアドレスを"読み出す" これらの動詞をもとに仕様化を行う、というのが書籍で紹介されている仕様化のテクニックです。それぞれの動詞に対して仕様化すべき内容としては以下のようなものがあるでしょう。 用紙やトナーの量を知る 用紙・トナーの残量をどのように入手するか 不足が分かる 用紙・トナーの残量がどれくらいであれば不足と判断するか メールを組み立てる メッセージの構成をどうするか 担当者のメールアドレスを読み出す メールアドレスをどこから読み出すか メールで知らせる どのように通知するか 再送方法をどうするか 「よく読むと見えてくる動詞」は慣れないと洗い出しが難しそうですが、個人的には、その要求を実現するためのプロセスをイメージすれば、そのプロセス一つひとつが動詞と対応付くのではと感じました。設計や実装のイメージを持ちながら要求仕様書の作成を行う考え方は、ここに直結するのだと思います。 実践してみた 書籍にはこの他にも要求仕様書の作成に関するテクニックが紹介されており、筆者が推奨するフォーマットも提示されています。そこで、過去に自分が要件定義を行った機能について、改めて書籍の手法を用いて仕様化を行ってみました。 ちなみに書籍で紹介されている要求仕様書の作成手法は USDM という名前が付いているようです。調べると資料も見つかりますので、詳しく知りたい方は書籍と併せてそちらもどうぞ。 作成した要求仕様書 書籍で紹介されているフォーマットに厳密には従っていませんが、一つひとつの要求を仕様にブレークダウンする書き方は踏襲しています。また、要求に複数の動詞が含まれる場合は、要求をもう一段階層化して動詞を分解しています。 実践した感想 (良くも悪くも)考えるスコープを絞り込める 一つの動詞+目的語に絞り込まれた要求を仕様に落とし込んでいくため、考えるべきスコープを絞って集中的に仕様化できることを実感しました。これまでの私は思いつくままに仕様化していましたが、全体から詳細にブレークダウンしていくことでより網羅的に仕様の洗い出しができている感覚がありました。 一方、考えるスコープが絞られるということはより近視眼的になることでもあります。仕様化を進める中でも、複数の要求にまたがるような仕様が漏れそうという不安がありました。定期的に要求レベルに立ち返って全体を俯瞰するなど、鳥の目・虫の目の視点の切り替えが肝になると感じました。 要求と理由をセットで記述するのは効果あり 書籍では、「なぜこの要求を実現したいのか」という依頼者の思いを明らかにするため、要求は理由とセットで記述するよう紹介されています。 実際に理由を添えて記述することで、「そういえばこの要求はなぜ必要なのだろう?」と初心に立ち返りながら考えることができました。背景や理由を明文化することで、要求を実現するためのよりよい解に至ることもできるのではと思いました。 また、仕様の経緯を辿れるようにする意味でも、背景や理由をドキュメントに残しておく意義はあると思います。 おわりに 試しに実践してみたことで、書籍のテクニックが仕様漏れ防止の一助になると感じました。このフォーマットをいきなり既存の要求仕様書に置き換えるのはハードルが高そうなため、まずは抜け漏れ防止のクロスチェック的な立ち位置で部分的に取り入れたいです。 また当然ではありますが、いくら漏れなく仕様化できたとしても、そもそもの要求の認識が依頼者とズレていたのであれば元も子もありません。今後は要求の引き出し方についても深く学んでいきたいです。 エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 forms.gle イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! rakus.connpass.com *1 : この記事では割愛していますが、書籍内には依頼者から要求を ヒアリ ングする方法も紹介されています。
はじめに はじめまして、新卒1年目のyykaoruです。 今回はDockerを勉強しようと思い、Dockerで PHP の実行環境を構築してみたお話です。 前提 Docker、docker-composeがインストールされていること やってみること概要 今回行うことは、 php コンテナにローカルストレージのsrcをマウントし、nginxコンテナからアクセスして確認、という流れです。 ファイル構成と ソースコード を順番に説明し、最後に実行してみたいと思います。 はじめに 前提 やってみること概要 ファイル構成 ソースコード docker-compose.yml default.conf Dockerfile php.ini index.php 実行 最後に ファイル構成 今回Dockerで PHP の実行環境を作成する上でのファイル構成は以下の通りです。 nginx Dockerコンテナ起動時、nginx内の設定ファイルに書かれている設定に上書きします。 なぜDockerファイルがないかは後に記述します。 php PHP コンテナを建てるために必要なDockerファイルと、 PHP コンテナの中に配置する PHP の環境設定ファイルとして php .iniを作成しておきます。 src 実行する php ファイルを配置しておきます。 このsrc配下のファイルがnginxで配信されます。 docker-compose.yml このファイルでnginx、 php コンテナをまとめてインストールできます。ホント便利。 │ docker-compose.yml │ ├─nginx │ default.conf │ ├─php │ Dockerfile │ php.ini │ └─src index.php ソースコード それでは今回使用するファイルの ソースコード を紹介していきます。 docker-compose.yml version: '3' services: nginx: image: nginx:stable-alpine ports: - "8080:80" volumes: - ./src:/var/www/html - ./nginx/default.conf:/etc/nginx/conf.d/default.conf php: build: ./php volumes: - ./src:/var/www/html docker-compose.ymlには、nginx, php コンテナのインストール内容が書かれています。 それぞれのコマンド名を簡単に説明していきます。 version: composeファイルのフォーマットのバージョンを明記しています。 services: コンテナのインストール内容が書かれます。 nginx: サービス「nginx」のインストールを示しています。 php : サービス「 php 」のインストール内容を示しています。 image: Dockerでは後述するDockerfileでコンテナのイメージ名を明記してインストールするのが基本ですが、imageでインストールイメージを指定することもできます。 今回はnginxコンテナはcomposeファイルでインストール、 php コンテナはDockerfileでインストールしています。 ports : ここではコンテナ側のポートとローカル側のポートをつなぐ役割をしています。 nginxの場合は「- "8080:80"」と書かれていますが、ローカル側の8080番をコンテナ側の80番とつなぐという意味を表しています。 volumes: ここではローカル側の ディレクト リ内容をコンテナにマウントする設定が書かれています。 今回はnginx配下で「- ./src:/var/www/html」と示していますが、これはローカル側の./srcをコンテナ側の/var/www/htmlにマウントするという意味を表しています。 build: 作成したDockerfileの場所を指定することで、指定したDockerfileの内容に従ってコンテナがインストールされます。 ※以前までは links: というものを書いてコンテナ間のネットワークに参加させる必要があったのですが、現在は書かなくてもデフォルトで network: というものが働く様になったようで、これにより各コンテナはそのネットワークに参加した状態になるということのようです。 default.conf server { listen 80; root /var/www/html; index index.php index.html; access_log /var/log/nginx/access.log; error_log /var/log/nginx/error.log; location / { try_files $uri $uri/ /index.php$is_args$args; } location ~ \.php$ { fastcgi_pass php:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } } default.confにはnginxの設定内容を記述します。 よくハマってしまう点としては「 fastcgi _pass」の設定です。 デフォルトの fastcgi _passは 127.0.0.1 :9000を指定しており、これはnginxコンテナの localhost を指定してしまうために502エラーを返してしまいます。(BadGateway) このエラーを解決するためには php コンテナを指定する必要があり、「 fastcgi _pass php :9000;」と明記することでサービス名( php )で php コンテナを指定することができます。 Dockerfile FROM php:fpm COPY php.ini /usr/local/etc/php/ Dockerfileでは php のコンテナイメージのインストール、 php 設定ファイルの上書きを行っています。 FROM php :fpmと指定することでDocker公式が提供している PHPイメージ をインストールします。 COPY ローカルにある php .iniをインストールした php .iniにコピー、上書きします。 php .ini [Date] date.timezone = "Asia/Tokyo" [mbstring] mbstring.internal_encoding = "UTF-8" mbstring.language = "Japanese" php .iniには php 実行環境の設定を記述します。 必要に応じて設定を追加します。 index. php <?php echo "Hello World!"; phpinfo(); ここでは php スクリプト を書いています。 内容はお好みでどうぞ! 実行 以上のファイルが用意できたら、Docker、Docker-composeがインストールされている環境で以下のコマンドを入力します。 docker-compose build コマンド入力後、dockerコンテナのビルドが走ります。 ビルドが正常に終了したことが確認できたら、以下のコマンドでコンテナを立ち上げます。 docker-compose up コンテナが立ち上がった後に localhost:8080 にアクセスすると以下のような画面が表示されます。 これで php の実行環境は完成です! 後は、src内で php ファイルを変更したりしてみましょう! 最後に 今回はDockerを利用して PHP の実行環境を構築してみました。 今回は必要最小限の構成なので、次回記事を書く機会には PHP の フレームワーク であるLaravel開発環境をDockerを利用して構築したお話をしたいと思います。 エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 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:radiocat です。2020年8月27日に開催された Developers Summit 2020 KANSAIに登壇させていただきました。今回はそのレポート記事です。 セッションの様子 Developers Summit 2020 KANSAI について 「 Developers Summit 」通称「 デブサミ 」は 翔泳社 さん主催の IT技術 者向けイベントです。近年は東京で夏と冬、そして関西と福岡で年1回開催されています。今回は関西で開催されて記念すべき10周年となったイベント「 デブサミ 関西」で登壇の機会を頂きました。 event.shoeisha.jp 今回のテーマは「The Future Has Come」ということで、少し先の未来の「変化」の到来に関するチャレンジをシェアしようという思いが込められたテーマでした。私も大阪オフィスで9年間勤務して様々な変化にチャレンジしてきました。貴重な機会を頂いたので、9年間の取り組みを可能な限り盛り込んで発表させて頂きました。 関西的なノリで変化の波をノリこなすチームの取り組み speakerdeck.com チームでの変化へのチャレンジのこれまでの経験を振り返ってみて、大切な要素のひとつが大阪のチームならではの「関西的なノリ」なのではないか?と考えたことが今回のテーマのきっかけです。もちろん、関西のノリがなければ変化に適応できないという意味ではありません。「関西のノリの中に変化に適応していくヒントがあるのではないか?」、「過去の経験を振り返ってそのヒントを整理してみたい!」という思いが、 デブサミ 関西に向かううえでの私自身のチャレンジでした。 基本的に不確実な変化の波を乗りこなすのは難しい そもそも変化の波を乗りこなすのは簡単ではありません。大阪では2018年に 地震 、豪雨、台風と、1ヶ月おきに災害が発生して、出勤できないなどで仕事の予定を調整せざるを得ない経験をしました。私自身、 地震 が発生した日は当時担当していたサービスのリリース当日で、たまたま出勤できたメンバーとリモートでやり取りしながら対応した経験があります。 2018年の関西 不確実な波を乗りこなすために、世の中に溢れている アジャイル の手法やマネジメントの事例を取り入れようとしますが、それもなかなかうまくいきません。変化の波を乗りこなすつもりが、気づいたら情報の波の乗りこなそうとしてしまい、溢れる情報のより複雑な渦に飲み込まれてしまうこともあります。 情報の波は複雑性の渦を呼び込む ノリで変化の波をノリこなす 不確実な波に抗わず、困難な状況に向き合って起きたことに対処するためには、「状況の洞察」と「ポジティブな姿勢」が大切だと考えました。これこそが今回のテーマである「ノリ」です。 ノリは「状況の洞察」と「ポジティブな姿勢」 変化の波をノリこなす3つのステップ ノリで変化の波を乗りこなし続けるには、3つのステップでノリを広げていくことが大切です。この3つのステップをこれまで経験してきた事例を交えながらご紹介しました。 変化の波をチームでノリこなす3つのステップ 自分らしさのリーダーシップ まず最初のステップが、チームで働く土台として一人ひとりが自分らしさのリーダーシップを持つことです。これは近年のリーダーシップ論で「オーセンティック・リーダーシップ」と呼ばれています。 オーセンティック・リーダーシップ チームの中で互いに自分らしさを持ちつつ、他者を理解しあう取り組みの事例として、以前このブログにも投稿したリモートワーク時の取り組みを「自己開示と安心・安全な仕組みに身を預ける」という2つのポイントでご紹介しました。 tech-blog.rakus.co.jp 自己開示と安心・安全な仕組みに身を預ける取り組み チームで経験から学ぶ 2つ目のステップの「チームで経験から学ぶ」については「 アジャイル の原則」「 カイゼン 文化」「ありたい姿」の3つを軸にすることをお話しました。 経験から学ぶチームのモデル これは Scrum Fest Osaka 2020で発表させていただいた内容 を引用していますが、今回は大阪のチームで過去に経験した開発プロジェクトを一通り振り返ってお話しました。 大阪のチームで経験した開発プロジェクト 過去の開発プロジェクトの反省点を突き詰めれば アジャイル の原則に向かうことは認めつつも、予測できない様々な変化の波を乗りこなしていくためには アジャイル の手法だけにとらわれず、チームが経験から学ぶ方向に向かうことが変化を乗りこなすことにつながると考えています。 イテレーティブなプロセスを繰り返して経験から学ぶ 枠を超えてつながる 3つ目のステップの「枠を超えてつながる」では、小さく始めた社内勉強会が組織や会社全体を巻き込んだイベントへと発展した事例をご紹介しました。 勉強会イベント主催の様子 変化の波の中で自分たちも変化していくために、少しずつ枠の外に向かっていくことが変化の波を乗りこなすことにつながるという内容です。 枠を超えてつながるノリの大切さ 枠を超えることよにって、つながった人たち同士で継続的な相互アップデートを生み出すことができました。 枠を超えたつながりによって得られたこと 以上の3つのステップを外側に向けて広げることが様々な変化の波を乗りこなすことにつながると私は考えています。 外側に向けてステップを広げる 関西人はノリこなすのが得意? 突然のリモートワークへの移行の中で「大変だけど、これはこれでオモロそう」と言ったメンバーがいました。この「オモロそう」が変化の波を乗りこなす「ノリ」のヒントだと私は考えました。 大変だけど「オモロそう」 「オモロそう」は、過去にやったことが無いけど、これからやる価値がありそうでポジティブなことです。つまり、「オモロそう」には「状況の洞察」と「ポジティブな姿勢」のノリで対処するための要素が含まれています。 「オモロそう」とは そして、関西人は突然怖い人が借金を取りに来てもノリで対処する喜劇やどんな無理難題でもノリで調査・報告する探偵番組など、普段からノリでノリこなす文化の中で生きています。 某喜劇のイメージ 関西人らしく「オモロそう」を引き寄せて外側へステップを進めていくことが、変化の波をノリこなすことにつながるのではないかと私は考えています。 「オモロそう」引き寄せて変化の波をノリこなす デブサミ も変化の波を乗りこなす 今回はオンライン開催ということでしたが、セッション内容はリアルタイム配信ではなく事前撮影方式でした。テレビ撮影のようにカメラを向けられてマイクで音声を拾ってもらいながら行うセッションは初めてで、とても貴重な経験をさせて頂きました。 デブサミ 自体も新たな変化に向けてチャレンジしていることを改めて実感できる体験でした。 セッション撮影の様子 枠を超えてつながりたい 今回の発表をきっかけに、これまで以上にたくさんの方々と枠を超えてつながることができればと思っています。今月も様々なイベントが計画されていますので、興味を持たれましたらぜひご参加ください。 rakus.connpass.com
はじめに こんにちは。kkystです。 開発を担当しているプロダクトではpg_bigmを利用して 全文検索 機能を提供しています。 今回、その 全文検索 を行っているテーブルにINSERTを行う一部の処理で、 応答時間 が増えていることを検知しました。 そこでその原因を調査していったところ、GINインデックスのGIN高速更新手法にたどり着き、待機リストの有無による 応答時間 の検証を行いました。 その結果として、GIN高速更新手法の有効性を確認することができたので、検証の記録を残しておきたいと思います。 はじめに 概要 GIN高速更新手法とは 検証環境 検証内容 検証結果 おわりに 概要 GIN高速更新手法とは GINインデックスは 全文検索 向けのインデックスで文書中の単語の位置を保持しているため、使用することで特定の単語の検索を効率的に行えるようになります。 基本的には英文(英単語)を意識したものとなっていますが、pg_bigmなどのモジュールを導入することで日本語でも利用できます。 GINインデックスの高速更新手法については、 PostgreSQL の リファレンス には下記のように記載されています。 1つのヒープ行の挿入または更新によりインデックスへの挿入が多く発生するという、 転置インデックス の本質的な性質のためGINインデックスの更新は低速になりがちです。 (各キー用のヒープ行はインデックス付けされた項目から取り出されます。) PostgreSQL 8.4からGINは、新しいタプルを一時的なソートされていない、待機中の項目リストに挿入することにより、この作業の大部分を遅延させることができるようになりました。 (中略) この手法の大きな欠点は、検索時に通常のインデックス検索に加え待機中の項目リストのスキャンを行わなければならない点です。 このため、待機中の項目リストが大きくなると検索が顕著に遅くなります。 他の欠点は、ほとんどの更新は高速ですが、待機中の項目リストが「大きくなりすぎる」きっかけとなった更新は即時の整理処理を招くことになり、他の更新に比べ大きく低速になります。 自動バキュームを適切に使用することで、これらの両方の問題を最小化することができます。 「整理処理を行う更新処理が大きく低速となる」という部分が実際にどれくらい低速となるの?という疑問が浮んだので、この部分に着目して実際の処理時間を計測することにしました。 検証環境 ローカル環境:Windows10 Intel (R) Core(TM) i7-8550 CPU @1.8GHz RAM:16GB SSD 512GB Docker for Windows version 2.3.0.3 PostgreSQLの公式Dockerイメージ(v12.4) テーブル構造 create table sample_table(id integer, note text); create index sample_table_ix1 on sample_table using gin (note gin_bigm_ops); ※待機リストを利用しない場合は、WITH (FASTUPDATE = OFF)を付与してインデックス再作成を実施。 検証内容 以下の状態でASCIIコードのランダム1000バイトの文字列データを1件挿入し、処理時間を計測する。 初期データなし 待機リストあり(空) 待機リストあり(最大≒gin_pending_list_limit(4MB):1000バイト * 170件) 待機リストを利用しない 初期データ100万件(各行1000バイト) 待機リストあり(空) 待機リストあり(最大≒gin_pending_list_limit(4MB):1000バイト * 170件) 待機リストを利用しない 検証結果 待機リスト(空) 待機リスト(最大) 待機リストなし 初期データなし 89.034ms 97.462ms 83.937ms 初期データあり 70.088ms 863.200ms 827.526ms この結果より、インデックスへの整理処理そのものに時間がかかり、整理する対象のデータ件数による差はほとんどありませんでした。 また、今回用意したパターンであれば、その整理処理自体も1秒を切っているので、他の処理との兼ね合いもありますがオンラインで利用しても許容できる速度であることがわかりました。 このことから、大半の更新処理が高速になるGIN高速更新手法は非常に有効であり、特別な要件がない限りは有効にしておくべきものだということがわかりました。 検証結果の詳細を展開 1.1. 初期データなし-待機リストあり(空) dev=# SELECT * FROM gin_metapage_info(get_raw_page('sample_table_ix1', 0)); pending_head | pending_tail | tail_free_size | n_pending_pages | n_pending_tuples | n_total_pages | n_entry_pages | n_data_pages | n_entries | version --------------+--------------+----------------+-----------------+------------------+---------------+---------------+--------------+-----------+--------- 4294967295 | 4294967295 | 0 | 0 | 0 | 2 | 1 | 0 | 0 | 2 dev=# INSERT INTO sample_table SELECT 1000000, STRING_AGG(str, '') FROM ( SELECT chr(40 + (RANDOM() * 1000)::INT % 84 ) AS str FROM GENERATE_SERIES(1, 1000) length) t; INSERT 0 1 時間: 89.034 ミリ秒 dev=# SELECT * FROM gin_metapage_info(get_raw_page('sample_table_ix1', 0)); pending_head | pending_tail | tail_free_size | n_pending_pages | n_pending_tuples | n_total_pages | n_entry_pages | n_data_pages | n_entries | version --------------+--------------+----------------+-----------------+------------------+---------------+---------------+--------------+-----------+--------- 2 | 4 | 5680 | 3 | 1 | 2 | 1 | 0 | 0 | 2 1.2. 初期データなし-待機リストあり(最大) dev=# SELECT * FROM gin_metapage_info(get_raw_page('sample_table_ix1', 0)); pending_head | pending_tail | tail_free_size | n_pending_pages | n_pending_tuples | n_total_pages | n_entry_pages | n_data_pages | n_entries | version --------------+--------------+----------------+-----------------+------------------+---------------+---------------+--------------+-----------+--------- 347 | 4 | 5660 | 177 | 59 | 2 | 1 | 0 | 0 | 2 dev=# INSERT INTO sample_table SELECT 1000000, STRING_AGG(str, '') FROM ( SELECT chr(40 + (RANDOM() * 1000)::INT % 84 ) AS str FROM GENERATE_SERIES(1, 1000) length) t; INSERT 0 1 時間: 97.462 ミリ秒 dev=# SELECT * FROM gin_metapage_info(get_raw_page('sample_table_ix1', 0)); pending_head | pending_tail | tail_free_size | n_pending_pages | n_pending_tuples | n_total_pages | n_entry_pages | n_data_pages | n_entries | version --------------+--------------+----------------+-----------------+------------------+---------------+---------------+--------------+-----------+--------- 2 | 4 | 5660 | 3 | 1 | 2 | 1 | 0 | 0 | 2 1.3. 初期データなし-待機リストを利用しない dev=# create index sample_table_ix1 on sample_table using gin (note gin_bigm_ops) WITH (FASTUPDATE = OFF); CREATE INDEX 時間: 43.528 ミリ秒 dev=# SELECT * FROM gin_metapage_info(get_raw_page('sample_table_ix1', 0)); pending_head | pending_tail | tail_free_size | n_pending_pages | n_pending_tuples | n_total_pages | n_entry_pages | n_data_pages | n_entries | version --------------+--------------+----------------+-----------------+------------------+---------------+---------------+--------------+-----------+--------- 4294967295 | 4294967295 | 0 | 0 | 0 | 2 | 1 | 0 | 0 | 2 dev=# INSERT INTO sample_table SELECT 1000000, STRING_AGG(str, '') FROM ( SELECT chr(40 + (RANDOM() * 1000)::INT % 84 ) AS str FROM GENERATE_SERIES(1, 1000) length) t; INSERT 0 1 時間: 83.937 ミリ秒 dev=# SELECT * FROM gin_metapage_info(get_raw_page('sample_table_ix1', 0)); pending_head | pending_tail | tail_free_size | n_pending_pages | n_pending_tuples | n_total_pages | n_entry_pages | n_data_pages | n_entries | version --------------+--------------+----------------+-----------------+------------------+---------------+---------------+--------------+-----------+--------- 4294967295 | 4294967295 | 0 | 0 | 0 | 2 | 1 | 0 | 0 | 2 2.1. 初期データあり-待機リストあり(空) dev=# SELECT * FROM gin_metapage_info(get_raw_page('sample_table_ix1', 0)); pending_head | pending_tail | tail_free_size | n_pending_pages | n_pending_tuples | n_total_pages | n_entry_pages | n_data_pages | n_entries | version --------------+--------------+----------------+-----------------+------------------+---------------+---------------+--------------+-----------+--------- 4294967295 | 4294967295 | 0 | 0 | 0 | 212649 | 2764 | 209881 | 7224 | 2 dev=# INSERT INTO sample_table SELECT 1000001, STRING_AGG(str, '') FROM ( SELECT chr(40 + (RANDOM() * 1000)::INT % 84 ) AS str FROM GENERATE_SERIES(1, 1000) length) t; INSERT 0 1 時間: 70.088 ミリ秒 dev=# SELECT * FROM gin_metapage_info(get_raw_page('sample_table_ix1', 0)); pending_head | pending_tail | tail_free_size | n_pending_pages | n_pending_tuples | n_total_pages | n_entry_pages | n_data_pages | n_entries | version --------------+--------------+----------------+-----------------+------------------+---------------+---------------+--------------+-----------+--------- 212646 | 212648 | 5740 | 3 | 1 | 212649 | 2764 | 209881 | 7224 | 2 2.2. 初期データあり-待機リストあり(最大) dev=# SELECT * FROM gin_metapage_info(get_raw_page('sample_table_ix1', 0)); pending_head | pending_tail | tail_free_size | n_pending_pages | n_pending_tuples | n_total_pages | n_entry_pages | n_data_pages | n_entries | version --------------+--------------+----------------+-----------------+------------------+---------------+---------------+--------------+-----------+--------- 212646 | 213158 | 5940 | 513 | 171 | 212649 | 2764 | 209881 | 7224 | 2 dev=# INSERT INTO sample_table SELECT 1000001, STRING_AGG(str, '') FROM ( SELECT chr(40 + (RANDOM() * 1000)::INT % 84 ) AS str FROM GENERATE_SERIES(1, 1000) length) t; INSERT 0 1 時間: 863.200 ミリ秒 dev=# SELECT * FROM gin_metapage_info(get_raw_page('sample_table_ix1', 0)); pending_head | pending_tail | tail_free_size | n_pending_pages | n_pending_tuples | n_total_pages | n_entry_pages | n_data_pages | n_entries | version --------------+--------------+----------------+-----------------+------------------+---------------+---------------+--------------+-----------+--------- 4294967295 | 4294967295 | 0 | 0 | 0 | 212649 | 2764 | 209881 | 7224 | 2 2.3. 初期データあり-を利用しない dev=# SELECT * FROM gin_metapage_info(get_raw_page('sample_table_ix1', 0)); pending_head | pending_tail | tail_free_size | n_pending_pages | n_pending_tuples | n_total_pages | n_entry_pages | n_data_pages | n_entries | version --------------+--------------+----------------+-----------------+------------------+---------------+---------------+--------------+-----------+--------- 4294967295 | 4294967295 | 0 | 0 | 0 | 212649 | 2764 | 209881 | 7224 | 2 (1 行) 時間: 63.233 ミリ秒 dev=# SELECT * FROM gin_metapage_info(get_raw_page('sample_table_ix1', 0)); pending_head | pending_tail | tail_free_size | n_pending_pages | n_pending_tuples | n_total_pages | n_entry_pages | n_data_pages | n_entries | version --------------+--------------+----------------+-----------------+------------------+---------------+---------------+--------------+-----------+--------- 4294967295 | 4294967295 | 0 | 0 | 0 | 212651 | 2764 | 209886 | 772968 | 2 dev=# INSERT INTO sample_table SELECT 1000001, STRING_AGG(str, '') FROM ( SELECT chr(40 + (RANDOM() * 1000)::INT % 84 ) AS str FROM GENERATE_SERIES(1, 1000) length) t; INSERT 0 1 時間: 827.526 ミリ秒 dev=# SELECT * FROM gin_metapage_info(get_raw_page('sample_table_ix1', 0)); pending_head | pending_tail | tail_free_size | n_pending_pages | n_pending_tuples | n_total_pages | n_entry_pages | n_data_pages | n_entries | version --------------+--------------+----------------+-----------------+------------------+---------------+---------------+--------------+-----------+--------- 4294967295 | 4294967295 | 0 | 0 | 0 | 212651 | 2764 | 209886 | 772968 | 2 おわりに 今回は、GIN高速更新手法の待機リストの整理処理による低速化に着目して検証し、GIN高速更新手法の有効性を実感することができました。 時間の都合もあり現時点における検証はここまでですが、gin_pending_list_limitなどの適切な設定値やデータ量や偏りとの関連などもありそうなので、今後もこのGIN高速更新手法について別な切り口で検証していきたいと思います。
はじめに はじめまして、新卒一年目のYoshidaMichaelです。 研修でGitの使い方を学び、「家で作ってる Bot もGitで管理しちゃうぞー!」なんて意気込んでいたわけですが、 うっかり トーク ンが載った状態のコードをpushしてしまって大変なことになりそうでした。 今回はそれを GitHub に助けていただいた、そんなお話です。 はじめに シークレットスキャニング 提携サービス 根本対処 方法1: 別のファイルに記述してそれを読み取る 方法2: 環境変数に記述する まとめ シークレットスキャニング シークレットスキャンニングについて - GitHub Docs GitHub は リポジトリ をスキャンして既知のシークレットのタイプを探し、誤ってコミットされたシークレットの不正使用を防止します なんと GitHub ではpushされた際に自動で トーク ン (ここではシークレットと表現されています) が含まれていないか 確認してくれるんですね。 どうやら私のようなうっかりミスをする人は多いらしく、 トーク ンスキャニングを導入してから発見された トーク ン 流出は1年間で10億件にものぼるそうです。 この機能が導入されてなかったと思うと恐ろしい話ですね......。 提携サービス 実は トーク ンが流出した当初、私はシークレットスキャニングが GitHub の機能であることを知りませんでした。 というのも、私が GitHub へpushした際に受けた警告は以下のようなものだったのです。 トーク ン流出時に受け取ったメッセージ これを見て「うわー、Discordって GitHub の投稿まで見てるんだ!すごい!」なんて思ったわけですが、調べてみると GitHub が調査して トーク ンを発見、提携しているサービスと情報を共有しているそうです。 現在 GitHub は32のサービスプロバイダと提携しており、それらのサービスであれば万一 トーク ンが流出しても大惨事になることは防げそうです。 根本対処 今回はシークレットスキャニングが優秀だったおかげで トーク ンを発見してもらい、すぐさまDiscordに トーク ンを リセットしてもらえたわけですが、いつも発見してもらえるかは正直わからないところです。 また、そもそも GitHub と提携していないサービスの トーク ンであれば検出されず、そのまま外部へ流出してしまうこともあるでしょう。 そこでここでは「そもそも トーク ンを流出させない工夫ってないの?」という方法を調べてみましたので紹介しようと 思います。 方法1 : 別のファイルに記述してそれを読み取る Bot を動かす環境に トーク ンの記述されたファイルを用意してそれを読み取る方法です。 ファイルは GitHub にはpushせず、 Bot を動かす環境だけで管理するようにします。 この方法は別の環境で Bot を動かしたい場合全ファイルを動かすだけで良いので楽な反面、そのファイルをうっかり GitHub などにアップしないことは気をつけなければなりません。 方法2: 環境変数 に記述する どうやらメジャーな方法はこちらのようです。 環境変数 自体は Windows でプログラミングをする際にPathを通す過程で触った方も多いのではないでしょうか。 環境変数とは - IT用語辞典 e-Words 環境変数 とは、OSが設定値などを永続的に保存し、利用者や実行されるプログラムから設定・参照できるようにしたもの。プログラムの実行時などに必要となる、利用者やコンピュータごとに内容が異なる設定値などを記録するために用いられる。 Python では標準ライブラリで 環境変数 の設定、取得等が行えるそうなので Python を利用した開発ではこちらの方法も気軽に利用できそうですね。( Python 以外でも気軽かはわかりません。) 私も今後はこちらで トーク ン管理をしようかなと思います。 まとめ 今回は GitHub のシークレットスキャニングと トーク ン管理の方法について紹介しました。 皆さんの トーク ン流出によるリスクを少しでも下げることに貢献できましたら幸いです。 それでは!
はじめに いつも ラク スエンジニアブログをご覧いただき、ありがとうございます! 技術広報のitoken1013です。 今回は8月第2回目のMeetup 『 SaaS 新規プロダクトの技術』 のコンテンツを紹介させていただきます。 当日は ラク スMeetup史上、最多の120名超の方々にご参加いただき、熱いイベントとなりました。 ご参加いただきました方々、本当にありがとうございました! rakus.connpass.com イベントテーマ概要 ラク スでは多くの to B向け SaaS を展開し続ける傍ら、新たな領域のサービス立ち上げに向けて企画が日々進められています。 今回はイベントテーマの『新規プロダクト』が表す通り、以下の新規 HRTech系プロダクトの開発より得られた知見を紹介させていただきました。 楽楽労務 楽楽勤怠 技術選定・開発・インフラそれぞれの立場より4名のエンジニアが発表させていただき、今までになく幅広いコンテンツを皆様にご提供できたイベントだったのではないかと思います。 ご参加いただきました方の中には新規プロダクト開発に関わっている方も多く、今回のMeetupを通じて日々の開発に役立つ情報をご提供できましたのであれば、大変幸いです。 発表の紹介 それでは今回発表させていただきました内容を紹介させていただきます! 発表スライドとlogmiから、当日の様子を感じ取っていただけますと幸いです! 新サービス立ち上げ時の技術選定と、サービス立ち上げに向けた ラク スの取り組み まずは ラク スの先行技術検証を主導しつつ、今年度はオンラインイベントのパーソナリティとしても大活躍中の鈴木より、楽楽 労務 における アーキテクチャ 選定のご紹介からです。 エンジニアであれば新サービス開発にはモダンな先端技術を導入したいものですが、 組織の 全体最適 を考えた結果、技術導入を見送ることも珍しいことではないかと思います。 今回、鈴木の紹介ではこのようなさまざまな経緯を踏まえて「導入しなかった技術」に着目し、 LADR(Lightweight Architecture Decision Records) という手法を紹介させていただきました。 技術検討から判断にいたるまでの意思決定の過程を可視化できるため、多くの ステークホルダー の関わる組織でプロダクト開発にお悩みの方には、ぜひ鈴木作成のテンプレートをご活用いただければと思います。 LADRを導入する上でのメリット・デメリットについても語られているため、多くのエンジニアにお役立ていただけるはずです。 logmi.jp speakerdeck.com はじめてのフロントエンド・バックエンド分離 2本目は楽楽勤怠でバックエンドエンジニアとして関わる西角より、 ラク ス初のフロントエンド・バックエンドを分離したプロダクト開発で得られた知見を紹介させていただきました。 2019年から開発がスタートした楽楽勤怠では、フロントエンドとバックエンドとのインターフェースとなるWeb API の開発を進めるにあたり、チーム間の連携にいくつかの課題を抱えていました。 フロントエンド・バックエンド双方のチームの生産性をより上げるための取り組みは、チーム開発に関わる日本中のエンジニアの皆様に参考にしていただけるはずです。 ラク スにとっても西角と楽楽勤怠チームが試行錯誤を繰り返して得た知見は、今後のプロダクト開発の大きな財産になっていくでしょう。 logmi.jp speakerdeck.com 新規プロダクトの開発速度と品質の両立を支える自動テスト 3本目は福岡より、楽楽 労務 での自動テストの取り組みの紹介です。 タイトルの通り、楽楽 労務 では開発のスピードと品質を両立するためにエンジニアが様々な取り組みを行っています。 今回はその中でも不可欠な3種類の自動テストを語らせていただきました。 各テストがもたらす恩恵、またどんな観点での品質担保を目的としてテストを作っていくのが最適なのか、開発の最前線に関わる福岡が詳細に紹介しています。 ユニットテスト ( JUnit ) アーキテクチャ テスト(ArchUnit) E2Eテスト(Puppeteer) テストコードを書くことが当たり前の文化となっている楽楽 労務 の開発チームから、品質担保と早期のプロダクトマーケットフィットの両立を実現する理想的な開発フローを知っていただけると幸いです。 logmi.jp speakerdeck.com 積極的に AWS サービスと自動化を使ってto Bの SaaS をローンチしたその後 最後はインフラエンジニアの柏木より、to B向け SaaS に AWS を活用する際のポイントを紹介させていただきました。 いまや IaaS の王道である AWS では数多くのサービスを利用可能ですが、楽楽 労務 をはじめとした大規模 SaaS を運用する柏木はインフラエンジニアとしてより長期的な目線に立ち、本質を捉えた利用ポイントを語っています。 後半に紹介される事例でも単に自動化を行うのではなく「なぜ?」を追求しており、柏木の理論から ラク スに根付く戦略志向を感じ取っていただけるはずです。 logmi.jp speakerdeck.com おわりに 新規プロダクトにおける ラク スの取り組み、いかがでしたでしょうか? 新たなプロダクトの開発に奮闘するエンジニアの皆様に参考となる情報があれば幸いです。 さて、次回9/16(水)のMeetupでは 『プロダクトを持続的に開発・運用し続けるための取り組み』 を紹介する予定です。 長く幅広く SaaS を展開する ラク スだからこそできる開発戦略にご注目いただければと思います。 皆様のご参加をお待ちしています! rakus.connpass.com
先日行われました、フロントエンドLT会 vol.1 -2020夏祭り- にて初LTを無事終えました。logy0704です。 rakus.connpass.com 今回はLT会で発表した内容に加えて、スライドには収めきれなかった話について書きたいと思います。 speakerdeck.com Nuxt.jsとfirebaseに興味を持ったきっかけ 他にハマったこと RealtimeDatabase vs FireStore おわりに Nuxt.jsとfirebaseに興味を持ったきっかけ vue.jsとfirebaseの組み合わせについては、過去のエンジニアブログでも何度か触れられていたこともあり、いつか触りたいという思いがありました。 tech-blog.rakus.co.jp tech-blog.rakus.co.jp 加えて、環境構築周りの負荷軽減(vuex, vue-router)、将来的にPWAにも触れてみたいというモチベーションでNuxt.jsとfirebaseの組み合わせに至りました。 他にNuxt.jsを採用する理由としては、 SSR (サーバーサイド レンダリング )が思い浮かびますが、今回の学習ではSPAモードを利用し、 SSR は採用しませんでした。 理由は、スライドにもある通り、バックエンド周りのことを極力気にしたくなかったからです。 他にハマったこと RealtimeDatabase vs FireStore これはハマったことというよりも知見がなかったため、考察に時間がかかったというほうが正しいです。 どちらもFirebaseによって提供される クラウド ホスト型 NoSQL データベースですが、似て非なる特徴を持っています。 具体的な選定ポイントについては、こちらのページを参考にさせていただきました。 firebase.google.com ページの記載からは、ややFireStore推しであるようにも受け取れますが、それぞれの特徴を踏まえた選択を行う必要があります。 特に、データモデルが異なるため、一度片方を利用し始めた後に、もう一方に乗り換えるのはなかなか難しいため注意が必要です。 今回のアプリでは、Realtime databaseを選択しました。 理由は、簡易なTODOアプリとしての利用であれば、FireStoreのような複雑なクエリは必要ないと判断したからです。 実際の商用レベルのアプリであれば、将来の機能拡張を加味した検討が必要になると思います。 おわりに 100人以上が聞いてくれている中での発表でLTデビューというのは多少緊張しましたが、良い経験になりました。 他の方の発表が良い刺激になったことは勿論、いざ自分が発表するとなると、あれこれ準備したりする分、やりっぱなしよりも知識が定着する気がします。 これが登壇駆動開発…!
こんにちわ @kawanamiyuu です。今回は私の所属する 楽楽労務 の開発チームで運用している コードレビュー ガイドライン とコードレビューにまつわる少し変わった取り組みについて紹介しようと思います。 楽楽労務の開発体制 コードレビューガイドライン策定の背景 コードレビューガイドライン策定の目的 レビュー指摘の重要度 コードレビューの工夫 「おやつ」という発明 おわりに 楽楽 労務 の開発体制 チーム 開発拠点は東京と大阪 1 チームあたり 3 〜 5 名の、合計 3 チーム(+ スクラム マスター等)で スクラム 開発 *1 私はそのうちの 1 チームのリーダーを担当 ツール GitLab でソース管理し、Merge Request *2 を活用してコードレビューを実施 レビューを通ったコードがメインブランチにマージされる 開発タスク(PBI *3 、SBI *4 )は Trello で管理 コードレビュー ガイドライン 策定の背景 開発規模拡大のための人員増とそれに伴う複数チーム体制への移行により、 チームごとにコードレビュー指摘の重要度や観点にばらつきがある 開発メンバーが自身の成長を測る指標として、Merge Request の差し戻し回数より細かい粒度の情報を収集しづらい といった課題が出てきていました。 コードレビュー ガイドライン 策定の目的 これらの課題を解決するためのコードレビュー ガイドライン を策定し、次の実現を目指しています。 開発チーム全体で同じ基準でコードレビューを行い、修正要否を判断できるようにする 開発メンバーがレビュー指摘を重要度や観点ごとに分析し、自身のふりかえりに活かせるようにする 観点を明文化することで、レビューイーのセルフチェックやレビュアー育成のインプットとして使えるようにする レビュー指摘の重要度 コードレビューで発生する指摘には必ず優先度を付け、プロダクト品質に対する重要度を表現します。 重要度 説明 MUST 必ず修正すべき。われわれが期待する当たり前品質に達しておらず、そのままではリリースできない。今放置するとあとで大きな負債になる SHOULD 可能なら修正すべき。リリーススケジュールを優先するため戦略的に修正を見送ることもできるが、その場合は次バージョンで修正を検討すべき IMO 修正判断はレビューイーに委ねられる。別解の提案や現時点では判断が難しい課題など、レビュアーの意見に過ぎない レビュアーはこれらの「重要度」に「観点」 *5 を加えて、Merge Request 上のコードに対してレビュー指摘を記述します。 <レビュー指摘の記述例> コードレビューの工夫 MUST レベルのレビュー指摘はリリース品質を満たしていないことを意味するため、必ず修正する必要がありますが、SHOULD レベルのレビュー指摘の修正判断には裁量があります。 私がリーダーを担当するチームでは 「SHOULD レベルのレビュー指摘はすぐには修正せずいったん負債として積み上げる」 という運用を試みています。平たくいうと、未修正の SHOULD レベルのレビュー指摘が残っていても Merge Request をマージしてよいことにしています。 これは、 まずはリリース可能品質到達を最速で目指すことで、開発の後工程のスケジュール上のリスクを減らしたい コードの問題を「いつ」「どの程度ちゃんと」修正するかという意思決定によって開発速度と品質のバランスを調整したい という考えが背景にあり、担当チームメンバーと開発チーム全体に説明のうえ、このような工夫を試しています。 この運用によって後回しにした負債タスクも Trello 上で管理しています。 その名も「 SHOULD 」レーン(まんま)です。 このレーンのタスクは、実装タスクのレビュー待ち時間やスプリントの切れ目など、主に開発業務のリードタイムが発生したときに、開発業務の 箸休め的なタスク として、各自が自主的に取って解消していく運用としています。 「おやつ」という発明 さて、いよいよ本題ですが、最近のスプリント終了時の振り返りで、私のチームのあるメンバーが「今回のスプリントはタイミングよく負債タスクを消化できた。おやつ感覚だった。」と発言しました。その瞬間、メンバー全員が「これだ!」と思いました。 そして 爆誕 したのが「 おやつ 」レーン(名前変えただけ)です。 この並びに「おやつ」の文字が並ぶのは面白いですね。 プログラマー にとって名前付けはとても重要です。 その対象の特徴を適切に捉えた良い名前が与えられることで、非常にすっきりとその世界観を理解することができます。 また、今回、負債タスクに「おやつ」という名前がついたことで、負債というネガティブなイメージがポジティブなイメージに変わり、負債解消に前向きに取り組むことができるようになりました。 おわりに 今回紹介したコードレビューに対する取り組みは、現状うまく回っています。開発中に発生した一部のレビュー指摘を負債として一時的に後回しにはするものの、「おやつ」感覚で適宜消化し、負債を溜め込まない状態をキープできています。 私たちの取り組みが、開発速度と品質を両立し、技術的負債に対して前向きに取り組みむためのア イデア となれば嬉しいです。 -- P.S. その後、次のような名言(迷言)も生まれています。 おやつの鮮度が落ちると背景理解に時間がかかる = 負債を放置すると、実際に修正しようとしたときにどんなコンテキストでの指摘だったのか思い出して理解するのに時間がかかる おやつを食べすぎた = メインの実装タスクよりも負債の解消を優先してしまい、実装タスクの完了が遅れた *1 : LeSS のようなイメージ *2 : GitHub でいうところの Pull Request のこと *3 : Product Backlog Item *4 : Sprint Backlog Item *5 : 外部品質上の指摘観点:「不具合」「互換性」etc。内部品質上の指摘観点:「理解容易性」「変更容易性」etc。
はじめに こんにちは。dd_fortです。 前回に引き続き、Dockerについての話になります。 Dockerの学習中に詰まった権限についての問題と、その解決法を紹介します。 はじめに ボリューム(Data Volume)とは permission denied が発生する問題 解決法 解決法1:マウントしたボリュームの権限を書き換える 解決法2:ユーザ情報の書かれたファイルを読み込み専用でマウントする 解決法3:コンテナ作成時にユーザとグループを追加する まとめ ボリューム(Data Volume)とは ボリュームとは、データを永続化できる場所を指します。 Dockerのコンテナ内部のデータはコンテナ破棄をすると消えてしまうため、 データを永続化させるための手段として ボリューム(volume) が存在します。 コンテナ内部の永続化の詳しい内容については、こちらの記事を参照ください。 tech-blog.rakus.co.jp permission denied が発生する問題 Linux 上でDockerコンテナ内でボリュームをマウントした際に、ホストからアクセスした場合に パーミッション の問題が発生することがあります。 ホストOSで使っているユーザとコンテナ内で使っているユーザのUIDと GID が不一致になることが原因のようです。 そのため、Docker for Mac / Windows ではほとんど発生しません。 $ id uid=1000(test) gid=1000(test) groups=0(test) # (コンテナ内) # id uid=0(root) gid=0(root) groups=0(root) 発生する環境 Linux ( Ubuntu , CentOS etc) WSL2 解決法 解決法1:マウントしたボリュームの権限を書き換える permission deniedが発生した ディレクト リ、ファイルの権限を直接書き換えることで解決することができます。 $ chmod 777 /var/project しかし、permission denied が発生しているファイル、フォルダのすべての権限を書き換えないといけないため、根本的な解決にはなりません。 解決法2:ユーザ情報の書かれたファイルを読み込み専用でマウントする コンテナ内のUIDと GID がホストOSと同じになるように、コンテナ起動時にUIDと GID を指定します。 また、 /etc/passwd との不整合が起きないようにホストOSの /etc/passwd をマウントする必要があります。 最後に、コンテナから /etc/passwd を書き換えないようにread only ( :ro ) でマウントします。 $docker run \ -u "$(id -u $USER):$(id -g $USER)" \ -v /etc/passwd:/etc/passwd:ro \ -v /etc/group:/etc/group:ro \ -it ubuntu 実行結果 $ id uid=1000(test) gid=1000(test) groups=1000(test) # (コンテナ内) test@hogehoge:~$ id uid=1000(test) gid=1000(test) groups=1000(test) 問題なくコンテナ内とホストOSでユーザ情報が共 通化 されていることが確認できました。 ただしこの方法のデメリットとして、別プラットフォーム間ではDockerファイル等を共有することができなくなります。 解決法3:コンテナ作成時にユーザとグループを追加する ホストOSのユーザ(UID)、グループ( GID )を 環境変数 として渡し、コンテナ内でユーザとグループを追加します。 # Dockerfile FROM ubuntu:latest RUN apt update \ && apt -yq dist-upgrade \ && apt install -yq --no-install-recommends \ sudo COPY entrypoint.sh /var/tmp CMD bash -E /var/tmp/entrypoint.sh && /bin/bash コンテナが終了しないように /bin/bash で対話モードで動かし続けます。 # docker-compose.yml version: "3" services: override: image: example/override-ids build: . container_name: override environment: - USER_NAME - USER_ID - GROUP_NAME - GROUP_ID volumes: - ./:/mnt/working tty: true 環境変数 で、ユーザ(UID)、グループ( GID )を渡すように設定します。 # entrypoint.sh useradd -s /bin/bash -m ${USER_NAME} export HOME=/home/${USER_NAME} usermod -u ${USER_ID} ${USER_NAME} groupadd -g ${GROUP_ID} ${GROUP_NAME} usermod -g ${GROUP_NAME} ${USER_NAME} useradd 、 groupadd でユーザとグループを追加。 usermod でユーザとグループを設定。 コンテナをビルドして実行します。 $ docker-compose build $ USER_NAME=$(id -un) \ USER_ID=$(id -u) \ GROUP_NAME=$(id -gn) \ GROUP_ID=$(id -g) \ sudo -E docker-compose up -d 実行結果 $ id uid=1000(test) gid=1000(test) groups=1000(test) $ docker exec -it override su - $(id -un) test@hogehoge:~$ id uid=1000(test) gid=1000(test) groups=1000(test) まとめ Dockerのコンテナ内にvolumeをマウントする際は、UID/ GID を正しく設定しなければなりません。 簡単な手段としての解決法は1,2ですが、解決法3を使うとほとんどの問題を解決することができます。 Dockerではまだまだ勉強を始めたばかりなので学習を進めていこうと考えています。 エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 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
はじめに こんにちは、yk_itgです。 これまでいくつか PostgreSQL の記事を作成しましたが、今回は知っていると便利だと思う テーブル・DBの閲覧・コピー に関するtipsをまとめてみました。 私はテストを実施する時に結果を確認したり、データを用意する際によく使います。 PostgreSQL ユーザなら必須のテクニックを紹介していきますので、どうぞお役立てください! はじめに テーブルを閲覧する テーブルの情報を確認したい:\d テーブルの一覧を確認したい:\dt 実行結果を見やすくしたい:\x 実行結果をファイルに出力して確認したい:\o DBを閲覧する DBの一覧を確認したい:\l テーブルをコピーする テーブルの構造をコピーしたい:CREATE TABLE (LIKE) テーブルのレコードをコピーしたい:INSERT INTO SELECT DBをコピーする DBをコピーしたい:createdb -T DBをコピーしたい:pg_dump 最後に 関連記事 テーブルを閲覧する テーブルの情報を確認したい: \d \d {テーブル名} を psql 上で実行するとテーブルの情報を表示することができます。 インデックスやシーケンスなどテーブル以外のリレーションを表示することも可能です。 postgres=# \d test テーブル "public.test" 列 | 型 | 照合順序 | Null 値を許容 | デフォルト ------+---------+----------+---------------+------------ id | integer | | not null | hoge | text | | | 'a'::text インデックス: "test_pkey" PRIMARY KEY, btree (id) "test_hoge_idx" btree (hoge) postgres=# \d test_pkey インデックス "public.test_pkey" 列 | 型 | キー? | 定義 ----+---------+-------+------ id | integer | はい | id プライマリキー, btree, テーブル "public.test" 用 テーブルの一覧を確認したい: \dt \dt を psql 上で実行するとテーブルの一覧を表示することができます。 テーブル以外にも \di 、 \ds 、 \dv を使えば、それぞれインデックス、シーケンス、ビューの一覧を表示することができます。 postgres=# \dt リレーション一覧 スキーマ | 名前 | 型 | 所有者 ----------+------+----------+---------- public | test | テーブル | postgres (1 行) 実行結果を見やすくしたい: \x \x を psql 上で実行すると SQL やメタコマンド等の実行結果を拡張表示(縦に表示)することができます。 例えば、カラムが多すぎてターミナルで折り返して表示されてしまう場合に便利です。 逆に見づらい場合には、もう一度 \x を実行すると元の表示に戻ります。 postgres=# SELECT * FROM test; id | hoge1 | hoge2 | hoge3 | hoge4 | hoge5 | hoge6 | hoge7 | hoge8 | hoge9 | hoge10 | hoge11 | hoge12 | hoge13 | hoge14 | hoge15 | hoge16 | hoge17 | hoge18 | hoge19 | hoge20 ----+------------+-------+-------+-------+-------+-------+-------+-------+-------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+-------- 1 | ABCDEFGHIJ | | | | | | | | | | | | | | | | | | | (1 行) postgres=# \x 拡張表示は on です。 postgres=# SELECT * FROM test; -[ RECORD 1 ]------ id | 6 hoge1 | ABCDEFGHIJ hoge2 | hoge3 | hoge4 | hoge5 | hoge6 | hoge7 | hoge8 | hoge9 | hoge10 | hoge11 | hoge12 | hoge13 | hoge14 | hoge15 | hoge16 | hoge17 | hoge18 | hoge19 | hoge20 | 実行結果をファイルに出力して確認したい: \o \o {ファイルパス} を psql 上で実行すると実行結果を指定したファイルに出力することができます。 ターミナル以外で実行結果を確認したい場合や保存したい場合に便利です。 SQL 実行後に \o を実行すると出力先を標準出力に戻すことができます。 postgres=# \o result.txt postgres=# SELECT * FROM test; postgres=# \o postgres=# SELECT * FROM test; id | hoge ----+------------ 8 | ABCDEFGHIJ (1 行) result.txt id | hoge ----+------------ 8 | ABCDEFGHIJ (1 行) DBを閲覧する DBの一覧を確認したい: \l \l を psql *1 上で実行するとDBの一覧を表示することができます。 または psql -l でターミナルを起動せずに表示することもできます。 postgres=# \l データベース一覧 名前 | 所有者 | エンコーディング | 照合順序 | Ctype(変換演算子) | アクセス権限 -----------------------------------+----------+------------------+----------+-------------------+----------------------- postgres | postgres | UTF8 | C | C | template0 | postgres | UTF8 | C | C | =c/postgres + | | | | | postgres=CTc/postgres template1 | postgres | UTF8 | C | C | =c/postgres + | | | | | postgres=CTc/postgres (3 行) テーブルをコピーする 続いてはコピー作業のテクニック、まずはテーブル編の紹介です。 なお、これからご紹介する内容は、本番環境での利用は想定していません。 誤って重要なデータの書き替えが発生してしまう可能性もありますので、ご利用にはくれぐれもご注意ください。 テーブルの構造をコピーしたい: CREATE TABLE (LIKE) CREATE TABLE {コピー先} (LIKE {コピー元} ) *2 の SQL を実行するとコピー元と同じ構造のテーブルを作成することができます。 LIKEの中で INCLUDING ALL を指定すると構造だけでなく、制約やデフォルト値、付与されているインデックス等もコピーすることができます。 postgres=# CREATE TABLE test2 (LIKE test); CREATE TABLE postgres=# \d test2 テーブル "public.test2" 列 | 型 | 照合順序 | Null 値を許容 | デフォルト ------+---------+----------+---------------+------------ id | integer | | not null | hoge | text | | | postgres=# CREATE TABLE test3 (LIKE test INCLUDING ALL); CREATE TABLE postgres=# \d test3 テーブル "public.test3" 列 | 型 | 照合順序 | Null 値を許容 | デフォルト ------+---------+----------+---------------+------------ id | integer | | not null | hoge | text | | | 'a'::text インデックス: "test3_pkey" PRIMARY KEY, btree (id) "test3_hoge_idx" btree (hoge) テーブルのレコードをコピーしたい: INSERT INTO SELECT INSERT INTO {テーブル名} SELECT * FROM {テーブル名} の SQL を実行するとSELECTの結果をそのままコピーすることができます。 そのままコピーすると主キー制約に引っかかる場合には、 CREATE TABLE {コピー先} (LIKE {コピー元} ) を使ってtempテーブルを作成し、変更したいカラムのみをUPDATEしてからINSERTすると主キーのカラム以外を考えずにコピーすることができます。 postgres=# SELECT * FROM test2; id | hoge ----+------------ 10 | ABCDEFGHIJ (1 行) postgres=# INSERT INTO test2 SELECT * FROM test2; INSERT 0 1 postgres=# SELECT * FROM test2; id | hoge ----+------------ 10 | ABCDEFGHIJ 10 | ABCDEFGHIJ (2 行) postgres=# SELECT * FROM test; id | hoge ----+------------ 1 | ABCDEFGHIJ (1 行) postgres=# CREATE TEMPORARY TABLE temp_test (LIKE test); CREATE TABLE postgres=# INSERT INTO temp_test SELECT * FROM test WHERE id = 1; INSERT 0 1 postgres=# UPDATE temp_test SET id = 2; UPDATE 1 postgres=# INSERT INTO test SELECT * FROM temp_test; INSERT 0 1 postgres=# SELECT * FROM test; id | hoge ----+------------ 1 | ABCDEFGHIJ 2 | ABCDEFGHIJ (2 行) DBをコピーする テーブルの次はDB編のご紹介です。 こちらもテーブル編と同様、本番環境での利用は想定したものではありません。 環境へ重大な影響を及ぼす内容となりますため、ご注意の上での利用をお願いします。 DBをコピーしたい: createdb -T createdb -T {コピー元} {コピー先} *3 のコマンドを実行するとコピー元DBの内容でコピー先DBを作成することができます。 後述の pg_dump と比較すると実行速度が速い印象です。 >psql -U postgres -d postgres postgres=# \dt リレーション一覧 スキーマ | 名前 | 型 | 所有者 ----------+-------+----------+---------- public | test | テーブル | postgres public | test2 | テーブル | postgres public | test3 | テーブル | postgres (3 行) >createdb -T postgres -U postgres postgres2 >psql -U postgres -d postgres2 postgres2=# \dt リレーション一覧 スキーマ | 名前 | 型 | 所有者 ----------+-------+----------+---------- public | test | テーブル | postgres public | test2 | テーブル | postgres public | test3 | テーブル | postgres (3 行) DBをコピーしたい: pg_dump pg_dump {コピー元DB} > {dumpファイル} *4 のコマンドを利用するとデータベースをバックアップするdumpファイルを作成することができます。 作成したdumpファイルを psql {コピー先DB} < {dumpファイル} でリストアすることによってコピー先のDBにコピー元のDBをデータを再現することができます。 前述の createdb -T と比較すると、dumpファイルがあれば同じサーバにコピー元のDBがなくてもコピーできる点で汎用的です。 また、 -n や -t のオプションを指定すると一致する スキーマ やテーブル単位でコピーすることもできます。 > pg_dump -U postgres postgres > postgres_dump > ls -1 | grep postgres_dump postgres_dump > createdb -U postgres -T template0 postgres3 > psql -U postgres -d postgres3 < postgres_dump > psql -U postgres -d postgres3 postgres3=# \dt リレーション一覧 スキーマ | 名前 | 型 | 所有者 ----------+-------+----------+---------- public | test | テーブル | postgres public | test2 | テーブル | postgres public | test3 | テーブル | postgres (3 行) 最後に PostgreSQL でのテーブル・DBの閲覧・コピーに関してご紹介してみましたが、いかがでしたでしょうか。 効率的な作業のお役に立てば幸いです。 関連記事 tech-blog.rakus.co.jp tech-blog.rakus.co.jp tech-blog.rakus.co.jp エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 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 : https://www.postgresql.jp/document/9.3/html/app-psql.html *2 : https://www.postgresql.jp/document/9.3/html/sql-createtable.html *3 : https://www.postgresql.jp/document/9.2/html/app-createdb.html *4 : https://www.postgresql.jp/document/9.2/html/app-pgdump.html