単なるstaticとsingletoneパターンの違い

オブジェクト指向プログラミングのプログラミング手法で、「singleton(シングルトン)」と呼ばれる手法があります。
この手法は、プロジェクト内で共通的に使われるインスタンスを1つだけ予め作成し、外部のクラスにはそのインスタンスを使用させる、という手法です。

この手法を学んだ時に「インスタンスを1つにすれば良いなら、クラス変数やメソッドをstaticにするだけで良いのでは?」と思ったので、試しにテストコードを書いてみました。
単純なstaticでは使用者側で好きにインスタンスを作成できる(実態としては1つ)のですが、singletonとしてインスタンスを作成すれば使用者側でのインスタンス作成を防ぎ、明示的にインスタンスを1つにできることを確認しました。

【テストコード】

・StaticMemory.java

・SingletoneMemory.java

・MemoryTestMain.java

【実行結果】


いかがでしたでしょうか。

「singleton」は、GoFの23個のデザインパターンの内の1つに数えられる手法です。
デザインパターンはオブジェクト指向プログラミングのプログラミング手法をまとめたものであり、効率的なプログラミングを行う上で有効ですので、これからも紹介していくと思います。

では、また次回!

基本情報処理技術者試験のpythonのサンプル問題を解いてみた

2019年10月28日にIPAから基本情報処理技術者試験のpythonのサンプル問題が公表されたので、解いてみました。
既に基本情報技術者試験.comから解説も出ています。

【2019年10月28日公開】基本情報技術者試験 Pythonサンプル問題|基本情報技術者試験.com

解いてみた感想を一言で言うと、
「pythonに詳しくなくても、頑張ってトレースすれば解ける」
と思いました。
シラバスの内容を照らし合わせて考えてみても、pythonの細かい文法知識を問われているというよりは、pythonを使って各種ライブラリを使いこなせるかどうかを問われていると思って良いでしょう。

サンプル問題を全問正解するには、以下に示す前提知識が必要になります。
高校の文系数学の知識はないと厳しいですが、pythonの知識はそこまで深く問われているわけではありません。
疑似言語を解けるだけの技量があれば、pythonの知識不足で点を落とす可能性があるのは9問中1問(設問2のc)だけだと思います。
言語選択の問題で、自分が得意な言語の問題がたまたま苦手な問題であった場合、試しにpythonの問題を見てみても良いのでは、と思いました。

【数学の知識】

・0度の場合はx軸を右側に進む

・一回転は360度である

・三角比の知識

【pythonの知識】

・文字列のスライシング

例えば、x[2:5]とすれば3文字目から5文字目を取得できる。
始点と終点は省略できる。
(人によっては、知らなくても選択肢から察しがつくかもしれない)

・stackの使い方

(ただし、設問中のロジックを見れば、知らなくても十分に察しがつく)


ちょっと今回は普段とは毛色の違う記事を書いてみました。

基本情報処理技術者試験は既に合格済みなのですが、合格した後も試験内容の変更には敏感であった方が良いのではないかと思っています。
いつの間にか新人でも知ってるようなことを知らない技術者になってしまったら悲しいですからね。

では、また次回!

java:イミュータブルなクラスを作る方法

「イミュータブル」とは「不変」という意味で、オブジェクト指向の世界では「状態(クラス変数)がオブジェクト生成時から変更されないこと」を指します。
有名所では、Stringがイミュータブルなクラスとして知られています。

イミュータブルなクラスを自作するためには、以下の条件を満たすようにクラスを作成する必要があります。

①クラス変数はprivateで定義する

クラス変数が外のクラスから直接書き換えられるのを防ぐため

②setterメソッドを定義しない

クラス変数を書き換えるメソッドは定義しない

③クラスをfinalで定義する

サブクラスでクラス変数を書き換えられるのを防ぐため

④ミュータブルなオブジェクトへの参照を含んでいない

ミュータブルなオブジェクトを変更する処理を記述しないようにするため

クラスをイミュータブルにするメリットとしては、内部の状態の変化を気にする必要が無くなるために処理を追いやすくなるというのがあります。
関数型プログラミングにも通じる考え方なのですが、オブジェクト生成より後で内部の状態が書き変わることがないため、クラスのメソッドが返す値が一定となり、内部の状態の変化を気にしながらトレースする必要がなくなります。
また、
hoge2 = hoge1;
のようなオブジェクトのアドレスのコピーが使いやすくなります。
イミュータブルではないクラスでこのコピー方法を用いると、hoge2の状態の変化がhoge1にも影響してしまい(hoge1とhoge2で同じアドレスを指しているため)、思わぬ影響が出ることがあります。
しかし、イミュータブルなクラスであれば、状態を変化させようと思えば新たにオブジェクトを作り直して新たなメモリ領域を確保しなければならないので、hoge2の状態を変化させても(オブジェクトを作り直しても)hoge1へ影響するのを防ぐことができます。
イミュータブルではないクラスでも、オブジェクトの中身全体をコピーすれば思わぬ影響を防ぐことはできるのですが、オブジェクトのアドレスをコピーする場合と比べてメモリ使用量が増え処理も遅くなります。

以下で、イミュータブルなクラスを定義してそれを使用するサンプルコードを示します。

【サンプルコード】

・ImmutableCalc.java

・ImmutableTestMain.java

【実行結果】


いかがでしたでしょうか。

オブジェクトの内部の状態を意識する手法には多くの技術者が慣れ親しんでいると思いますし、オブジェクトのアドレスのコピーも便利ですが、それが思わぬ影響を及ぼすこともあります。
思わぬ影響を防ぐために、イミュータブルなクラスを作成する手法は覚えておいて損はないでしょう。

これからも役に立つ情報を発信していきたいと思います!

「コピー新規(修正新規)」とは

【背景】

金融系SIerでは「コピー新規」という言葉を聞くことがあります。
(「修正新規」と呼ばれることもあります)
特定の現場だけではなく複数の現場で聞いたことがあるので、一種の業界用語だと思います。
しかし、コピー新規という言葉でWeb検索をかけてもヒットしないので、この記事を書くこととしました。

【言葉の説明】

「コピー新規」とは、「既存のソースファイル(プログラム)を丸ごとコピーし、コピーしたソースファイルに対して必要な個所だけ改修することで、新たなソースファイルを作成すること」を指します。

【コピー新規を行う理由】

コピー新規は、金融系の巨大なレガシーシステム(数百万~数億STEP)を改修する際に、高品質と工数圧縮を両立する現実的な最適解として行われます。
具体的には、以下の2つの理由で行われます。

理由1:システムの既存部分への影響を防ぐ

既存のソースファイルを修正して複数の要件に対応できるようにする場合、システムの既存部分への影響が懸念されます。
しかし、コピーして新たなソースファイルを作成すれば、システムの既存部分への影響を防ぐことができます。

理由2:既存部分を流用することでテスト工数を削減する

本番運用で動いている既存のソースファイルは、品質が保証されたものです。
そのソースファイルの中から使える部分は流用することで、その部分に対するテスト工数を削減することができます。

【コピー新規の欠点】

コピー新規を行うことで、以下の2つの欠点があります。

欠点1:改修した箇所が流用した箇所に影響を与えてバグになるリスクがある

コピーしたソースファイルについて、改修した箇所と流用した箇所は適切にスコープ分割やクラス分割されているわけではないので、改修した箇所が流用した箇所に影響を与える可能性があります。
流用した箇所はテストを省略している(理由2より)ので、影響を与えていてもそれに早い段階で気付くことが難しく、リリース直前やリリース後にバグとして顕在化する可能性が高まります。

欠点2:将来の改修が困難になる

ソースファイルの流用箇所については丸ごとコピーされるため、将来その流用箇所に修正が発生した場合、流用箇所を全て洗い出した上で同じ修正を複数のソースファイルに対して行う必要があります。
このことにより、将来の改修コストが増大します。

【欠点への対策】

コピー新規の欠点に対して、以下のような対策が行われることが多いです。
(これらの対策はコピー新規に限った話ではないですが)

対策1:コーディング規約をガチガチに固める

人によって癖があるソースコードの記述方法について、コーディング規約がガチガチに固められていることが多いです。
例えば、「それぞれのメソッドに番号を割り振り、メソッド内でのみ使われる変数名の先頭にその番号をつける」という規約を設ければ、当該の変数が当該のメソッド外に影響を与えることを防ぐことができます。
また、「『商品』は全て『Shohin』と記述する」というルールを設ければ、『Syohin』『Shouhinn』『Commodity』といった似たような単語を使われるのを防ぐことができ、影響分析が容易になります(単語検索漏れを防ぎやすくなります)。

対策2:影響調査ツールを導入する

巨大レガシーシステム向けに、影響調査ツールを提供するベンダーが複数存在します。
例えばNTTデータ社の「TERASOLUNA DS」の機能として「トレーサビリティー機能」が提供されていたり、NCS&A社が「REVERSE PLANET」というツールを提供していたりします。
これらのツールを用いることで、最新の資産状況が明らかになり、その最新の資産状況で単語検索や構成図確認を行うことができるようになります。

対策3:人海戦術に頼る

コーディング規約や影響調査ツールだけで改修コストの増大に対応するには限界があるので、最後には人海戦術に頼ることになります。
(金融機関に開発資金があるからこそできる対策です)
単純に協力会社やオフショアから開発者を集めるだけでなく、集めた開発者にシステム開発に参加してもらう仕組みを作ることが肝になります。
新たに参画する開発者は少なくとも個社システム独自のことは知らないですし、開発者のレベルにもバラつきがあります。新たに参画する開発者が品質の低いプログラムを開発して後に問題になることは少なくないですし、多数の開発者から一人の有識者に一気に質問が集中することで有識者の本来の業務に支障をきたすこともあります。オフショアの場合は、言語や文化の違いからコミュニケーションが困難になる場合もあります。
そのため、「開発手順の整備」「コミュニケーションの改善(例:窓口役を設ける、英語教育をする)」「独自フレームワークの構築(どのような開発者でも一定の品質の開発ができるようにする、プログラミングせずにシステム開発できる例も)」といった仕組み作りで対応する必要があります。

【リファクタリングしない理由】

現在のプログラミングの潮流は、「適切にクラス分割して、重複した記述はなるべく排除する」というものです。
記述が重複しそうな場合は、クラス分割をやり直して記述の重複を極力防ぐ(リファクタリングする)のが筋です。
「記述の重複を許容し、その代わりコーディング規約や影響調査ツールや人海戦術で悪影響を防ぐ」というのは、その潮流に逆行しているように見えます。

しかし、金融系のレガシーシステムは、その多くがCOBOLの時代に書かれたものです。
COBOLにはオブジェクトの考え方どころか、メソッドやスコープの概念すらありません。
(言語としての機能の問題だけでなく、COBOLが使われていた当時はオブジェクト指向の概念も広まっていなかったはずです)
当然、テストコードなんてものも存在しません。
仮にjavaでリプレースされていたとしても、そのjavaのソースコードはCOBOLのソースコードを自動変換したもので、中身はCOBOLっぽい構造になっているはずです(1クラスが数千~数万STEPある、変数が全てクラス変数になっている、等)。
その時代に書かれたソースコードが数百万~数億STEPも存在しているので、システム全体をリファクタリングするには非現実的なコストがかかります。

現在ではFintechベンチャーが活躍していますが、ベンチャー企業が手を出しているのは仮想通貨やロボアド等の、金融系から見れば傍流のシステムであり、銀行や証券等の基幹システムに手を出すという話はまだ出ていません。 基幹システムは新規参入の企業が容易に手が出せる規模のシステムではなく、これらのレガシーシステムのソースコードが今風のソースコードに淘汰されるというのも現在では考えにくいことです。 海外ではパッケージソフトで大型リプレースをかけるということも行われているようですが、日本の独自の商取引ルールが詰め込まれた基幹システムをパッケージソフトで代替するというのも困難で、レガシーシステムのソースコードは残り続けると思っています。
(比較的歴史の浅いシステムでは事例がないわけではないです。例えば、大阪証券取引所(現日本取引所)の証券デリバティブ取引システム「J-GATE」をNASDAQ OMX社製のパッケージソフトでリプレースした例はあります。)

そのため、既にリファクタリングが手遅れになってしまったソースコードを受け入れ、そのようなソースコードとの付き合い方を考えることが現実的な解だと思っています。
「コピー新規」は、そのための手段の一つと言えます。


いかがでしたでしょうか。

プログラミングについて学んでいくと、「なぜコピー新規のようなことをするのか」と疑問が沸いてくると思います。
(私も疑問が沸きました)
今回は、単に言葉の説明だけでなく、そのような疑問にも答えられるように意識して記事を執筆しました。

今回の記事のような内容は、実際に業界に携わらないとなかなか実感が湧かないことで、それを文章に起こすことには意味があると思っています。
業界経験を基にした記事は、これからも書いていこうと思っています!

java8:関数型インターフェースの背景にある考え方

【前置き】

Java8から関数型インターフェースが使用可能になりました。
具体的に「ラムダ式」「Stream」「Optional」「Files」と言った方がわかりやすいでしょうか。

関数型インターフェースの使用を半ば強制されるフレームワークが登場していたり(例:Apache Spark)、関数型インターフェースでJavaを書く開発者も増えてきたので、目にすることも多くなってきたかと思います。

関数型インターフェースは関数型プログラミングをサポートするものであるため、従来からJavaでサポートされていたオブジェクト指向プログラミングとは発想が異なります。
そのため、従来のJavaを学習してきた方にとっては抵抗感を感じるものであると思います。

今回の記事では、抵抗感を少しでも減らすために、関数型プログラミングの考え方を簡単に紹介したいと思います。

【サンプルコード】

言葉で説明するよりも先にサンプルコードを見た方がわかりやすいと思うので、サンプルコードを先に紹介します。
年齢のリストから30代の人数を数える、というプログラムです。
ごく短いプログラムですので、お付き合いください。

・FunctionTest.java

・実行結果

【関数型プログラミングの考え方】

関数型プログラミングでは、以下のことを実現しようとしています。
色々難しい用語(例えば「副作用」等)はあるのですが、今回は用語を使わずに簡潔にまとめます。

・内部状態(State)を排除する

最も本質的な考え方です。

関数型プログラミングでは、内部状態を排除することを目的としています。
「内部状態」とは、上記のコードで言うと「count」や「i」を指します。

内部状態が入りこんでしまうと、内部状態により関数の結果が変わってしまうため、内部状態を把握する必要が出てきてしまい、可読性が悪化します。
(把握のために「count」や「i」をトレースする必要が出てきてしまう)
把握しきれずに意図しないバグを出してしまうことも珍しくありません。
内部状態を排除して、品質を上げよう、という発想です。

また、コンピュータにとっては内部状態は重要ですが、人間にとってはやりたいことを実現できれば良く、内部状態は重要ではありません。
重要ではない記述を削減することでコードを完結にしたい、という発想もあります。

Java8のラムダ式では、ラムダ式の外部で定義された変数の値をラムダ式の内部で変更することを禁止されています(コンパイルエラーになる)。
その背景には、内部状態の排除があると思っています。

・自然言語に近い形で処理を記述する

これは、コードが簡潔になった結果生じた副次的な考え方かもしれません。

関数型プログラミングでは、関数を組み合わせることにより処理を実現します。
関数を次々とつなぎ合わせるように記述することで、ソースコードが自然言語に近い形になります。
わかりやすく言えば、ソースコード自体がコメントのようになります。

例えば、サンプルコードでは「年齢のリストから30代の人数を数える」という処理を行おうとしています。
従来のプログラミングでは、これを実現するためにforループとかカウント用の変数を使用しており、何をしているのか把握するためには、内部状態をトレースして意図を汲み取る必要があります。
しかし、関数型プログラミングでは、
「list.stream().filter(x -> x >= 30 && x <= 39).count()」→
「listを30<=x<=39でfilterしてcountする」
と読めるため、
「年齢のリストから30代の人数を数える」
という処理であることを自然に把握することができます。


いかがでしたでしょうか。

IT業界、特にSIer業界だと、情報処理技術者試験を軸にして知識を身に付けることが多いかと思います。
しかし、情報処理技術者試験では手続き型プログラミングやオブジェクト指向プログラミングを中心とした出題で、関数型プログラミングが扱われることは全く言って良いほどありません。
そのためとっつきにくさは拭えないと思いますが、先進的な企業を中心に関数型プログラミングを取り入れる企業も出てきています。
これからのことを考えると、せめて、関数型プログラミングに対する抵抗感は払拭するべきではないかと思っています。

今回はこれで締めくくりたいと思います。
では、また来週!