転職して半年経ったので振り返ってみる

自分が今の会社に入って半年くらい経ったので社内でおこなってきた活動を列挙し今後どのようなアクションを実行していくのかを書きたいと思います。

雑多なポエム的なものになっちゃいますがお付き合いください。

やったこと

  • 輪読会
  • スクラムマスターのみでの振返り(2週に一回)
  • PHPUnitの勉強会
  • フロントエンドのLint設定モブプロ
  • フロントエンドの共通化促進
  • チームのPMと日々1on1
  • その他
    • 提案等

振り返ってみて色々やったなーという感がありますね。かいつまんでそれぞれどんなことを行い、社内にてどんな影響があったかなどを主観を元に書いていきたいと思います。

実際の活動内容

輪読会

前職での経験も踏まえて提案を行い実際に開催

  • 開催目的

    • チーム(事業部)の垣根を超えたコミュニケーション
    • 自分達が作業をする上でプラスになる知識がつけれる
  • 輪読会の流れ

    • 最初の45分前後を使って対象書籍を回し読み
    • 最後の15分前後を使用して当日読んだ箇所に関連する文脈を話題にした議論タイム

当初は議論の時間を設けておらず読み進めていく途中で議論できそうなことを拾っていましたが、参加している方から枠を設けての議論をする時間が欲しいとのことで上記のような流れになりました。

  • 影響

    • 参加者が違うチームにいたり、専門領域が異なっていたりしている中でそれぞれの視点から意見や考えが出てきた
    • 第二回目からBizサイドも巻き込んだ輪読会になる
  • 今後

    • 事前に資料を作成したりして発表をするような輪読会ではないので、輪読会の場で発散や出たアイディアが蓄積されていない点の改善
    • 二冊目の輪読会のメンバーも集まり、開発メンバーだけでなくBizからも参加者が生まれたのはとてもいい傾向です

スクラムマスター振返り

  • 前提

    • 弊社では、スクラムチームが3チーム存在しており、それぞれにスクラムマスターが存在しています。(認定CSM)
  • 開催目的

    • 各チームが独立しており、コミュニケーションや今現在チームで起きている問題、開発している内容が不透明な状態をスクラムマスターだけでも共有し合おう
    • スクラムマスター同士手を取り合って課題に対して相談し合える環境を作る
  • 振返りの流れ

    • YWTという簡易的な振り返りのフレームワークを使用しています
    • チーム内の振返りで出たプロブレムやアクションの共有
    • 他のスクラムマスターへ聞きたいこと
    • 共有(チーム横断的なものとか)
  • 今後

    • よりいい方向にチーム(組織全体)が進むための活動と捉えて引き続き定期開催していくつもりです

PHPUnitの勉強会

  • 開催目的

    • テストがないことによりコードが何をしているのか仕様的に正しいのかちゃんと動いているのだろうかなどが不明になっていた
    • テストを書く文化が根付いておらず、テストがないPRが常態化していた
  • 基礎編と応用編を行う

    • 基礎編は、単純なFeatureテストの実装について
    • 応用編は、Unitテストの作成でdataProviderやReflectionについて
  • 影響

    • 少しづつだがテストを書こうという意識が芽生えてきた気がする
  • 今後

    • テストを書くというのが実装とセットになっているという文化にする
    • テスト書けない人がいない世界にする

フロントエンドのLint設定モブプロ

  • 開催目的

    • Lintがメンテされていない
      • ルールが古い
    • ディレクトリによって違う
    • Lintに対して意識が低い
  • 若手のフロントエンジニアをドライバーとして実際にモブプロを行う

    • シニアエンジニアの方の力も借り、形にすることができ、ドライバーになったエンジニアにとってもいい経験になったのではないかなと思います
  • その後

    • 歴史的経緯から即座に反映させることが困難状態であることが判明したため、いまだに反映までは至っておらず自分が部署移動したら順次着手するタスクになってそうです

フロントエンドの共通化促進

  • 目的

    • 機能毎にディレクトリが分かれており、またVueのインスタンスが異なっている。なんでもVuexにぶっ込んでおり剥しづらい状態を徐々に剥がしていき、Vuexでの管理からも適切なもの以外を取り除いていくこと想定して共通化を図っていく
  • 影響

    • 既存にある程度の影響が生まれるのは仕方ないという状態ではあるので結合テストの範囲が広くなってしまう
      • E2Eが網羅率も低くどうしても人力になってしまう状態
    • 少しずつ共通化が進行している
  • やったこと

    • 自分が担当した機能のフルリニューアルで既存のBlade(Laravelで使うテンプレートエンジン)からVueへの移行を行うタイミングで共通化(簡略的なAtomicデザイン)をおこなっていきました
  • やったことの結果

    • 影響にも書きましたが共通化は、このフルリニューアルでベースとなる構成を他チームに示せたこと
    • また事前に他チームに対しても設計の面に関して共有していました

チームのPMと日々1on1

  • 目的

    • チームをより良い状態にするため
    • PMとしての自分が理解している範囲、経験からの提案など
  • どうして日々の1on1なのか

    • 週一、月一でもよかったかもしれませんがチームとして前進するため毎日の業務で躓いていることなどを言語化してもらいどこに課題があり、解決したいのか
    • 右も左もわからない状態で教えてくれる人もいない中で進むくらいなら自分と伴走して一緒にチームの開発や設計に関して円滑に進めるように日々解決していくのがベストかと思ったため
  • 結果

    • 他チームのPMと比べてもしょーもないですが、間違えなく成長してくれていると思います。これは、俺のアドバイスがあったからではなく、彼自身の真摯な姿勢や理解し実行し検証し着実に一歩一歩進んで行こうとする足を止めなかったからだと思います
    • 最近の1on1は、課題解決云々よりも雑談のが増えていますが開発のスピードが落ちているわけでもないのでポジティブに捉えてます

その他

  • あげたらキリがなさそうですが列挙します
    • MTG前の準備(MiroやNotionなどの資料作成)
    • MTGアジェンダ作成
    • 他者へのファシリテートの移譲
    • プロダクトに関連するミドルウェアなどのversion up
    • テストに関してのコーディング規約やディレクトリ構成などのたたき作成
    • 役員との1on1
    • 合同共有会の根回し
    • モブプロ、ペアプロの開催(チーム内で)
    • レトロスペクティブで感謝の時間(スプリント内でメンバーはもちろん社内で誰かに感謝を表明する時間)

最後に

入社して約半年ですがまぁー色々やっている気がするなーと振り返って思いました。ただこれらは、やりたくてやったという色もあるので今後は、メンバーや部署内から同じように提案が出てきて皆で挑戦していけるような文化になることがいいと思っています。

また新入社員であれこれと口うるさい人間が社内でこういった行動ができる環境は、ベンチャーならではと感じてます。

会社として、組織として改善は、まだまだ山積みで人からすれば遠ざけたくなるようなことや触れたくないようなこともたくさんありますが今後の自分のキャリアを考えるとこういった経験は、なかなか出会えることはないとも思っています。

長くなりましたが、最後までポエムに付き合っていただきありがとうございます

スクラム開発の立ち上げに携わる

はじめに

  • 気づいたら一年振りのブログです
  • 職場も新しくなり右往左往しています

この記事の概要

  • スクラム開発立ち上げの時期に入社したのでその際に感じたことなどを書きます
    • 厳密には、すでに移行の準備は始まっており、自分が入社した段階ではチーム開発を始める手前の状態でした
  • ポエムっぽい側面もあります。

スクラム開発スタート

前提

見積もりは時間ではなく相対的なポイントで見積もる

  • 個人的な観点ではなしますと時間で見積もれるならそれはもう不確実性がなく、何をどう実装しどれくらいで終わるか明瞭であるため、スクラムで開発する必要性はないと思いますので、まぁスクラムでやらなくていいんじゃないんですか?と思っています

  • 不確実性とは?

    • ふたしかなこと と定義されています。
システム開発においての不確実性とはどんなことがあるのでしょうか?
  • 例えば
    • 影響範囲、結合度が高い実装がされている場合至る所に影響が発生します。これは予め予測が可能なものではないです。
    • 外部サービスを使用している場合、社内に知見がなかったらドキュメントを読み解きながらの実装になりますので想定より時間がかかってしまうことが多いですね。
    • また途中で考慮漏れに気付いてのタスクの追加、設計の見直しなども起こりうることですね

他にも様々なことが起因になり、想定外のことが発生し、時間を使うこともしばしばあると思います。

時間で見積もりを出すとどうなる?

  • 時間という概念は、皆が平等に共通認識を持っています。15分は15分以上でもなく以下もなく15分です。なのでその時間内に終わらせないといけないという縛りがメンタル的に発生します。そうなると心理的ハードルがあがります。また焦りも生まれ、その時間内に終わらせるために気づかないうちにバグを生み出しているかもしれません。そうなると本末転倒ですね。なので時間で見積もる必要性がないです。また不確実なことに対して時間を見積もれるわけがないです。

  • また時間というのは、あればあるだけ使えばいいという発想になりがちです。締め切りがm月d日だったとしてぎりぎりその日までに終わればいいやとなります。実際は、もっと早く終えられるかもしれないのにです。

    • ただしポイントにしたからといってここが完全に解決されるわけでもありません

結果正しい見積もりって可能なの?

  • 結論は完璧には、無理です。

    • なので不確実性に対して学習をしていき、スクラムイベントのスプリントプランニングで決める計画がスプリント内で完走できる計画になるように確度を上げていく姿勢(学習だったり、のうはうの蓄積)が大事になります。
  • もし社内で正しい見積もりが必要であるなら概算を出すことは可能だが前後するという共通認識は必要ですね。

    • スクラムを続けていくことにより確度の高いリリース予定日は想定可能になっていくと思いますが

守破離を意識しましょう

  • スクラムに限ったことじゃないですが、未経験の未知なことに対して分からないことがわからないままカスタマイズするのは、かなり悪いミス(悪手)です。

    • わからないことが悪いことではないですが、経験した上でオリジナルに昇華させるのが大事です。
    • また経験も自らが学んだこと出なければいけないと思います。事実に基づいてがとても大事ですね
  • またルールがあるということは、そのルールに従うことを想定したフレームワークになっているので原則は、従うのがベストだと思います。

守破離の守を守らずに始めるとどうなるか?

  • スクラムをやっている気になることです。
    • 中途半端な状態でなんとなくうまくいっているふうに感じてしまっている状態と言えそうですね。
    • スクラムは経験主義なので経験し学習し、改善し自分達にあったやり方を見つけていき、不確実性に対して向き合うことが大事だからです。
    • やっている気になると、問題点に対して誤った解釈し、ミスリードされたまま話が進みレールから外れた状態に陥ります。

チームでの動きが始まってから

  • まず僕がスクラムマスターに就任しました。ただこれも開発者との兼務なので良くない状態です。
    • CSM(認定スクラムマスター)を取得しました。

やったこと

  • スクラムがわからない人で集まって事前勉強会
  • 他のチームのスクラムマスターとの振り返り
  • チーム内で率先としてスクラムマスターの動きなどを行う
    • タスクのやる目的、タスクが終わる状態の確認(受入条件の確認)
    • MTG時にアジェンダの作成や空中戦を避けるための資料作成など
  • SCRUM BOOT CAMPの輪読会を開催

スクラムの文脈とは異なることもありますがどれも不足していると感じたため行動しました。

今後の課題

  • 責任の所在など
    • ここは、個人の領域から離れてしまうため事業部全体でどうしていくかの共通認識を取る必要がありそう
    • 肩書き上は、明確には分かれているように外から見えなくもないがひどく曖昧な状態と感じている
  • 開発者のスキルの底上げ(現状webとmobileのエンジニアが混在している)
    • 誰がどのタスクをとっても作業できる状態になるといいんですが領域が異なるため基本的には、各人の領域でのスキルアップが当面の目的になりそう
    • スキルの底上げにフォーカスして考えるモブプロかペアプロを常に行う必要性がある
  • スクラムイベントの目的の再共有
  • 複数のスクラムチームが存在しているためLeSSへ昇華させるよう働きかけ

他にもたくさんの課題が潜在的にも表面的にありそうです。が課題等は、積極的にみつけて改善していきたいですね。

まとめ

  • スクラムは、経験者がいればどうにかなるってこともなく、大事なのは、アジャイルスクラムに対してのマインドセットな部分も強いんだろうなと感じてます。個人的には、スクラムでの開発は、スプリント内のやることが明確だったり目指すゴールが何かなどが知れたり、ウォーターフォール開発よりは好きです(対極の開発スタイルと言っているわけではないです比較対象として)

  • 今後は、開発者のロールは剥がされ、スクラムマスターとして接していく立場になる予定です。この半年でスクラムマスター兼開発者→SRE所属のスクラムマスターとなりそうです。本来ならスクラムマスターとしてだけの立ち回りがいいのでしょうが今のチームの自己組織化がまだまだ未達の状態なので(自分の至らなさですね)そのような状態になりそうです。

Kubernetesを学習するにあたって出てきた用語をまとめてみる

目的

  • 個人でKubernetes(以下k8s)を学習したときに関連用語がたくさん出てきて今現在ほとんど頭から抜けているため復習がてら用語をブログにまとめてみる

概要

  • ここでは、k8sについての概要説明等は、いたしません。以下をご参考ください。

kubernetes.io

k8sを構成する要素の概要と言葉

kubectl

  • K8s クラスタを操作するためのコマンド
  • Macならbrew install kubectlで入ります

コントロールプレーンコンポーネント

kube-apiserver

  • API サーバーは kubectl などの API クライアントからの REST リクエストを検証して API オブジェクトを構成、または、状態を報告する

kube-scheduler

  • ワークロード専用のスケジュール機能である

Kubernetesのスケジューラー | Kubernetes

kube-controller-manager

  • 制御ループを使ってシステム状態を調整する。モニタリングした現在状態から希望状態への遷移を実行する

cloud-controller-manager

Kubernetesのコンポーネント | Kubernetes

etcd

  • K8s クラスタのすべての管理データは etcd で保存される。

kubelet

  • 各ノードで動作する。
  • ボッッドとコンテナの実行
  • ポッドとノードの状態を API サーバーへ報告する
  • コンテナを検査するブローブを実行
  • 内臓する cAdvisor がメトリックスを集約して公開する
  • k8sが作成したものではないコンテナは管理しません。

kube-proxy

  • 各ノードで動作し、高可用性かつ低オーバーヘッドのロードバランシングを提供
  • サービスとポッドの変更を API サーバーで監視し、構成を最新状態に保ち、ポッド間とノード間の通信を確実にする
  • サービスの生成時に ClusterIP へのパケットをトラップして、対応するポッドへリダイレクトするように、iptables のルールを操作する
  • サービス名と ClusterIP をアドオンの DNS へ登録する

coredns

  • ポッドがサービス名から IP アドレスを得るために利用されている

kube-flannel

  • すべてのノードで実行され、複数のノードの間で IPv4 ネットワークを提供する。これによりコンテナ(ノード)は K8s 内部の IP アドレスでノードを超えて、疎通できるようになる
  • ネットワークポリシーを必要とする場合には、calico を使用しなければならない

calico-kube-controllers

  • calico のためのコントローラ。データストアとしての etcd を利用するために使われる

calico-node

  • 全てのノードで実行され、ノード間のコンテナ(ポッド)の疎通、アクセスコントロール、ルーティングを提供

kubernetes-dashboard

  • WebUI

metrics-server

  • heapster に代わり API の aggregation layer を通じて、K8s クラスタ全体のメトリクスを収集する

k8sを触る際によく出る言葉や概要

コンテナ

  • 必ずポッド内で実行する
  • 軌道に設定できる項目がある

ポッド

  • コンテナを実行するためのオブジェクトで、複数のコンテナを内包している

コントローラ

  • ポッドの実行を制御するオブジェクト

コンフィグレーション

  • コンテナ内のアプリケーションの設定やパスワードなどの情報は、デプロイされた「名前空間」から取得することが推奨されている。
  • 設定を保存する:ConfigMap(コンフィグマップ)
  • 秘匿情報を保存する:Secret(シークレット)
  • 名前空間下に保存された情報は、コンテナ内のファイルや環境変数として、アプリケーションのコードから参照できるようになる

k8sの状態を示す言葉と概要

ContainerCreating

  • イメージをダウンロード中、またはコンテナ起動進行中を表す。
  • ConfigMap や Secret をマウントできず、コンテナ生成が保留された時もこの値が表示される

CrashLoopBackOff

  • ボッド内のコンテナが終了し、次の起動まで待機状態になる。
  • コンテナ内のプロセスを見直す必要がある

Pending

  • ポッド生成の要求を受け取るが、1 つ以上のコンテナが作成されていない状態
  • リソース不足、ポリシー制約によってスケジュールできていないケースがあるため見直す

Running

  • ノードに対応づけられ、少なくとも 1 つのコンテナが実行中、開始中、または再起動中である

Terminating

  • コンテナへの終了要求シグナルが送られ、コンテナが終了するまで待機中
  • 猶予時間をすぎ、コンテナが終了できていない場合、強制終了する

Succeeded

  • 正常

Completed

  • ポッド内のコンテナが正常終了し存在している。削除されるまで存在し続ける。ポッド名を指定することでログやステータスを取得できる
  • ポッド内に複数のコンテナがある場合、第 1 コンテナが正常終了するとポッドは、正常終了として扱われる

Error

  • コンテナが異常終了した。
  • Completed の対義

Failed

  • ポッドないの少なくとも 1 つのコンテナが異常終了した

Unknown

  • 何らかの理由により、ポッドの状態を取得できない状態
  • また、ノード障害、マスターからノードの状態を取得できなくなったときにもこの表示になる

参考資料

kubernetes.io

SQLアンチパターン~第4章~

趣旨

  • 最近学習をサボっておりアウトプットも行っていない。エンジニアとして後退する老害になりかねない。こんなんじゃだめだ!という理由で書きます。
  • 最近「ん?ちょっと何言っているわからない」ってことが本業外であり、会話を成立させるには、体系的に理解しているのでなくちゃんと言語化できるレベルで理解しようねってことでまとめます。

おおまかな流れ

  • みんな大好き「SQLアンチパターン」の第4章キーレスエントリー(外部キー嫌い)を再読とまとめ
    • 物量的に全然多くなく主張も簡潔です。
  • なぜ第4章なのかの話
  • Laravel で外部キー制約を設定する
  • まとめ

第4章の主張

  • パフォーマンスが改善し、単純複雑を問わず、データの変更における参照整合性の意地に役立つよってデーターベースでのミスを未然に防ぐために外部キー制約を用いましょう。

主張に関して

  • 基本的に例外を除いて外部キー制約を使わない理由は、あまりないですよって話。

アンチパターン

  • そもそも外部キー制約を適用できない
  • DB の設計を柔軟にしなければならない

アンチパターンの見つけ方

  • A テーブルには存在するけど、B のテーブには存在しない値を調べるクエリはどう書く?
  • A テーブルの値が、別のテーブルへの挿入に使われている子を簡単にチェックする方法はある?
  • 外部キーは、DB の実行速度を遅くするからつかってはならない
    • 外部キー制約を設けることでの起きる問題より設けない時の問題の方が障害になる可能性が高い  

以上の発言があるとアンチパターンの兆候がある

なぜ第4章の話なのか

  • データ構造を追いかける際に(ER図吐き出したり)外部キーがあると大変便利でだよね。(だから使ってくださいと言いたい)

  • なので職場に外部キー設定しません!っていう人がいたら第4章をお経のように読み上げ続けましょう。個人的に外部キーがないデータの全体構造を理解するのは、できるとはおもいますが時間の浪費になるので勿体ないことになるんだろうな〜っておもっています。仕事上時間の浪費は、本当に無駄です。

Laravelで外部キー制約を設定する

  • 前提:MySQL, MariaDB

  • migration fileを作成し、fileを編集します。

php artisan make:migration CreatePosts
php artisan make:migration CreateComments
  • 今回は、外部キーを設定するのが目的なので最小限のカラムを書きます(型は気にせず)
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreatePosts extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->id();
            $table->unsignedBigInteger('user_id')->comment('user_idを格納します');
            $table->text('content')->comment('投稿内容を保存します');
            $table->timestamps();

            $table->foreign('user_id')
                ->references('id')
                ->on('users');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('posts');
    }
}
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateComments extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('comments', function (Blueprint $table) {
            $table->id();
            $table->unsignedBigInteger('post_id')->comment('posts tableのidを格納します。');
            $table->text('comment')->comment('コメントを格納します');
            $table->timestamps();

            $table->foreign('post_id')
                ->references('id')
                ->on('posts');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('comments');
    }
}
php artisan migrate
  • 注意点としては、外部キーを設定する対象の型を合わせないとならない点です
    • 最新のLaravelだとmigration fileを作成した際にデフォルトで記載ある、auto incrementsのカラムがidとなった点なりました。こちらの型(Laravel上で使用するメソッド)は、unsignedBigIntegerとなるので注意してください。
    • また外部キー制約を設定する際にindexも作成してくれているんですが、table名とid名が長いとエラーになりますので、foreign()の第2引数にindex名を指定することで回避が可能です。
    • 個別(create時ではなくaddなどで)に外部キーを設定した際は、必ずdownを記述しましょう。エラーでmigrateがエラーになります。
  • 外部キーの設定を行う際に、onDelete()という親tableへの動作に応じて振る舞いを連動させるメソッドがあるんですが、今回は、設定しません。

  • ちなみに外部キー設定などに関する情報は、information_schemaというDBに保存されます。

まとめ

  • 外部キー制約は、ちゃんと設定しましょう
  • Laravelで普通に設定する分には、なんら問題なく動作することがわかりました。しっかりFWの使える機能は、利用しましょう

続) GitHub Actionsの何か

はじめに

昨年末に実家が空き巣に入られたり、年始早々身体に異変があってメンタル的に疲れているところに自分史上間違えなく上位に入る節々の痛さと突発的高熱、その翌週には、首を盛大に寝違えてキョンシーみたいな姿勢での生活を余儀なくされました。どうも僕です。

ちなみに巷で流行っているコロナではなく、ただの風邪で2日半くらいで回復して味覚等々を失うことなく今は、元気に業務と麻雀に励んでいます。

前回のブログ

  • GitHub Actionsのcronがあるらしいってことでそれを使ってアニメの放送日にSlackへ通知しようぜ!ってのをやりました。

あれから一ヶ月

  • おかげさまでアニメを見逃す日がなくなりました!(素晴らしい)

改善点と改善案

GitHub Actionsのcronラグあり過ぎじゃね?問題

  • こちらは、無料枠で無料でGiHub様の善意の元リソースを使わせていただいているのでちょっと文句言いたいけどスルーすることにしました
    • ただ何かしらのプロダクトには、活用場面なさそうって思いました。

Jsonデータで管理するのだるい問題

  • 前回の目的は、見たいアニメの放送を見逃さない。っていう点にしか焦点は当たっておらずデータの収集や管理に関しては、全くの無関心でした
  • なので改善点としては、以下になると思います。
    • JsonデータのGitHub以外での管理
    • Jsonデータを自動で作成する

改善点挙げておいてなんですが何かと初物や久々(何かを)に触るのがいいと思っていますので、遠い昔に触ったFirebaseでJsonを管理しようかなと考えたんですがクレデンシャルをどう管理すんねんS3か?って考えたんですがこのプロジェクトは、すべてを無料で!を心がけているので却下です。(微々たる金額で済むのも想定できるんですがね)

今回の実装

スクレイピングJsonデータ作るぞの巻きに決定しました。

やらないこと

前提

import requests
from bs4 import BeautifulSoup
from os import getenv
import json
import datetime as dt
import calendar

today = dt.date.today()
dayName = calendar.day_name[today.weekday()]

if today.month < 10:
    month = f'0{today.month}'
else:
    month = today.month

weekList = {
    '月': 'Monday',
    '火': 'Tuesday',
    '水': 'Wednesday',
    '木': 'Thursday',
    '金': 'Friday',
    '土': 'Saturday',
    '日': 'Sunday'
}

load_url = getenv('SCRAPING_TARGET_URL')
html = requests.get(load_url)
soup = BeautifulSoup(html.content, "html.parser")

animeList = soup.find(class_='week_area').find_all('li')

jsonList = []
for e in soup.find(class_='week_area').find_all('li'):
    week = weekList[e.find(class_="oatime").find(class_='youbi').text]
    e.find(class_="oatime").find(class_='youbi').decompose()
    e.find('h4').find('strong').decompose()

    item = {
        'title': e.find('h4').text,
        'publish_at': e.find(class_="oatime").text,
        'channel': 'TOKYO MX',
        'day_of_week': week
    }
    jsonList.append(item)


with open(f'./data/anime/{today.year}{month}.json', 'w') as f:
    json.dump(jsonList, f, ensure_ascii=False, indent=4)

前回同様解説していきます

コードを読めばわかるって内容なので解説すべきことはないんですけどね!   スクレイピングしたあとのDOM操作に関しては、ほんと愚直な方法しか思いつかなかったので誰かいい方法があったら教えて欲しいです

  - あーどうしよっておもったDOM操作が下記のようなHTMLでした

<div class="oatime"><p class="youbi"></p>25:05~ </div>
  • こんな形のHTMLだとclass="oatime"直下のテキストを抜くと25:05〜が改行付きで取得できてしまう。なので先に曜日だけ取得して曜日のクラスのDOMを削除して時刻の取得を行うってことをしました。
    • 下記のような処理
week = weekList[e.find(class_="oatime").find(class_='youbi').text]
e.find(class_="oatime").find(class_='youbi').decompose()

さきに最終的なJsonファイルをどうぞ

[
    {
        "title": "\n7SEEDS\n",
        "publish_at": "22:30~ ",
        "channel": "TOKYO MX",
        "day_of_week": "Monday"
    },
    {
        "title": "\nたとえばラストダンジョン前の村の少年が序盤の街で暮らすような物語\n",
        "publish_at": "23:00~ ",
        "channel": "TOKYO MX",
        "day_of_week": "Monday"
    },
   ......
]
  • 最終的に前回手動で作成したJsonファイルと同じような形式をとることにしました。(処理をわざわざ変えるのもなーっておもったので)
    • ファイル名に関しては、前回ファイル名を取得する際の方法で生成したyyyymmの形式にしました。

新たな問題点

  • TOKYO MXしか見ねー的なノリで始めたんですが、推しキャラなの二乃がいる五等分の花嫁約束のネバーランドがT◯Sやフ◯TVじゃないですか。
  • これに関しては、それ以外の取得に関して考えて、結果マージしたデータでSlackに飛ばすようにするんだろうなってうっすら考えてます。まぁどちらもアマプラでいつでも見れるんですけどね!

  • いつcronで動かすよ問題!

    • 日次で動かす内容ではないし、月1でも違うし、1クール単位がいいんですがー言うてそんな設定できるんか?基準日を考慮できないと思うのでn日毎には実行難しそう。
    • ってことでこちらは、おそらくPython側で判定する形にするでしょう!月1実行とかにして。

反省

Pythonのお作法わからずだぁーって書いてしまっているので、リファクタリングとmodule化等を行っていきたいと思います。年末年始、特に年末が何してたかわからんくらいな状態だったので何もできなかったのでこれから平日含め学習をする癖がつくことただただ願います。

最後に独り言

アニメの通知ってamebaTVでできません?やアルっていう漫画サービスで発売日通知できますよって会社の同僚に言われました。「そそそそそうだったの」って内心では、思ったのは、内緒です。

GitHub ActionsにCronがあると聞いたんだ

はじめに

腹筋ローラーを初めて以降、筋肉痛なのか、姿勢の悪さからのコリなのかわからない苦しみが起きています。

あとウォーキングデットやゴールデンカムイを見ていついかなる状況になっても生存しなくてはいけないと感じてキャンプに興味を持ちました。

さて今回の目的

GitHub ActionsのCronで何すっかって話なのですが

最近に限ったことではなくて、僕は、数字の4桁以上を覚えるのがなかなか苦手なレベルの脳内メモリ所有者なのですが

来期のアニメの見なくてはいけないタイトルが10タイトルくらいあり、放送日時を把握し切れる訳がないじゃないか!って危機的状況に陥っている訳です。

仮にリアタイで見れなくてもアマプラさんで解決する部分もあったり絶命するわけではないのですが、個人的にHPが2割以下くらいになって日々のモチベーションがダダ下がりするのだろうと想定されるのです。

なのでよしGitHub Actionsの学習がてら毎朝か業後くらいの時間に通知行くようにしようぜ!

なんならPythonでやってみようぜ!

って昨日の夜中に思い立ったのでやってみたいとおもいます。

補足

我が家は、録画機能を有した何かしらの文明機器を持ち合わせていません。

前提

ゴール

最終的に以下のようにslackへ放送日に通知が行くようにします。 f:id:hironekosun:20201223154138p:plain

実装:Python

コードを先に晒します。

import json
import slackweb
from os import getenv
import datetime as dt
import calendar

today = dt.date.today()
dayName = calendar.day_name[today.weekday()]

if today.month < 10:
    month = f'0{today.month}'
else:
    month = today.month

data = json.load(open(f'./data/anime/{today.year}{month}.json', 'r'))

slack = slackweb.Slack(url=getenv('SLACK_WEBHOOK_URL'))

for v in data:
    if v['day_of_week'] == dayName:
        attachments = [
           {
               "fallback": 'アニメの放送時間のご案内',
               "pretext": '本日放送のアニメ',
               "fields": [
                   {
                       "title": 'タイトル',
                       "value": v['title'],
                   },
                   {
                       "title": '放送時間',
                       "value": v['publish_at'],
                       "short": "true"
                   },
                   {
                       "title": 'チャンネル',
                       "value": v['channel'],
                       "short": "true"
                   }
               ]
           } 
        ]
        slack.notify(attachments=attachments)

解説

  • 特別語ることは少ないです。

  • slackweb

こちらのライブラリが一番手っ取り早く簡単そうだったので使いました。

所感としては、ほんと単純なコードで通知することが可能なので誰でも迷わず実装が行えると思います。

  • getenv

getenvを使用して環境変数を取得してslackのweb hook urlをコードにハードコードしないようにします。

  • 全体

読めばわかるとは思うのですが、予めJSONのfileにデータを入れておきそのデータを元に今日放送をするアニメかどうかを判定して通知を行っています。

.
├── attachments.json
├── data
│   └── anime
│      ├── 202001.json
│      └── 202012.json
├── read.py
└── requirements.txt
  • JSON 下記の形式で通知対象にしたい情報を入れておきます(ここはスクレイピングでどうにかしたい)

file名は、必ずyyyymm.jsonとします。

[
    {
        "title": "転生したらスライムだった件",
        "publish_at": "23:00",
        "channel": "TOKYO MX",
        "day_of_week": "Tuesday"
    },
    ....
]

実装:GitHub Actions

  • こちらも先にコードを晒します。
on: 
  schedule:
    - cron: '55 0 * * *'

jobs:
  slack:
    name: Run slack
    runs-on: ubuntu-latest
    env:
      SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
    steps:
      - name: Checkout
        uses: actions/checkout@v2
      - name: Set up Python 3.8
        uses: actions/setup-python@v1
        with:
          python-version: 3.8
      - name: pip install
        run: pip install -r requirements.txt
      - name: post
        run: python read.py
  • cron

注意点として5分以下は動かないっぽいのとdefault branch以外は、cronの対象にならないとのこと

またこのcronが結構いい加減なので5分くらいのズレは、デフォです。

なので今回実行時間を10時に実行されるといいなーって気持ちでUTC 00:55 = 日本時刻 09:55 に設定しました。

GitHub Actionsよろしく!

  • secrets

今回slackのweb hook urlを使いますのでコードにハードコードしたくなく、外に晒すのもなーっていう気持ちなのでGitHubの機能を使用しました

Settings > Secretsで設定ページへ遷移します。New repository secretボタンを押下し追加します。

使い方は${{ secrets.KEY_NAME }}となります。

今後

漫画やアニメが好きなんですが既存の世に出ているサービスでは、僕のやってほしいことを満たすサービスが1mmもないのでこれを気に作ってやるんだから!って気持ちがふつふつと湧いてきました。

年末年始、時間があるようでないようなものですが積読している書籍読みつつ、開発していきたいなって気持ちです。

最後に独り言

グーカレでよくね?という意見は、受け付けますん。すみませんブログネタ考えたあとに振り返ったらグーカレの定期でよくねって考えに至りました。

Next.js + TS + Tailwindで入門してみる

はじめに

最近腹筋ローラーを買って速攻で筋肉痛になりました。The walking deadもやっとシーズン10まで見終わりそうです。

なぜNext.jsを触るのか?

在籍している会社にてNext.jsを使用する流れになり、入門はしたけども忘れてしまったから再度入門して肩慣らしでもしておこう!という気持ちになり、今回触ることにしました。 TSにしているのは、会社ではTSで実装しているため多少いじるにしてもTSの方がいいでしょう!という理由です。

Next.js

nextjs.org

Nuxt.jsより以前から開発が行われているReactのフレームワークです。

公式に物凄い数のexampleが用意されています。

github.com

ゴール

今回は、Next.jsを開発しているVercel社が用意しているサンプルにTailwindを適用してみるの巻です。

参照

kakakakakku.hatenablog.com

今回は、参照記事を元に公式が用意しているexampleのwith-typescriptを使用してみたいとおもいます。

準備

  • nodeのversionは、v14.15.1を使用します。
mkdir next-ts-sample
cd next-ts-sample
npx create-next-app --example with-typescript .
  • installが終わったのでyarn devを実行してみる。下記画像のような表示がされました。

f:id:hironekosun:20201209140713p:plain

  • Next.jsのアプリ全ページにTailwindを使えるようにするためにpages以下に下記fileを作成します。
    • ComponentなどをLayoutなどでラップしてあげるといいです
import './styles/tailwind.css'

function App({ Component, pageProps }) {
    return <Component {...pageProps} />
}

export default App

Tailwind入れる

yarn add -D tailwindcss@latest postcss@latest autoprefixer@latest
npx tailwindcss init --full
touch pages/styles/tailwind.css
  • 作成したtailwind.cssに以下を追加します
@tailwind base;
@tailwind components;
@tailwind utilities;

tailwind.config.jsに追記

  • Purge オプションを使ってビルドサイズの縮小するこができます。
const colors = require('tailwindcss/colors')

module.exports = {
  purge: ['./components/**/*.jsx', './pages/**/*.jsx']

// 省略

postcss.config.jsの作成

module.exports = {
  plugins: ['tailwindcss', 'autoprefixer'],
}

ブラウザで一度確認してみる

  • さきほどまでと様子が違うのでtailwindがあたってそうですね。

f:id:hironekosun:20201209154509p:plain

Tailwindを感じてみよう!

  • pages以下にweather.tsxを作成し以下を記述する
const Weather = () => (
    <div className="min-h-screen flex items-center justify-center">
        <div className="border-solid border-2 flex flex-col bg-white rounded p-4 w-full max-w-xs">
            <div className="font-bold text-xl">Sydney</div>
            <div className="text-sm text-gray-500">Thursday 10 May 2020</div>
            <div className="mt-6 text-6xl self-center inline-flex items-center justify-center rounded-lg text-indigo-400 h-24 w-24">
                <svg className="w-32 h-32" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 15a4 4 0 004 4h9a5 5 0 10-.1-9.999 5.002 5.002 0 10-9.78 2.096A4.001 4.001 0 003 15z"></path></svg>
            </div>
            <div className="flex flex-row items-center justify-center mt-6">
                <div className="font-medium text-6xl">24°</div>
                <div className="flex flex-col items-center ml-6">
                    <div>Cloudy</div>
                    <div className="mt-1">
                        <span className="text-sm"><i className="far fa-long-arrow-up"></i></span>
                        <span className="text-sm font-light text-gray-500">28°C</span>
                    </div>
                    <div>
                        <span className="text-sm"><i className="far fa-long-arrow-down"></i></span>
                        <span className="text-sm font-light text-gray-500">20°C</span>
                    </div>
                </div>
            </div>
            <div className="flex flex-row justify-between mt-6">
                <div className="flex flex-col items-center">
                    <div className="font-medium text-sm">Wind</div>
                    <div className="text-sm text-gray-500">9k/h</div>
                </div>
                <div className="flex flex-col items-center">
                    <div className="font-medium text-sm">Humidity</div>
                    <div className="text-sm text-gray-500">68%</div>
                </div>
                <div className="flex flex-col items-center">
                    <div className="font-medium text-sm">Visibility</div>
                    <div className="text-sm text-gray-500">10km</div>
                </div>
            </div>
        </div>
    </div>
)

export default Weather
  • localhost:3000/weatherにアクセスする f:id:hironekosun:20201209161131p:plain

  • pages以下にcard.tsxを作成し以下を記述する

const Card = () => (
    <section className="flex flex-row flex-wrap mx-auto">
        <div
            className="transition-all duration-150 flex w-full px-4 py-6 md:w-1/2 lg:w-1/3"
        >
            <div
                className="flex flex-col items-stretch min-h-full pb-4 mb-6 transition-all duration-150 bg-white rounded-lg shadow-lg hover:shadow-2xl"
            >
                <div className="md:flex-shrink-0">
                    <img
                        src="https://www.unfe.org/wp-content/uploads/2019/04/SM-placeholder-1024x512.png"
                        alt="Blog Cover"
                        className="object-fill w-full rounded-lg rounded-b-none md:h-56"
                    />
                </div>
                <div className="flex items-center justify-between px-4 py-2 overflow-hidden">
                    <span className="text-xs font-medium text-blue-600 uppercase">
                        Web Programming
                    </span>
                    <div className="flex flex-row items-center">
                        <div
                            className="text-xs font-medium text-gray-500 flex flex-row items-center mr-2"
                        >
                            <svg
                                className="w-4 h-4 mr-1"
                                fill="none"
                                stroke="currentColor"
                                viewBox="0 0 24 24"
                                xmlns="http://www.w3.org/2000/svg"
                            >
                                <path
                                    stroke-linecap="round"
                                    stroke-linejoin="round"
                                    stroke-width="2"
                                    d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
                                ></path>
                                <path
                                    stroke-linecap="round"
                                    stroke-linejoin="round"
                                    stroke-width="2"
                                    d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
                                ></path>
                            </svg>
                            <span>1.5k</span>
                        </div>

                        <div
                            className="text-xs font-medium text-gray-500 flex flex-row items-center mr-2"
                        >
                            <svg
                                className="w-4 h-4 mr-1"
                                fill="none"
                                stroke="currentColor"
                                viewBox="0 0 24 24"
                                xmlns="http://www.w3.org/2000/svg"
                            >
                                <path
                                    stroke-linecap="round"
                                    stroke-linejoin="round"
                                    stroke-width="2"
                                    d="M7 8h10M7 12h4m1 8l-4-4H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-3l-4 4z"
                                ></path>
                            </svg>
                            <span>25</span>
                        </div>

                        <div
                            className="text-xs font-medium text-gray-500 flex flex-row items-center"
                        >
                            <svg
                                className="w-4 h-4 mr-1"
                                fill="none"
                                stroke="currentColor"
                                viewBox="0 0 24 24"
                                xmlns="http://www.w3.org/2000/svg"
                            >
                                <path
                                    stroke-linecap="round"
                                    stroke-linejoin="round"
                                    stroke-width="2"
                                    d="M14 10h4.764a2 2 0 011.789 2.894l-3.5 7A2 2 0 0115.263 21h-4.017c-.163 0-.326-.02-.485-.06L7 20m7-10V5a2 2 0 00-2-2h-.095c-.5 0-.905.405-.905.905 0 .714-.211 1.412-.608 2.006L7 11v9m7-10h-2M7 20H5a2 2 0 01-2-2v-6a2 2 0 012-2h2.5"
                                ></path>
                            </svg>
                            <span>7</span>
                        </div>
                    </div>
                </div>
                <hr className="border-gray-300" />
                <div className="flex flex-wrap items-center flex-1 px-4 py-1 text-center mx-auto">
                    <a href="#" className="hover:underline">
                        <h2 className="text-2xl font-bold tracking-normal text-gray-800">
                            Ho to Yawn in 7 Days
                        </h2>
                    </a>
                </div>
                <hr className="border-gray-300" />
                <p
                    className="flex flex-row flex-wrap w-full px-4 py-2 overflow-hidden text-sm text-justify text-gray-700"
                >
                    Lorem ipsum dolor sit amet consectetur adipisicing elit. Alias, magni
                    fugiat, odit incidunt necessitatibus aut nesciunt exercitationem aliquam
                    id voluptatibus quisquam maiores officia sit amet accusantium aliquid
                    quo obcaecati quasi.
                </p>
                <hr className="border-gray-300" />
                <section className="px-4 py-2 mt-2">
                    <div className="flex items-center justify-between">
                        <div className="flex items-center flex-1">
                            <img
                                className="object-cover h-10 rounded-full"
                                src="https://thumbs.dreamstime.com/b/default-avatar-photo-placeholder-profile-icon-eps-file-easy-to-edit-default-avatar-photo-placeholder-profile-icon-124557887.jpg"
                                alt="Avatar"
                            />
                            <div className="flex flex-col mx-2">
                                <a href="" className="font-semibold text-gray-700 hover:underline">
                                    Fajrian Aidil Pratama
                                </a>
                                <span className="mx-1 text-xs text-gray-600">28 Sep 2020</span>
                            </div>
                        </div>
                        <p className="mt-1 text-xs text-gray-600">9 minutes read</p>
                    </div>
                </section>
            </div>
        </div>
    </section>
)

export default Card
  • localhost:3000/cardにアクセスする f:id:hironekosun:20201209161203p:plain

感想

今回のゴールがあくまでTailwindの適用なのでTSである必要なかったな

フロントのキャッチアップは、目に見えてわかるから楽しい反面、エラーが発生したりすると何を言っているんだって気持ちになるんで億劫になりがちだけど

今後も入門程度でもいいからいろいろ触って経験値だけは積んでいきたいとおもった。

あとVSCodeのフォーマッターがうまく動かず。。。