スタイル・エッジLABO技術ブログ

士業集客支援/コンサルティングのスタイル・エッジグループ スタイル・エッジLABOのエンジニアによるブログです。

Flutterのパッケージ備忘録

はじめに

こんにちは!スタイル・エッジLABOのガッキーです。
私は昨年10月より未経験でエンジニアとして入社し、
現在は、スタイル・エッジLABOの福井オフィスで勤務しています!

福井オフィスは昨年12月に新設されたばかりの、とてもスタイリッシュなオフィスです🌱
素敵な環境で気持ちよく勤務させていただけることに感謝しながら、日々仕事に邁進しております。
オフィスの写真は、こちら↓から是非ご覧ください! www.talent-book.jp

さて、私は入社して3ヵ月の研修後、「Flutter」を使ったスマホアプリ開発の機会を得られました。
今回は、Flutterのパッケージについて、備忘録的に書き留めておきたいと思います。

Flutterとは?

Flutterは、Googleが提供するモバイルアプリフレームワークです。
また、Flutterの開発にはDartという言語が用いられます。

一般的なネイティブアプリ開発では、AndroidiOS等、それぞれの環境に合わせたアプリ開発が必要ですが、 Flutterを使った開発の場合は、一度の開発でさまざまなデバイスやOS(AndroidiOS/Web/WindowsmacOS)に対応できます。

Flutterのパッケージ

Flutterでは外部パッケージを導入して、アプリに様々な機能を追加できます。
パッケージ自体はDart packagesから探すことができ、下記3ステップでインストールできます。

(例)audioplayers というパッケージをインストールする場合

① Flutterプロジェクトの中にあるpubspec.yamlファイル*1に、パッケージ名・バージョンを記述
dependencies:
  audioplayers: ^0.20.1
flutter pub getコマンドの実行

Visual Studio Codeを利用している場合は、Flutter・Dart拡張機能を導入すると、pubspec.yamlファイルを保存することで上記コマンドが走ります。)

③ main.dart等、パッケージを利用したいファイルの冒頭に import 文を書き加える
import 'package:audioplayers/audioplayers.dart';

パッケージのインストール手順や使用方法については、
各パッケージ詳細ページの 「Installing」・「Readme」 に記載されています。
(例)audioplayers のインストール手順:https://pub.dev/packages/audioplayers/install
(例)audioplayers の使用方法:https://pub.dev/packages/audioplayers
使い方はパッケージによって異なるので、それぞれ確認が必要になります。

さて、このパッケージについて、Flutter開発の際に実際に使ってみて便利だった、もしくは、
使わなかったけど使ってみたいパッケージを織り交ぜてご紹介したいと思います。

パッケージ3選

1. flutter_native_splash

アプリの起動中に画像やアニメーションがうぉんっと表示される、スプラッシュ画面を実装するためのパッケージです。
pubspec.yamlファイルに スプラッシュ画面に表示する画像と、背景色を記載することで、簡単にスプラッシュ画面が実装できます。
pub.dev

2. settings_ui

アプリの設定画面を実装するためのパッケージです。
このパッケージを使うことにより、各環境に最適化されたUIを提供できます。
どのような画面が作成できるかは、リンク先を是非見てみてください…! pub.dev

3. local_auth

iOS/Android端末で設定されている情報を使って、指紋認証・顔認証を実装できるパッケージです。
生体認証の結果(認証成功・失敗)を使って、セキュアなアプリにできます。 pub.dev

最後に

パッケージを使うことで簡単に機能や画面の実装ができて、パッケージの便利さが実感できます。
他にもたくさんのパッケージが存在しているので、どんどん活用してみたいと思います。

☆スタイル・エッジLABOでは、一緒に働く仲間を募集しています☆
Flutterの開発・モバイルアプリ開発に興味を持っていただけましたら、 採用サイト↓も覗いてみてください!
recruit.styleedge-labo.co.jp

*1:プロジェクトの設定ファイルで、パッケージマネージャーとしての役割もあります。新規でFlutterプロジェクトを作成した際、プロジェクトフォルダの中に自動的に作成されます。

MySQL のソースを読んでみる ~初めまして、コードリーディング編~

ご挨拶

初めまして。スタイル・エッジLABO の しお です。昨年10月に中途で入社してきました。
前職では SQL を書いてデータ調査をしたり巨大なシステムの中で路頭に迷ったり Java を書いたり書かなかったりしていました。
現在は、スタイル・エッジの社内で使用されているとあるシステムの保守開発をさせて頂いています。

日々の業務の中で MySQL を使用してデータ調査用のクエリを組んだりバッチ処理を書いたりしているわけですが、ふと「MySQL の実装ってどうなってるんだ?そもそもこれってどんな仕組みで動いてるの??」と思ったので、今日は実際に MySQLソースコードを読んでみようと思います。

MySQLOSS (オープンソースソフトウェア) で、ソースコードが全世界に公開されています。そのため、誰でも自由にソースコードを読むことが出来ます。
個人的には飛行機の飛ぶ原理とかも気になって仕方がないのですが、飛行機は OSS ではないので今回は諦めて MySQLソースコードを読んでいくことにします。

モチベーション

  • 色んな OSS のコードリーディングしてみたい。
  • 初見のコード読めるようになりたい。
  • MySQL の仕様について自信を持ちたい。

対象

普段業務で使っている、mysql-server のソースを読んでいきます。ただ、闇雲に読んでも迷子になるだけなので、目的は設定しておきます。
今回は、最近知った 宇宙船演算子 (英: spaceship operator) というカッコイイ演算子 *1 の謎を紐解いていきます。

f:id:styleedge_tech:20220323171636j:plain

宇宙船演算子とは?

宇宙船演算子は、簡単に言うと null 値を比較できる比較演算子です。

例えば、数値同士は以下の通り何の問題もなく比較できます。

mysql> set @hoge = 1;
Query OK, 0 rows affected (0.00 sec)

mysql> select @hoge = 1 as result;
+--------+
| result |
+--------+
|      1 |
+--------+
1 row in set (0.00 sec)

文字列同士での比較も同様です。

mysql> set @hoge = 'aaa';
Query OK, 0 rows affected (0.00 sec)

mysql> select @hoge = 'aaa' as result;
+--------+
| result |
+--------+
|      1 |
+--------+
1 row in set (0.00 sec)

また、異なる型同士でも、比較自体は可能です。

mysql> set @hoge = 1;
Query OK, 0 rows affected (0.00 sec)

mysql> select @hoge = 'aaa' as result;
+--------+
| result |
+--------+
|      0 |
+--------+
1 row in set, 1 warning (0.00 sec)

では、比較対象が null 値だとどうなるのでしょうか?

mysql> set @hoge = null;
Query OK, 0 rows affected (0.00 sec)

mysql> select @hoge = 'aaa' as result;
+--------+
| result |
+--------+
|   NULL |
+--------+
1 row in set (0.00 sec)

この通り、比較結果は null となり結果は返ってきません。そのため、null と比較するときは coalesce (これなんて読むのが正解?)*2 で初期値を設定するか、is not null and 等でよくお茶を濁します。

mysql> set @hoge = null;
Query OK, 0 rows affected (0.00 sec)

mysql> select coalesce(@hoge, '') = 'aaa' as result;
+--------+
| result |
+--------+
|      0 |
+--------+
1 row in set (0.00 sec)

mysql> select (@hoge is not null) and (@hoge = 'aaa') as result;
+--------+
| result |
+--------+
|      0 |
+--------+
1 row in set (0.00 sec)

ただ、null 値の考慮のために coalesceis not null and 等の比較を入れていることで単純にソースの分量も増えて読みづらくなるし、本来のクエリの目的もぼやけて伝わりづらくなってしまいます。

この問題を解決するのが宇宙船演算子で、彼は null 値との比較を可能にしてくれます。

mysql> set @hoge = null;
Query OK, 0 rows affected (0.00 sec)

mysql> select @hoge <=> 'aaa' as result;
+--------+
| result |
+--------+
|      0 |
+--------+
1 row in set, 1 warning (0.00 sec)

ちなみに、比較対象が両方とも null ならば等しいと見なされるそうです。

mysql> set @hoge = null;
Query OK, 0 rows affected (0.00 sec)

mysql> set @fuga = null;
Query OK, 0 rows affected (0.00 sec)

mysql> select @hoge <=> @fuga as result;
+--------+
| result |
+--------+
|      1 |
+--------+
1 row in set (0.01 sec)

実際に読んでいく

ソースの展開

前置きが大変長くなってしまいました。以上が宇宙船演算子の概要です。ここからは、実際に MySQL のソースを読んで宇宙船演算子の確かな仕様を掴みに行きます。

ソースの展開は以下の手順で行いました。

# 任意の場所に移動
cd ~/projects
# ソースを落としてくる
git clone https://github.com/mysql/mysql-server.git
# ディレクトリ移動する
cd ./mysql-server

とにかく grep だ!

兎にも角にも、宇宙船演算子なるものの正体を暴くためにキーワード <=>grep してみます。

$ grep -rn "<=>" ./
./storage/innobase/include/log0types.h:222:       @name Users <=> writer
./storage/innobase/include/log0types.h:276:       @name Users <=> flusher
./storage/innobase/include/log0types.h:453:       @name Log flusher <=> flush_notifier
./storage/innobase/include/log0types.h:478:       @name Log writer <=> write_notifier
./storage/ndb/src/mgmclient/CommandInterpreter.cpp:351:"                      #&()*+-./:;<=>?@[]_{|}~.\n"
./storage/ndb/src/kernel/blocks/dbtup/DbtupExecQuery.cpp:3754:   * len=4 <=> 1 word
./storage/ndb/src/kernel/blocks/dbdih/DbdihMain.cpp:21970:       * Nothing queued or started <=> Complete on that node
./storage/ndb/nodejs/jones-ndb/impl/src/ndb/QueryOperation.cpp:128://  DEBUG_PRINT_DETAIL("compareTwoResults for level %d: %d <=> %d", level, r2, r1);
./storage/myisam/mi_key.cc:277:    unpack_blobs        true  <=> Unpack blob columns
./storage/myisam/mi_key.cc:278:                        false <=> Skip them. This is used by index condition
./include/base64.h:77:    60,        61, -1, -1, -1, -1, -1, -1, /* 0123456789:;<=>? */
./include/mysql/service_command.h:184:  @param is_unsigned   TRUE <=> value is unsigned
./unittest/gunit/decimal-t.cc:409:  sprintf(s, "'%s' <=> '%s'", s1, s2);
./unittest/gunit/libmysqlgcs/xcom/gcs_xcom_xcom_base-t.cc:641:     gt_ballot(m->proposal, p->proposer.msg->proposal) <=>
./unittest/gunit/libmysqlgcs/xcom/gcs_xcom_xcom_base-t.cc:642:     gt_ballot((0,0), (0,1)) <=>
./unittest/gunit/opt_trace-t.cc:828:      "0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]^_`"
./unittest/gunit/xplugin/xpl/mysql_function_names_t.cc.in:238:    {"<=>", OPERATOR},
./plugin/group_replication/include/sql_service/sql_service_context.h:136:    @param is_unsigned   TRUE <=> value is unsigned
./plugin/group_replication/include/sql_service/sql_service_context_base.h:142:    @param is_unsigned   TRUE <=> value is unsigned
./plugin/innodb_memcached/daemon_memcached/scripts/damemtop:369:                @newrows = sort { $a->[$colnum] <=> $b->[$colnum] } @rows;
./plugin/innodb_memcached/daemon_memcached/scripts/damemtop:375:                @newrows = sort { $b->[$colnum] <=> $a->[$colnum] } @rows;
./plugin/x/src/ngs/command_delegate.h:273:    @param unsigned_flag true <=> value is unsigned
./scripts/fill_help_tables.sql:157:INSERT INTO help_topic (help_topic_id,help_category_id,name,description,example,url) VALUES (55,10,'<=>','Syntax:\n<=>\n\nNULL-safe equal. This operator performs an equality comparison like the\n= operator, but returns 1 rather than NULL if both operands are NULL,\nand 0 rather than NULL if one operand is NULL.\n\nThe <=> operator is equivalent to the standard SQL IS NOT DISTINCT FROM\noperator.\n\nURL: https://dev.mysql.com/doc/refman/8.0/en/comparison-operators.html\n\n','mysql> SELECT 1 <=> 1, NULL <=> NULL, 1 <=> NULL;\n        -> 1, 1, 0\nmysql> SELECT 1 = 1, NULL = NULL, 1 = NULL;\n        -> 1, NULL, NULL\n','https://dev.mysql.com/doc/refman/8.0/en/comparison-operators.html');
./scripts/fill_help_tables.sql:860:INSERT INTO help_keyword (help_keyword_id,name) VALUES (56,'<=>');
./scripts/mysqldumpslow.pl.in:157:my @sorted = sort { $stmt{$b}->{$opt{s}} <=> $stmt{$a}->{$opt{s}} } keys %stmt;
./sql/field.h:4590:    true <=> source item is an Item_field. Needed to workaround lack of
./sql/json_dom.h:1598:    @param[out] err    true <=> error occur during coercion
./sql/json_dom.h:1618:    @param[out] err    true <=> error occur during coercion
./sql/json_dom.h:1636:    @param[out] err    true <=> error occur during coercion
・・・
以下、お見せできないほどの量

そっか・・・

落ち着いて tree

情報量に圧倒されてしまいそうだったので、一旦呼吸を整えて tree コマンドでざっくりとディレクトリ構成を把握することにしました。

$ tree ./ -L 1
./
├── CMakeLists.txt
├── Docs
├── Doxyfile-ignored
├── Doxyfile.in
├── INSTALL
├── LICENSE
├── MYSQL_VERSION
├── README
├── client
├── cmake
├── components
├── config.h.cmake
├── configure.cmake
├── doxygen_resources
├── extra
├── include
├── libbinlogevents
├── libbinlogstandalone
├── libchangestreams
├── libmysql
├── libservices
├── man
├── mysql-test
├── mysys
├── packaging
├── plugin
├── router
├── run_doxygen.cmake
├── scripts
├── share
├── sql
├── sql-common
├── storage
├── strings
├── support-files
├── testclients
├── unittest
├── utilities
└── vio

29 directories, 10 files

なんだか sql の下が匂う・・・気がするので、再度./sql 配下で grep してみます。

$ grep -rn "<=>" ./sql
./sql/field.h:4590:    true <=> source item is an Item_field. Needed to workaround lack of
./sql/json_dom.h:1598:    @param[out] err    true <=> error occur during coercion
./sql/json_dom.h:1618:    @param[out] err    true <=> error occur during coercion
./sql/json_dom.h:1636:    @param[out] err    true <=> error occur during coercion
./sql/temp_table_param.h:174:    true <=> don't actually create table handler when creating the result
./sql/sql_class.cc:2518:  @param  all   true <=> rollback main transaction.
./sql/sql_const_folding.h:36:  Fold boolean condition {=, <>, >, >=, <, <=, <=>} involving constants and
./sql/range_optimizer/index_range_scan.h:79:  bool free_file; /* TRUE <=> this->file is "owned" by this quick select */
./sql/range_optimizer/index_range_scan_plan.h:55:      index_read_must_be_used  true <=> assume 'index only' option will be set
./sql/range_optimizer/index_range_scan_plan.h:57:      update_tbl_stats         true <=> update table->quick_* with information
./sql/range_optimizer/index_range_scan_plan.h:100:      update_tbl_stats  true <=> update table->quick_* with information
./sql/range_optimizer/partition_pruning.cc:911:          Ok, we've got "fieldN<=>constN"-type SEL_ARGs for all partitioning
./sql/range_optimizer/partition_pruning.cc:939:          Ok, we've got "fieldN<=>constN"-type SEL_ARGs for all subpartitioning
./sql/range_optimizer/range_analysis.cc:1134:  @param comp_op                    Comparison operator: >, >=, <=> etc.
./sql/range_optimizer/range_analysis.cc:1246:          Independent of data type, "out_of_range_value =/<=> field" is
./sql/range_optimizer/range_analysis.cc:1544:    Any sargable predicate except "<=>" involving NULL as a constant is always
./sql/range_optimizer/tree.cc:1072:  // (cond) OR (IMPOSSIBLE) <=> (cond).
./sql/rpl_rli_pdb.cc:1710:        lwm_estimate < last_committed  <=>  last_committed  \not <= lwm_estimate
./sql/sql_optimizer_internal.h:69:  Currently 'op' is one of {'=', '<=>', 'IS [NOT] NULL', 'arg1 IN arg2'},
./sql/lex.h:74:    {SYM("<=>", EQUAL_SYM)},
./sql/handler.h:3579:  Flag set <=> default MRR implementation is used
./sql/handler.h:3588:  Flag set <=> the caller guarantees that the bounds of the scanned ranges
./sql/handler.h:4165:  /* true <=> source MRR ranges and the output are ordered */
./sql/handler.h:4168:  /* true <=> we're currently traversing a range in mrr_cur_range. */
./sql/handler.h:4200:    true <=> the engine guarantees that returned records are within the range
./sql/handler.h:6777:  bool dsmrr_eof; /* true <=> We have reached EOF when reading index tuples */
./sql/handler.h:6779:  /* true <=> need range association, buffer holds {rowid, range_id} pairs */
./sql/handler.h:6782:  bool use_default_impl; /* true <=> shortcut all calls to default MRR impl */
./sql/sql_executor.h:392:  /** true <=> remove duplicates on this table. */
./sql/opt_explain_json.cc:903:        (x <=> (SELECT FROM DUAL) AND x = (SELECT FROM DUAL)),
./sql/table_function.h:214:    true <=> NESTED PATH associated with this element is producing records.
./sql/table_function.h:304:    @param[out]  skip  true <=> it's a NESTED PATH node and its path
./sql/item_cmpfunc.cc:1071:  @param [out] is_null        true <=> the item_arg is null
./sql/item_cmpfunc.cc:1490:  @param[out] is_null  true <=> the item_arg is null
./sql/item_cmpfunc.cc:1553:    is_null    [out]    true <=> the item_arg is null
./sql/item_cmpfunc.cc:4949:  /* true <=> arguments values will be compared as DATETIMEs. */
./sql/sql_resolver.cc:1867:  @param top         true <=> cond is the where condition
./sql/sql_resolver.cc:1868:  @param in_sj       true <=> processing semi-join nest's children
./sql/sql_resolver.cc:5479:      // If antijoin, we can decorrelate '<>', '>=', etc, too (but not '<=>'):
./sql/join_optimizer/make_join_hypergraph.cc:2235:      //   (t1 <opA> t2) <opB> t3 <=> t1 <opA> (t2 <opB> t3)
./sql/join_optimizer/interesting_orders.cc:1414:      // TODO(sgunders): When we get C++20, use operator<=> so that we
./sql/join_optimizer/join_optimizer.cc:3831:// TODO(sgunders): Include x=y OR NULL predicates, <=> and IS NULL predicates,
./sql/sql_opt_exec_shared.h:109:    true <=> disable the "cache" as doing lookup with the same key value may
./sql/sql_partition.cc:3112:      include_endpoint  true <=> the endpoint itself is included in the
./sql/sql_optimizer.h:589:  /** Exec time only: true <=> current group has been sent */
./sql/handler.cc:7151:  @param      interrupted  true <=> Assume that the disk sweep will be
./sql/sql_select.cc:2561:  index are available other_tbls_ok  true <=> Fields of other non-const tables
./sql/sql_select.cc:5036:          <=> N > refkey_rows_estimate.
./sql/opt_trace.cc:219:    0 <=> this trace should be in information_schema.
./sql/opt_explain.h:97:  bool zero_result;           ///< true <=> plan will not be executed
./sql/key_spec.h:182:  /// true <=> ascending, false <=> descending.
./sql/key_spec.h:185:  /// true <=> ASC/DESC is explicitly specified, false <=> implicit ASC
./sql/opt_sum.cc:759:  bool eq_type = false;          // =, <=> or IS NULL
./sql/opt_sum.cc:760:  bool is_null_safe_eq = false;  // The operator is NULL safe, e.g. <=>
./sql/opt_trace_context.h:370:    <>0 <=> any to-be-created statement's trace should not be in
./sql/sql_const_folding.cc:1053:  [*] for the "<=>" operator, we fold to FALSE (0) in this case.
./sql/partitioning/partition_handler.h:1022:    @param[in]  have_start_key  true <=> the left endpoint is available, i.e.
./sql/partitioning/partition_handler.h:1025:                                false <=> there is no left endpoint (we're in
./sql/partitioning/partition_handler.cc:2208:  @param idx_read_flag  true <=> m_start_key has range start endpoint which
./sql/partitioning/partition_handler.cc:2211:                        false <=> there is no start endpoint.
./sql/item.h:1607:        left_endp  false  <=> The interval is "x < const" or "x <= const"
./sql/item.h:1608:                   true   <=> The interval is "x > const" or "x >= const"
./sql/item.h:1610:        incl_endp  IN   false <=> the comparison is '<' or '>'
./sql/item.h:1611:                        true  <=> the comparison is '<=' or '>='
./sql/item.h:5944:    true <=> that the outer_ref is already present in the select list
./sql/item.h:6504:    true <=> cache holds value of the last stored item (i.e actual value).
./sql/sql_tmp_table.cc:1962:  @param force_disk_table true <=> Use InnoDB
./sql/sql_tmp_table.cc:2013:  @param force_disk_table true <=> Use InnoDB
./sql/item.cc:2041:  @param skip_registered <=> function be must skipped for registered SUM items
./sql/item.cc:2131:  /* An item of type Item_sum  is registered <=> referenced_by[0] != 0 */
./sql/item.cc:7310:  @param [out] arg If != NULL <=> Cache this item.
./sql/item.cc:9150:        We can't ignore NULL values here as this item may be used with <=>, in
./sql/sql_select.h:736:  /** true <=> AM will scan backward */
./sql/sql_select.h:811:  bool null_key{false}; /* true <=> the value of the key has a null part */
./sql/sql_select.h:992:  @param func   comparison operator (= or <=>)
./sql/sql_parse.cc:6551:  if ((cmp == &comp_eq_creator) && !all)  //  = ANY <=> IN
./sql/sql_parse.cc:6553:  if ((cmp == &comp_ne_creator) && all)  // <> ALL <=> NOT IN
./sql/table.cc:4170:  @param is_virtual true <=> it's a virtual tmp table
./sql/sql_planner.cc:965:  @param disable_jbuf      true<=> Don't use join buffering
./sql/sql_optimizer.cc:6151:      AND t11.b <=> t10.b AND (t11.a = (SELECT MAX(a) FROM t12
./sql/sql_optimizer.cc:6281:  @param  other_tbls_ok  true <=> Fields of other non-const tables are allowed
./sql/sql_optimizer.cc:6661:  -   (t2.key = t1.field OR t2.key <=> t1.field) -> null_rejecting=false
./sql/sql_optimizer.cc:6846:  @param eq_func            True if we used =, <=> or IS NULL
./sql/sql_optimizer.cc:7006:    Only the <=> operator and the IS NULL and IS NOT NULL clauses may return
./sql/sql_optimizer.cc:7062:    @param  eq_func        True if we used =, <=> or IS NULL
./sql/sql_optimizer.cc:8830:        Note that ref access implements "table1.field1 <=>
./sql/sql_optimizer.cc:10247:       3) If the <=> operator is used, result is always true because
./sql/table.h:1725:    For tmp tables. true <=> tmp table has been instantiated.
./sql/table.h:1742:      true <=> range optimizer found that there is no rows satisfying
./sql/table.h:3354:       <=>
./sql/table.h:3358:       <=>
./sql/table.h:3362:       <=>
./sql/table.h:3686:  /// true <=> VIEW CHECK OPTION condition is processed (also for prep. stmts)
./sql/table.h:3688:  /// true <=> Filter condition is processed
./sql/table.h:3826:  /// true <=> this table is a const one and was optimized away.
./sql/table.h:3830:    true <=> all possible keys for a derived table were collected and
./sql/item_cmpfunc.h:143:  bool set_null{true};  // true <=> set owner->null_value
./sql/item_cmpfunc.h:336:    True <=> this item was added by IN->EXISTS subquery transformation, and
./sql/item_cmpfunc.h:531:/// Abstract base class for the comparison operators =, <> and <=>.
./sql/item_cmpfunc.h:564:    return "<=>";
./sql/item_cmpfunc.h:681:  >, >=) as well as the special <=> equality operator.
./sql/item_cmpfunc.h:1067:  The <=> operator evaluates the same as
./sql/item_cmpfunc.h:1071:  a <=> b is equivalent to the standard operation a IS NOT DISTINCT FROM b.
./sql/item_cmpfunc.h:1089:  const char *func_name() const override { return "<=>"; }
./sql/item_cmpfunc.h:1230:  bool negated;    /* <=> the item represents NOT <func> */
./sql/item_cmpfunc.h:1231:  bool pred_level; /* <=> [NOT] <func> is used on a predicate level */
./sql/item_cmpfunc.h:1261:  /* true <=> arguments will be compared as dates. */

またまた情報量に圧倒されつつ、一つ明らかに怪しいものが・・・

./sql/lex.h:74:    {SYM("<=>", EQUAL_SYM)},

というわけで、./sql/lex.h:74 近辺を詳しく見てみます。

細かく grep しつつ読んでいく

static const SYMBOL symbols[] = {
    /*
     Insert new SQL keywords after that commentary (by alphabetical order):
    */
    {SYM("&&", AND_AND_SYM)},
    {SYM("<", LT)},
    {SYM("<=", LE)},
    {SYM("<>", NE)},
    {SYM("!=", NE)},
    {SYM("=", EQ)},
    {SYM(">", GT_SYM)},
    {SYM(">=", GE)},
    {SYM("<<", SHIFT_LEFT)},
    {SYM(">>", SHIFT_RIGHT)},
    {SYM("<=>", EQUAL_SYM)},
    {SYM("ACCESSIBLE", ACCESSIBLE_SYM)},
    {SYM("ACCOUNT", ACCOUNT_SYM)},
    {SYM("ACTION", ACTION)},
    {SYM("ACTIVE", ACTIVE_SYM)},
    {SYM("ADD", ADD)},
    {SYM("ADMIN", ADMIN_SYM)},
    {SYM("AFTER", AFTER_SYM)},
    {SYM("AGAINST", AGAINST)},
    {SYM("AGGREGATE", AGGREGATE_SYM)},
    {SYM("ALL", ALL)},
    {SYM("ALGORITHM", ALGORITHM_SYM)},
    {SYM("ALTER", ALTER)},
    // 省略

こんな感じで SQL予約語が定義されてました。本筋とは関係ない話ですが、MySQL って <>!= の両方をサポートしてたんですね(両方とも NE なので同じ働きしてくれそう)。知らなかった。

さて、肝心の宇宙船演算子を意味する比較演算子 <=> はここでは EQUAL_SYM と定義されているらしいので、再度 EQUAL_SYM./sql 配下を grep してみます。

$ grep -rn "EQUAL_SYM" ./sql
./sql/lex.h:74:    {SYM("<=>", EQUAL_SYM)},
./sql/sql_yacc.yy:716:%token  EQUAL_SYM 416                     /* OPERATOR */
./sql/sql_yacc.yy:1405:%left   EQ EQUAL_SYM GE GT_SYM LE LT NE IS LIKE REGEXP IN_SYM
./sql/sql_yacc.yy:10462:        | EQUAL_SYM { $$ = &comp_equal_creator; }

上から順に見ていくと、

./sql/lex.h:74:    {SYM("<=>", EQUAL_SYM)},

これはさっき見た定義ファイル。

./sql/sql_yacc.yy:716:%token  EQUAL_SYM 416                     /* OPERATOR */
./sql/sql_yacc.yy:1405:%left   EQ EQUAL_SYM GE GT_SYM LE LT NE IS LIKE REGEXP IN_SYM

これは前者がよく分からないけど、後者はただ比較演算子を列挙しているだけっぽいのでスルー。

./sql/sql_yacc.yy:10462:        | EQUAL_SYM { $$ = &comp_equal_creator; }

次に見るとしたら comp_equal_creator な気がするので、grep してみます。

$ grep -rn "comp_equal_creator" ./
./sql/sql_yacc.yy:10267:            if ($2 == &comp_equal_creator)
./sql/sql_yacc.yy:10462:        | EQUAL_SYM { $$ = &comp_equal_creator; }
./sql/sql_parse.h:76:Comp_creator *comp_equal_creator(bool invert);
./sql/sql_parse.cc:6512:Comp_creator *comp_equal_creator(bool invert [[maybe_unused]]) {

上2つは、雰囲気的に comp_equal_creator 関数の呼び出し元で、下2つのどちらかが定義部分っぽい?

comp_equal_creator は、sql_parse.cc では以下のように定義されていました。

Comp_creator *comp_equal_creator(bool invert [[maybe_unused]]) {
  assert(!invert);  // Function never called with true.
  return &equal_creator;
}

最初の謎 assert は、コメントを信じると「引数 invert は基本的に true を取らない」と書いてあるし、引数定義部分で maybe_unused と注釈?があるので、読み飛ばします。

次に注目するワードは equal_creatorequal_creatorgrep するとさっき検索した comp_equal_creator も引っかかってしまうので、-w オプションを付けて完全一致で検索することにします。

$ grep -rnw "equal_creator" ./
./sql/item_cmpfunc.cc:287:Item_bool_func *Equal_creator::create_scalar_predicate(Item *a, Item *b) const {
./sql/item_cmpfunc.cc:292:Item_bool_func *Equal_creator::combine(List<Item> list) const {
./sql/sql_parse.cc:6514:  return &equal_creator;
./sql/mysqld.cc:1500:Equal_creator equal_creator;
./sql/item_cmpfunc.h:559:class Equal_creator : public Linear_comp_creator {
./sql/item_cmpfunc.h:2707:extern Equal_creator equal_creator;

Equal_creator というクラスの定義があるので、./sql/item_cmpfunc.h:559 を見てみます。

class Equal_creator : public Linear_comp_creator {
 public:
  const char *symbol(bool invert [[maybe_unused]]) const override {
    // This will never be called with true.
    assert(!invert);
    return "<=>";
  }

 protected:
  Item_bool_func *create_scalar_predicate(Item *a, Item *b) const override;
  Item_bool_func *combine(List<Item> list) const override;
};

ざっと見てみると、一番上の const は比較演算子の文字列定義、下の二つがメソッド? create_scalar_predicate(Item *a, Item *b) が引数を2つ取っていてそれっぽいので、grep してみます。

$ grep -rn "create_scalar_predicate" ./
./sql/item_cmpfunc.cc:257:  create_scalar_predicate().
./sql/item_cmpfunc.cc:275:  return create_scalar_predicate(a, b);
./sql/item_cmpfunc.cc:278:Item_bool_func *Eq_creator::create_scalar_predicate(Item *a, Item *b) const {
./sql/item_cmpfunc.cc:287:Item_bool_func *Equal_creator::create_scalar_predicate(Item *a, Item *b) const {
./sql/item_cmpfunc.cc:296:Item_bool_func *Ne_creator::create_scalar_predicate(Item *a, Item *b) const {
./sql/item_cmpfunc.h:544:  virtual Item_bool_func *create_scalar_predicate(Item *a, Item *b) const = 0;
./sql/item_cmpfunc.h:555:  Item_bool_func *create_scalar_predicate(Item *a, Item *b) const override;
./sql/item_cmpfunc.h:568:  Item_bool_func *create_scalar_predicate(Item *a, Item *b) const override;
./sql/item_cmpfunc.h:577:  Item_bool_func *create_scalar_predicate(Item *a, Item *b) const override;

どうやら実装は ./sql/item_cmpfunc.cc:278 にあるようです。*Equal_creator::create_scalar_predicate を見てみます。

Item_bool_func *Equal_creator::create_scalar_predicate(Item *a, Item *b) const {
  assert(a->type() != Item::ROW_ITEM || b->type() != Item::ROW_ITEM);
  return new Item_func_equal(a, b);
}

最初に、また謎の assert。 一旦 assert は読み飛ばして、return new してる Item_func_equal で検索してみます。

$ grep -rn "Item_func_equal" ./sql
./sql/sql_help.cc:687:      Item *cond_topic_by_cat = new Item_func_equal(
./sql/sql_help.cc:689:      Item *cond_cat_by_cat = new Item_func_equal(
./sql/item_cmpfunc.cc:289:  return new Item_func_equal(a, b);
./sql/item_cmpfunc.cc:2493:bool Item_func_equal::resolve_type(THD *thd) {
./sql/item_cmpfunc.cc:2500:longlong Item_func_equal::val_int() {
./sql/item_cmpfunc.cc:2535:float Item_func_equal::get_filtering_effect(THD *, table_map filter_for_table,
./sql/item_cmpfunc.h:1075:class Item_func_equal final : public Item_func_comparison {
./sql/item_cmpfunc.h:1077:  Item_func_equal(Item *a, Item *b) : Item_func_comparison(a, b) {
./sql/item_cmpfunc.h:1080:  Item_func_equal(const POS &pos, Item *a, Item *b)

./sql/item_cmpfunc.h:1075 で、Item_func_equal クラスが定義されているので、早速見てみます。

/**
  The <=> operator evaluates the same as

    a IS NULL || b IS NULL ? a IS NULL == b IS NULL : a = b

  a <=> b is equivalent to the standard operation a IS NOT DISTINCT FROM b.

  Notice that the result is TRUE or FALSE, and never UNKNOWN.
*/
class Item_func_equal final : public Item_func_comparison {
 public:
  Item_func_equal(Item *a, Item *b) : Item_func_comparison(a, b) {
    null_on_null = false;
  }
  Item_func_equal(const POS &pos, Item *a, Item *b)
      : Item_func_comparison(pos, a, b) {
    null_on_null = false;
  }
  longlong val_int() override;
  bool resolve_type(THD *thd) override;
  enum Functype functype() const override { return EQUAL_FUNC; }
  enum Functype rev_functype() const override { return EQUAL_FUNC; }
  cond_result eq_cmp_result() const override { return COND_TRUE; }
  const char *func_name() const override { return "<=>"; }
  Item *truth_transformer(THD *, Bool_test) override { return nullptr; }

  float get_filtering_effect(THD *thd, table_map filter_for_table,
                             table_map read_tables,
                             const MY_BITMAP *fields_to_ignore,
                             double rows_in_table) override;
};

かなり核心に近づいている気がします。

Javadoc 的なやつを書いてくれてるのでざっくり雰囲気で読んでみると、

<=> 演算子は、

a IS NULL || b IS NULL ? a IS NULL == b IS NULL : a = b

と同じ動きをします。

a <=> b は、標準的な構文の a IS NOT DISTINCT FROM b と同じ働きをします。

この関数により返却されるのは TRUEFALSE のどちらかであり、UNKNOWN にはならないことに注目してください。

とのこと。(IS NOT DISTINCT FROM の構文知らなかった・・・。 *3

・・・

時間切れです。

すみません、Item_func_equal のクラスを見つけるのに精一杯で、結局宇宙船演算子の実装までは辿り着けませんでした。 敗因としては、コードリーディングの経験値の少なさと、そもそも C++ の構文を知らなさ過ぎて何となく当てずっぽうで読み進めてしまったところでしょうか。

次回はもう少し勉強して、踏み込んだ内容で記事を書けるよう研鑽します。

オライリー社出版の Understanding MySQL Internals がどうやら今の自分のニーズにぴったりの本で、実際にソースコードを読み進めていきながら MySQL の理解を深めていくという内容らしいので、会社の書籍購入支援制度で購入してもらって読んでみようと思います!!!

まとめ

残念ながら結論は出せませんでしたが、今回コードリーディングをしてみたことで、世界中で使われている超メジャーな OSS でもソースコードを落としてきてキーワードを grep していくだけである程度欲しい情報には近づけるということに気が付けた点においては大きな収穫と言えるかと思います。

普段何気なく使っているツール類でも「あれ、これってどうやって作ってるんだろう?」という視点で見てみると、より理解が深まったり新たなバグが発見出来てしまうかもしれません。

それでは、次回「MySQL のソースを読んでみる ~宇宙船演算子完全に理解した編~」でお会いいたしましょう。


スタイル・エッジLABO では、一緒に働く仲間を募集しています。
もし興味を持っていただけましたら、以下の採用サイトも一度覗いてみてください!

recruit.styleedge-labo.co.jp

*1:正式には「NULL安全等価演算子(英:NULL-safe equal)」
dev.mysql.com

*2:先輩に教えてもらいました。読みは「コ(ウ)アレス」とのこと。

*3:「IS DISTINCT FROM」は SQL:1999、「IS NOT DISTINCT FROM」は SQL:2003 にて策定されたそうです。
modern-sql.com

Laravelでファットコントローラーを防ぎたい

はじめに

こんにちは!スタイル・エッジLABOのZNです。
2022年が始まったと思えば、もう2ヶ月が経ってしまいました。
エンジニアLIFEは、時間の流れが早いように感じます。

私は約8ヶ月前に未経験でエンジニアとして「スタイル・エッジLABO」に入社しました。
未経験でのエンジニアへの挑戦だったので、面接にはポートフォリオを持参し挑みました。
ポートフォリオはLaravelというフレームワークを使用して、チャットのようなものを作成したのを覚えています。
入社から8ヶ月経った今、そのポートフォリオの中身を見ると「ザ・ファットコントローラー」だったので、Laravelでファットコントローラーを防ぐ秘訣について少し紹介できればと思います!

そもそもファットコントローラーとは何...?

Laravelなどのフレームワークで用いられているコントローラーに様々な処理を任せてしまい、1つのコントローラー内、1つのメソッド内の行数が多くなってしまって肥大化していることを指します。

f:id:styleedge_tech:20220210163851p:plain

では、何が問題なのかをいくつか挙げてみます。

はじめに、「どこに処理を記述したかが分かりにくく、コードを追いにくい」 ということが挙げられます。
実装した本人でさえ後から処理を追おうとすると、どこに記述したか・どういった経緯でDBから値を持ってきたかが分かりにくくなります。ましてや、実装者以外がコードを見たときは言うまでもなく、処理を追うのがとても大変になります。
そのため、複数人の開発では大きな問題になります。

次に、「改修漏れが生じやすい」ことが考えられます。
ファットコントローラーでは同じ処理を至るところに書いている場合が多く、仕様変更などがあった場合は同じ処理のところを全て変更しなければならなくなり、改修漏れが生じやすくなってしまいます。

ファットコントローラーにはこのような問題が挙げられ、 私のポートフォリオでもよく起こっていました。

それでは、ファットコントローラーにならないためにはどうしたら良いのか、いくつか秘訣を紹介します。

ファットコントローラーを防ぐ秘訣

その壱:バリデーションは FormRequest にまとめる

これはファットコントローラーと言われたら一番に思いつく内容ではないでしょうか。

Laravelには、独自のバリデーションおよび認可ロジックをカプセル化するカスタムリクエストクラスで、標準の機能として FormRequest が用意されています。
バリデーションをコントローラー内に記述する際、入力パラメータや条件が複数ある場合や仕様変更により入力パラメータが増えた場合は、コントローラー内の行数は確実に増え、読みにくくなってしまいます。
そのため、バリデーションは FormRequest にまとめてしまい、バリデーションによる肥大化を防ぎます。
仕様変更があった際や、入力パラメータが増えた場合にも FormRequest のみを変更するだけで済むようになります。

その弍:DBの処理はRepositoryに任せる

LaravelなどのMVCフレームワークを使用している場合、コントローラーにSQLを直接記述してしまうことで、どんどん肥大化していってしまいます。
おそらく、これが見た目の部分で見づらいなと思う大きな要因になっているのではないでしょうか。
扱うテーブルやカラムが多くなればなるほど、コントローラー内の行数は多くなってきてしまいます。
FormRequest のようにLaravel標準の機能ではない為に少しレベルは上がりますが、DBの処理はRepositoryにまとめることで、コントローラー内をスッキリさせることができます。

その参:コントローラーやメソッド間で重複している処理は共通化する

コントローラーやメソッド間で重複している処理は、共通のメソッドを作成してそのメソッドを呼び出すようにします。

私はポートフォリオを作成中に、ほとんど同じデータで結果も似たようなものだったのにもかかわらず、何度も同じ処理を書いていたので手間がかかりましたし、とても読みにくいコードになっていました。
もし仕様変更などでDBの内容に変更が生じた場合には、その処理を書いているところは全て修正しなくてはならなくなります。
通化しておけば修正箇所は1つで済みますし、プロジェクトに新規参画したメンバーにも分かりやすいコードになります。
しかしながら、共通化できそうなコードを無理に共通化しようとすると、意図しない値の置き換えが生じる場合があるので注意も必要です。

おわりに

いかがだったでしょうか。今回は実体験を元にファットコントローラーを防ぐ方法を幾つか紹介しました。

Laravelは自由度が高いことから、レベル感が異なるメンバー同士での開発ではコーディングにバラつきが出てしまったり、勉強したての頃はコードが煩雑になりやすいです。
個人開発では好きなところに好きにコードをかけましたが、共同開発ではいかに読みやすいコードにするかがポイントになってくると思います。

今回紹介した内容だけでファットコントローラーが解消される訳ではなく、より良い方法や対策案はいくつもあると思います。
また、Laravelは奥が深く、私もまだまだ理解が追いついていない部分も多くありますが、日々勉強し少しずつ前進しています!
スタイル・エッジLABOの一員としてより良いシステムを開発していけるように、これからも全力を尽くします!!!

☆スタイル・エッジLABOでは、一緒に働く仲間を募集しています☆ もし興味を持っていただけましたら、採用サイト↓も覗いてみてください!

recruit.styleedge-labo.co.jp

バッチ処理について

はじめに

はじめまして!スタイル・エッジLABOのKKです!
昨年7月に中途入社し、ドキドキワクワクなエンジニアライフも7ヶ月目に突入しました。
本当に、早いものです。

私は未経験でエンジニアデビューしたので、最初の3ヶ月は有り難いことに、学び多き研修用Webアプリを作らせていただきました。
その後、プロジェクト配属時に意気揚々と「まずはバックエンドの処理を書けるようになりたいです!(かっこいいから..)」と話し、数あるプロジェクトの中でもバックエンド色の強いプロジェクトに配属され、日々切磋琢磨して取り組んでいます。

そんな中でも私が、かっこいい!便利!と感じる機能が「バッチ処理」です✨

初めは「バッチ処理とは..」となっていた私ですが、今ではその威力を感じる毎日です。

今回は、そんな私が普段扱っている、バッチ処理について紹介していきます!

バッチ処理とは

そもそもバッチ処理とは何か、について書いてみます。
まず"バッチ(Batch)"には「一束、一群、一回分、一団」という意味があります。
つまり、バッチ処理とは、「一定量のまとまったデータを集め一括処理」することなのです。

大量のデータに対して処理が走るので、コンピュータのリソースを圧迫して操作に支障をきたすことを防ぐため、夜間に動くものが多いです。

身近なバッチ処理でいうと、銀行の夜間バッチなどが挙げられます。
通常、ATMでお金を預け入れるような単体処理の場合、即座に反映されますが、企業の給与振り込みや企業間取引などは、膨大なデータを扱うため夜間バッチとして行われています。

今私が所属しているプロジェクトでも、夜間に大量のデータを取り込み、一定のロジックに沿って分析業務などを行なっています。

バッチ処理のメリット

ここからは、バッチ処理の良いところを書いていきます。

タスクスケジューリングによって処理を予約できる

バッチ処理の最大のメリットは、あらかじめ決められた時間に決められた処理を自動で実行可能なことです。

そのため、一度ロジックを作ってスケジューリングすれば、そこに人員は必要ありません。

例えば、 「○月●日の△時▲分に処理Aを実行する」のように特定の時間に実行させることもできれば、「毎週月曜日の深夜1時に処理Cを実行する」のように定期的に実行させることもできます。

大規模なシステムではこの方法で、人が動かない深夜帯などに処理を実行させているのですね!

また特に大量のデータを扱う処理であれば、人が操作するのは時間がかかりすぎて現実的ではないため、効率的に処理を行う上で、バッチ処理は欠かせないものです。

f:id:styleedge_tech:20220114182101p:plain

ヒューマンエラーが排除できる

通常、人がデータ処理を行うと、データの数が多いほどミスする可能性も上がってしまいますが、バッチ処理では全てのデータが決められた順番で処理を通っていくため、処理を正しく書けてさえいれば、人為的な操作ミスは起こらなくなります。

f:id:styleedge_tech:20220114182222p:plain

バッチ処理のデメリット

処理内容がブラックボックス化しやすい

私が思うバッチ処理のデメリットは、処理内容が複雑になり、ブラックボックス化しやすいことです。

大量のデータであるほど、全てのデータが全く同じ処理を通るのではなく、データの種類によって条件分岐をし処理を分けることが多いです。

また全自動で実行されるので、開発担当者しかシステムの全容を把握していない、というパターンも起こる可能性があります。

そのためスタイル・エッジLABOでは、そのロジックが誰でもわかるような設計書を作り、わかりにくいところはプログラムだけでなく、設計書も改良していくことを大切にしています!! 😎

エラーによってデータ処理が完了しないことがある

先にも述べたように、バッチ処理は重くなりやすいため、人が操作しない深夜帯などに実行されることが多いです。

ただ深夜帯に行えば全て問題ない訳ではなく、処理時間が長く、システムに負担がかかることも事実です。

そのため、SQLの書き方やアプリケーションの構造によっては、深夜帯にエラーが起きて全ての処理が完了していなかった、なんてことも起こり得るのです。

そのためスタイル・エッジLABOでは、業務で利用するチャットにエラー通知が届くようにしており、早期発見のための工夫をしています。

このように、バッチ処理を扱う際にはただプログラムを書くだけではなく、突然のエラーに常に備えている必要があります。

この辺りの対策はまだまだ私も勉強中ですが、日々システムに触っている中で、「こういう書き方をすると、データ量が多い時にエラーが起こる可能性があるんだ!」と、学んでいます!

最後に

今回は私が普段扱っているバッチ処理について書いてみましたが、いかがでしたでしょうか?
バッチ処理はとても便利で業務効率を上げてくれる反面、ブラックボックス化しやすいため、コード内に補足説明を入れることや、分かりやすい仕様書を作成して日々更新していくこと、が大切だと思っています。💪

またただ動くコードを書けばいいわけではなく、突然のエラーを起こさないための書き方やシステムのメンテナンスも必要になってきます。

とはいえ、バッチ処理は様々なシステムでその威力を発揮しています。

それくらい求められている方法ということですね!✨

システムを長く快適に使えるために、このバッチ処理を活用しながら、これからも改善を繰り返していきます!

☆スタイル・エッジLABOでは、一緒に働く仲間を募集しています☆
もし興味を持っていただけましたら、採用サイト↓も覗いてみてください!

recruit.styleedge-labo.co.jp

WSL2について

はじめに

こんにちは!スタイル・エッジLABOのNTです。
今年の4月に入社し、早くも2021年が終わろうとしています。
入社してからの毎日は学びの日々でした!
社会人としてエンジニアとして成長を実感しています。

私は入社してから半年間、士業を支援するプロダクトの開発・保守の業務に携わりました。
プロジェクト配属直後、開発に必要なLinux環境を構築しました。
今回は、その際に使用したWSL 2について紹介したいと思います!

f:id:styleedge_tech:20211222212831j:plain

WSL 2とは

まず先ほど登場したWSL 2についてです。
正式名称はWindows Subsystem for Linux 2です。
こちらは一言で説明すると「Windows10上でLinuxを動かすことができる仕組み」です。
WSL 1の後継であり、両者には違いがいくつか存在します。

WSL 1とWSL 2の違い

それでは、WSL 1とWSL 2の違いについて3つほど紹介します!

・完全なLinux

WSL 1ではWindows上で完全にLinuxが動作しているわけではありません。
LxCore.sys/Lxss.sysと呼ばれるNTカーネルドライバがLinuxシステムのコールを受け取り、
Windowsシステムコールへと変換しています。
WSL 1は、LinuxWindowsの間に立ち「翻訳」する役割を果たしています。
しかし、この「翻訳」が完全でなかったため一部ソフトウェアが動作しないこともありました。

WSL 1の後継であるWSL 2では、Hyper-Vを利用しLinuxカーネルそのものを動作させることができます。
Hyper-Vは一台のコンピューター上で複数の仮想マシンを稼働させ、
それぞれ別のオペレーティングシステム(OS)を起動することができます。
Hyper-Vを用いて仮想化することで、WindowsカーネルLinuxカーネルを切り離すことが可能となり
Windows上でLinuxを動作させることができるのです。
バイキングで例えるなら、大皿(Hyper-V)の上にお寿司(Windowsカーネル)とカレー(Linuxカーネル)を
一緒によそう欲張りな状態です!

参考:「WSL 2」が正式リリース! ~「WSL 1」とのメリットは? 「Windows Terminal」にも注目 - 窓の杜
  :完全なLinuxがWindows 10上で稼働する? 「WSL 2」とは:Windows 10 The Latest - @IT

f:id:styleedge_tech:20211220184006p:plain

・ファイルアクセス速度

WSL 1では、ファイルアクセス速度が遅いとされていましたが、
WSL 2では改善されファイルI/Oパフォーマンスが向上しています。
実際どの程度高速になるかは、実行しているファイルシステムやアプリによって変わります。
Microsoftのドキュメントには「git clone、npm install、および cmake を使用する場合は、
約 2 倍から 5 倍速くなります。 」と記載されており、かなり速くなっています!

参考:WSL 1 と WSL 2 の比較 | Microsoft Docs

IPアドレスが異なる

WSL 1は、Windowsカーネルを利用しているため、Windows 10と同じIPアドレスを使用します。
WSL 2では、仮想化によってWindows上にWindowsカーネルLinuxカーネルが存在するため
ホストであるWindows 10のIPアドレスを使用することができません。
そのため、Windows 10とは別のIPアドレスを使用します。
WSL 2は、IPアドレスの割り当てをWindows 10の起動時に行い、必ずしもIPアドレスが一定ではありません。
環境構築で詰まりやすい点でもあるため注意が必要です。

参考:前バージョンから大幅に性能向上した新Linux環境「WSL 2」の実力を探る:Windows 10 The Latest - @IT

最後に

今回私が普段使用しているWSL 2について紹介しました。
Windows上で本物のLinuxを手軽に動かすことができるという最大の利点が伝わりましたら幸いです。
また、WSL 2は環境構築が比較的優しいため、初めてLinux環境を作る際にもおススメいたします!

私がプロジェクトに配属された際に、
代表から「スタイル・エッジLABOでは、WSL2以外で環境構築をしている社員が多いため、
新しくWSL 2を使用した環境構築手順を確立してほしい」と頼まれました。
ここから既存の技術や考えに捉われず、新しい技術を積極的に取り入れる風土があると感じています。
エンジニアとして成長することができるそんな環境に身を置いてみませんか?

☆スタイル・エッジLABOでは、一緒に働く仲間を募集しています☆
もし興味を持っていただけましたら、採用サイト↓も覗いてみてください!

recruit.styleedge-labo.co.jp

フレームワークについて

はじめに

はじめまして!スタイル・エッジLABOのYHです。
今年の4月に新卒として入社し、早いもので半年が過ぎました。
時間の流れの速さに震える日々を過ごしております…。

私は6月にプロジェクトに配属されてから現在まで、所謂「開発系」の業務に携わってきました。
LABOではPHPフレームワークを利用した開発を行っており、
9月まで配属されていたプロジェクト内ではFuelPHPを利用していました。
はじめこそ四苦八苦していたフレームワークを用いたコーディングも少しずつ慣れてきて、
FuelPHPと仲良くなれたかも!😊」と思っていた矢先、
10月から新たにジョインしたプロジェクトではLaravelの利用がスタート…!
まだまだ戦っている最中ではありますが、FuelPHPとLaravelの違いについて少しまとめてみました。

エンジニア歴半年ではありますが、初心者だからこそ気づいた違いもある(はず!)ということで、
温かい目でお読みください🙋‍♀️
f:id:styleedge_tech:20211118085117p:plain

0.フレームワークとは

違いを列挙する前に、そもそもフレームワークとは何かということについて整理してみます。
Wikipediaにはフレームワークについて以下の記載がありました。

ソフトウェアフレームワーク(英: software framework)とは、プログラミングにおいて、アプリケーションソフトウェア等の実装に必要となる一般的な機能や定型コードを、ライブラリとしてあらかじめ用意したものである。

(https://ja.wikipedia.org/wiki/%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2%E3%83%95%E3%83%AC%E3%83%BC%E3%83%A0%E3%83%AF%E3%83%BC%E3%82%AF)

プログラミングにおける土台・雛形を用意してくれているのがフレームワークであり、
開発に必要な機能があらかじめ実装されているため工数を減らせるという利点があります。
フレームワークも各言語によって様々な種類があり、どのフレームワークにも得意・不得意な点があります。
何を実装したいか、どのように開発していきたいかによって選定する必要があり、
FuelPHPとLaravelはPHP言語のフレームワークの一種、ということになります。

実装するにあたってフレームワーク
「これがないとプログラミングは不可能!」 というものではありません。
フレームワークがなくてもプログラム自体は書くことはできますし、
独自機能による制限がない分、自由度は高くなると思います。
しかし、個人開発ではなくチームで開発するとなると、
各人が好きなように設計しコーディングしたものを製品として運用するのは
セキュリティ的にも脆弱性が高くなり、なにより保守できなくなってしまいます。
そこでフレームワークを用いて、同じ土台である程度の規約や設定に則ることで、
個々の経験やスキルの差をある程度吸収しつつ、一定の品質のプロダクト開発を実現するというのが
チーム開発においてフレームワークを導入する利点だと思います。

1.学習コスト

FuelPHPでコーディングをしているとき、一番困ったのが
「探しても知見が出てこない(探すのが困難)」ということでした。
調べが甘いのかな?と思い、検索ワードをあれやこれや変えてみても解決方法が見当たらず、
最終的には全然違うフレームワークでのコーディング方法が出てきて行き詰まることもしばしば😥
プロジェクト内外の先輩方にかなり助けてもらっていなんとか解決してきたのですが
コーディング初心者からすると情報がないという状況はかなり辛かったです…。
その点Laravelは公式ドキュメントが充実しており、非公式ですが日本語にも翻訳されています。
ユーザの個々の知見に頼らずとも公式ドキュメントに利用方法や解決策が詳細に記載されているので、
かなり助かっています。
また具体的な実装方法や、エラーの際の詳細な解決方法などといった
公式ドキュメントでカバーしきれない部分はユーザが投稿した情報をネットで多く手に入れることができます。
以上を踏まえて、Laravelの方がFuelPHPよりも学習コストが低く、
プログラミング初心者でも挑戦しやすいと感じました。
f:id:styleedge_tech:20211118085332p:plain

2.コーディングの柔軟性・拡張性

FuelPHPは「規約より設定」を重視して作られていることからも、かなり柔軟性の高いフレームワークです。
またcoreの拡張が容易だったりコーディング規約も比較的緩いことから、
ある程度は自由にコーディングすることができます。
(入社して少し経ったころ、先輩社員から
FuelPHPは生のPHPを書いている感覚と似ている」と教えていただきました。
当時は「そうですかね…?🤔」くらいの感想だったのですが、確かにLaravelと比較すると、
「独自の機能を持った生のPHP」という扱いができると思います。)

一方Laravelは、独自のメソッドが充実しており、開発時においてはそれらを活用することで
自前開発をする必要性を抑えることが出来ます。
フルスタックフレームワークとしての完成度が高いため、
初心者にとっては始めやすく、上級者にとっても開発時の煩雑さを減らせるので、
バランスの良いフレームワークなのではないかな?と思います。

3.フレームワーク独自機能の量

FuelPHPではフレームワークとしての機能にないため、自前で実装を行うことも時々あったのですが、
実装に時間がかかってしまう場面が多々あり、プログラミング初心者の私は
かなり悪戦苦闘しながら開発を進めていました。
(そのたびに先輩方に助けていただきました…!😳
この「自分の技術を互いに共有し合う」文化は、私がLABOで一番好きな特徴です✨)
その点、Laravelでは機能が充実しているため、実装自体がとても簡単です。

最近感動したのがページネーションの実装です。
ページネーション自体は、かみ砕くとそこまで難しいことはやっていない…ようなのですが、
データを分割したり、2ページ目に表示させるデータが何番目のデータになるのかだったり、
これは工数的にどのくらいかかるんだ…??と冷や汗をかいていました。
しかし、Laravelの優秀な機能のひとつにページネーション作成機能があるため、
ほとんどデータを操作することなく、ほんの少しの時間で実装が完了しました。
これは感動モノだったのですが、裏を返せば
「どのような処理が走っているかはわからないけれど、なんか動いた」 という状態であり、
私のような初心者プログラマが訳も分からずフレームワークの機能で実装していると、
バグが発生したときに対処できない可能性も出てきています。
実装に省略できた時間を処理内容を理解する時間に充てるなどして、
機能に頼りすぎないことも大切かな、と感じました。
f:id:styleedge_tech:20211118091338p:plain

最後に

FuelPHPとLaravelの違いについてまとめてみましたが、いかがだったでしょうか?
今回紹介した内容と私の観点を含めて、FuelPHPとLaravelについて以下の表にまとめてみました。 f:id:styleedge_tech:20211124140518p:plain
なんだかこう見ると「Laravel圧勝!?」という感じなのですが、
プログラミング学習という点においてLaravelは
メソッドが豊富な分処理の内容がブラックボックスになりがちという特徴があると思います。
私のようなプログラミング初心者の場合は、メソッドの内容をよく理解したうえで使用していくのが
今後の知見を増やすという意味でも大切だと感じました。

フレームワークを利用すると素早く簡潔に実装できるという利点があります。
また初心者でもコーディングしやすく、理解もしやすいため、技術力の向上も早くなると思います。

弊社ではプログラミング未経験でも、かなり本格的に実装を経験することができ、
また学びあう風土があるため、エンジニアとしてどんどん成長することできる環境です!

☆スタイル・エッジLABOでは、一緒に働く仲間を募集しています☆
もし興味を持っていただけましたら、採用サイト↓も覗いてみてください!
recruit.styleedge-labo.co.jp

OSI参照モデルとネットワークを構成する機器(ルータ、スイッチングハブ)

はじめに

はじめまして!スタイル・エッジLABOのYAです。 今年度新卒で入社しました!
僕は入社後のプロジェクト配属の時に、「ネットワークに関わってみたいです!」と希望を出したので、
現在は弊社や弊社のクライアントの方々のネットワークを構築・管理する役割を担っています。
基本的に希望が通る環境なので、自分の興味や関心のあるプロジェクトに所属できるのは
ありがたいなと思います😊
今回はそんな僕が普段設定・管理しているネットワークデバイスについて紹介していきます!
f:id:styleedge_tech:20211011135825p:plain

ネットワークデバイスとは?

ネットワークの中でのデータのやり取りをコントロールする機器たちのことです。
コントロールというのは、データをどこに向かって送るのか、逆に送らないようにするのか
などといった 通信におけるデータの流れを制御することです。

ネットワークでの通信のルール

このデータの流れを制御するには、各メーカーの各デバイスで共通したルールが必要です。
たとえばA社の製品とB社の製品が異なるルールでデータをやり取りしていたら、通信ができなくなります。
ちょうど人がお互いに知らない言語でコミュニケーションを取ろうとしてもうまくいかないようなものです。

そこで、通信のベースとなる共通のルールを定めるために、OSI参照モデルというものが生み出されました。
OSI参照モデルは、7つの階層構造で表されます。
そして、各階層のルールにしたがって通信を行うネットワークデバイスを定義します。
こうすることで、ネットワークデバイスたちはたとえ製品が異なったとしても、
各階層ごとの共通のルールにしたがって通信ができるようになります。
たとえば、A社、B社関わらず第3層を担当するデバイスは、第3層のルールに基づいて
データを送受信し、同様に第2層担当のデバイスは第2層のルールに基づいて
データを送受信するというように、各デバイスは各階層のルールにしたがってデータをやり取りします。
また、ルールを階層構造で表すことで一連の通信の中で各デバイスの役割分担ができるようになります。

ここからは各層のルールと代表的なネットワークデバイスとして
第2層を担当するスイッチングハブと第3層を担当するルータについて説明します!
1、4ー7層には申し訳ないですが、ここでは割愛させていただきます😌

      f:id:styleedge_tech:20211026135531p:plain

スイッチングハブ

第2層のネットワークデバイスです。
第2層では、ざっくり説明するとMACアドレスという情報をもとに、
同じネットワーク内の直接接続されている機器とデータをやり取りします。
MACアドレスとは、各機器に割り当てられる一意の番号のことで、
同じネットワーク内で「誰に」データを送るのかを表すための情報です。
スイッチングハブは、自分に接続されているPCなどの機器のMACアドレスを認識し、
そのMACアドレスの情報をもとに正しい送信先にデータを送り、
正しくない送信先にはデータを送らないようにして、データのやり取りを制御します。

〇ルータ

第3層のネットワークデバイスです。
第2層が同じネットワーク内の機器とのやり取りを行っていたのに対して、
第3層では、異なるネットワークにある機器との間でデータをやり取りします。
また、データをやり取りするための最適な経路を決定します。

ここで、話は少し逸れますが異なるネットワークとは何かについて説明します。
現在は世界中がネットワークに接続し、どこからでも動画の視聴やSNSでのやり取りが可能です。
このような世界規模のネットワークは、大きな1つのネットワークがあるのではなく、
実は小さな異なるネットワークが互いにつながることで構成されています。
日本の自宅から海外にいる友人にSNSでメッセージを送るとき、そのデータは自宅のネットワークを出発し、
SNSを管理するネットワークやその他の様々な小さな異なるネットワークを経由して、
友人の自宅のネットワークに到着します。

ルータはこのような異なるネットワークとのデータのやり取りをIPアドレスという情報をもとに行っています。
IPアドレスは、「どこの」ネットワークの「誰に」データを送るかを表すための情報です。
この情報をもとに、ルータは指定された異なるネットワークにデータを送信し、
さらにそのネットワーク内の別のルータが次の異なるネットワークにデータを送信することで、
次々にネットワークを経由してデータが正しい送信先に送られます。
f:id:styleedge_tech:20211011135943p:plain

最後に

今回はネットワークデバイスのうち特に代表的なスイッチングハブとルータについて紹介しました。
一言でネットワークや通信といっても、今回のようなデバイス間のものだけでなく、
クラウドサービスやシステム間でのネットワークや通信などもあり、非常に奥が深いです🤔
今後は、ネットワークに関する知識を武器にさまざまな分野に足を踏み込んでいきたいです!
このように自分の興味のある分野から派生して、興味・関心を広げていけるのは
エンジニアとしてとても楽しいですし、弊社にはそれを実現するための環境が備わっていると思います!


☆スタイル・エッジLABOでは、一緒に働く仲間を募集しています☆
もし興味を持っていただけましたら、採用サイト↓も覗いてみてください!
recruit.styleedge-labo.co.jp