新人エンジニアが初めてC#のデリゲートを使った時の話

こんにちは。弥生の木下です。この記事は Misoca+弥生 Advent Calendar 2019 15日目の記事です。

私は2019年に入社した新人エンジニアです。研修中は主にJavaを使って開発を行っており、現在のチームに配属されてから初めてC#を使い始めました。

今回は、C#を学習していく中で行き詰った「デリゲート」について、どこの理解が困難だったか、それをどのようにして理解したかをまとめてみようと思います。

デリゲートって何に使うの?何が便利なの?

私はデリゲートを学習した際に、文献で見たサンプルコードは次のようなものでした。

using System;

delegate void SampleDelegate();

class sample
{
    static void SayHello()
    {
        Console.WriteLine("こんにちは");
    }

    public static void Main()
    {
        //デリゲートの生成
        SampleDelegate sd = new SampleDelegate(SayHello);
        //デリゲートを通してSayHello関数を呼び出す
        sd();
    }
}

このコードを見て、私は「これ、デリゲートを使う意味ある?」「何がしたいの?」といったことを思いました。今見ても、この例ではデリゲートの良さはわからないなと思います(笑)

この時点で、デリゲートは「関数を何かのオブジェクトにする」というところまではわかっていました。しかし、それを何に使うのか、何のために行っているのかが全く理解できませんでした。

トレーナーからヒントをもらった

文献のソースコードを見ても、書き方はわかったもののやはり目的がわからなく、何が便利なのかさっぱりだったので、後日トレーナーに相談しました。すると、次のようなコードを考えてみようと、例を提示してくれました。

using System;
using System.Collections.Generic;

class Person
{
    public int _id;
    public string _name;
    public string _address;
}

class Program
{
    static void Main(string[] args)
    {
        List<Person> personList = new List<Person>();
        //personListをソートする関数を作る(ID順、名前順、住所順)
    }
}

これを見て私が一番最初に書いたコードは次のように、ID、名前、住所それぞれでソートする関数を別々に作成してしまいました。

//IDでのソート
public static List<Person> SortById(List<Person> personList)
{
    for (int i = 0; i < personList.Count - 1; i++)
    {
        Person min = personList[i];
        int k = i;

        for (int j = i + 1; j < personList.Count; j++)
        {
            //比較
            if (personList[j]._id < min._id)
            {
                min = personList[j];
                k = j;
            }
        }
        Person temp = personList[i];
        personList[i] = personList[k];
        personList[k] = temp;
    }
    return personList;
}

//Nameでのソート
public static List<Person> SortByName(List<Person> personList)
{
    for (int i = 0; i < personList.Count - 1; i++)
    {
        Person min = personList[i];
        int k = i;

        for (int j = i + 1; j < personList.Count; j++)
        {
            //比較
            if (string.Compare(personList[j]._name, min._name, true) < 0)
            {
                min = personList[j];
                k = j;
            }
        }
        Person temp = personList[i];
        personList[i] = personList[k];
        personList[k] = temp;
    }
    return personList;
}

//Addressでのソート
public static List<Person> SortByAddress(List<Person> personList)
{
    for (int i = 0; i < personList.Count - 1; i++)
    {
        Person min = personList[i];
        int k = i;

        for (int j = i + 1; j < personList.Count; j++)
        {
            //比較
            if (string.Compare(personList[j]._address, min._address, true) < 0)
            {
                min = personList[j];
                k = j;
            }
        }
        Person temp = personList[i];
        personList[i] = personList[k];
        personList[k] = temp;
    }
    return personList;
}

これをトレーナーに見せたところ、「この3つの関数は共通化できるね。」と言われました。つまり、1つ「ソートをする」という関数を作って、何でソートするかをメインメソッドで指定するような関数を作れば、ソートの処理を1つの関数で共通化できるのです。

「共通化」を目標にコードを書いてみた

3つのソート関数で、「比較する対象と比較の方法」以外は全く同じ処理を書いていることには気が付いたので、比較の部分のみをメソッドに切り出すところまではできました。

デリゲートを使わなかったら詰んだ

今までの私の知識で、共通化したソート関数は次のようにif文で分岐したコードになりました。

//指定した条件でのソート
public static List<Person> Sort(List<Person> personList, string sortSign)
{
    for (int i = 0; i < personList.Count - 1; i++)
    {
        Person min = personList[i];
        int k = i;
        bool isLower = false;

        for (int j = i + 1; j < personList.Count; j++)
        {
            //sortSignの値によって比較方法を変える
            if (sortSign == "id")
            {
                if (CompareId(personList[j], min))
                {
                    min = personList[j];
                    k = j;
                }
            }
            else if (sortSign == "name")
            {
                if (CompareName(personList[j], min))
                {
                    min = personList[j];
                    k = j;
                }
            }
            else if (sortSign == "address")
            {
                if (CompareAddress(personList[j], min))
                {
                    min = personList[j];
                    k = j;
                }

            }
        }
        Person temp = personList[i];
        personList[i] = personList[k];
        personList[k] = temp;
    }
    return personList;
}

//比較する関数の定義
public static bool CompareId(Person person1, Person person2)
{
    return person1._id < person2._id;
}

public static bool CompareName(Person person1, Person person2)
{
    return string.Compare(person1._name, person2._name, true) < 0;
}

public static bool CompareAddress(Person person1, Person person2)
{
    return string.Compare(person1._address, person2._address, true) < 0;
}

この書き方はまず条件分岐が多く、長くて見にくい。そして最大の問題は、もし新たな順序のソートを追加する場合、さらに条件分岐を追記していかなくてはなりません。さらに関数内のソースコードが長くなります。今までの知識だと、ここで「詰み」なのです。

デリゲートを使ったら書けた

そこで、「デリゲートを使って書いてみよう。」というトレーナーのヒントから、コードを書いてみました。

using System;
using System.Collections.Generic;

delegate bool MyDelegate(Person person1, Person person2);

class sample
{
    //指定した条件でのソート
    public static List<Person> Sort(List<Person> personList, MyDelegate md)
    {
        for (int i = 0; i < personList.Count - 1; i++)
        {
            Person min = personList[i];
            int k = i;

            for (int j = i + 1; j < personList.Count; j++)
            {
                if (md(personList[j], min))
                {
                    min = personList[j];
                    k = j;
                }
            }
            Person temp = personList[i];
            personList[i] = personList[k];
            personList[k] = temp;
        }
        return personList;
    }
}

これなら、もし新たな順序のソートを追加する場合、デリゲートに格納する比較関数を変えるだけで対応でき、ソート関数の中身は一切いじらずに対応できます。

デリゲートを使うことによって、うまくソート関数を共通化することができました!

ここまでわかってようやくデリゲートを使う意味、その価値がわかりました。

まとめ

私がデリゲートとは何なのか、何のために使うものなのかを理解できたきっかけは、「共通化」という言葉でした。もっとも、デリゲートの目的の一つが共通化なので当然ですね(笑)

今後ソースコードを書く上で、「共通化」は非常に大切であることを実感することができました。今後もより良いソースコードを書けるよう、継続的に学習を重ねていきたいと思っています!

最後に

Misoca+弥生 Advent Calendar 2019 の16日目は、higo_yayoiさんです。 お楽しみに!

Webアプリケーションをつくろう!開発未経験の新人が挑戦した話

こんにちは、弥生の松坂です。この記事は「Misoca+弥生 Advent Calendar 2019」の14日目の記事になります。

タイトルにもある通りプログラミング未経験で、今年の4月に弥生へ入社しました。そんな私がWebアプリケーション開発の研修を通し、学んだことを書いていきたいと思います。

どんなアプリケーションをつくったの?

以下の機能を備えた社員情報管理システムを実装しました!

  • 一覧表示
  • 新規追加、編集、削除
  • 検索
  • CSVファイル出力 

Webアプリケーションの処理の流れを知ろう!

■使用した言語

  • HTML:Webページ(画面)の文章構造や画像などを定義する
  • CSS:Webページ(画面)の色・サイズ・レイアウトなどを指定する
  • JavaScript:Webページ(画面)に動きを与える
  • Java(Servlet):サーバ上でWebページ(画面)の動的な生成やデータの処理を行う
  • SQL:データベースの操作や定義をする 

■データの流れ

説明だけだとイメージがつかみにくいので、下の図を見てください。緑の矢印がリクエスト、青の矢印がレスポンスを示しています。

f:id:ym_AdventC:20191211115518p:plain

実装してみた

要件定義書を読み設計書に書き起こす際、正直初心者にWebアプリケーションなんてつくれるの!?と思いながら研修を受けていました。

■当時の私の気持ち

  • 各言語の定義や関係性がピンと来ない
  • 使用する言語が多く、一つの言語を扱うのにも一苦労
  • 調べ方や質問の仕方が分からない(どこが理解不足なのか把握できていない)
  • エラーが発生したら一大事…どこをどう修正すればいいの?

記載されている単語が分からないため、「初心者向け」と書かれている記事ですら理解できませんでした(泣)

 ■ここで苦労した!動的に生成したボタンへのクリックが効かない…

実装していく中でつまずいたところはたくさんありました。その中でも1番最初にぶつかった壁が、動的に生成した編集ボタンにクリックイベントが効かないことでした。

◇「静的」・「動的」とは?
  • 静的ページ:Webページ内のコンテンツが一切変化しない
  • 動的ページ:Webページ内のコンテンツが変化する

つまり、元々あるものか、アクセスするたびに生成されるのか、という感じです。
そもそも「静的」・「動的」なんて概念のない私にその謎が解けるはずもなく、プログラミング経験のある同期や先輩に助けてもらいました。

◇「click」と「on click」のちがい

私が最初に書いていたソースコードは「click」を使用していました。

$(document).ready(function() {
 $(“#edit-button”). click (function(){
   //行いたい処理
 });
});

これだと動的に生成したボタンをクリックできません。ということで、「on click」に変更してみると…「クリックできた!」

$(document).ready(function() {
 $(document).on("click", "#edit-button", function() {
   //行いたい処理
 });
});

このとき、「静的な」要素には「click」を、「動的な」要素には「on click」をという認識で解決してしまったのです。後から「「on click」を使っても、クリックできない」という言葉を聞いた私は、またまた混乱してしまいました(笑)

実は、onを呼び出すときに奇跡的にdocumentを使っていたのですが、以下のように書いてしまうとクリックできません。

$(document).ready(function() {
 $(“#edit-button”).on("click", function() {
   //行いたい処理
 });
});

HTMLを読み込んだ時に、動的に生成したボタンのidがまだ存在しないかららしいです…。documentを使えばHTMLの全ての要素が対象になるのでクリックできました。

プログラミングを通して学んだこと

プログラミング未経験ながらWebアプリケーションをつくってみて、成長したなと思えるところがあります。

■始めたばかりの頃は...

  • 一度に全てを理解しようとしたり、全体を捉えようとしたりする
  • つまずいたとき手を動かさず、ソースコードを眺めているだけ
  • 質問する際に指示語が多く、説明が抽象的である
  • 分からないのに、1人で解決しようとする
  • 1つの方法しか考えない

■成長したこと

  • 文書や図で書き出すことで関係性が見え、考えを整理できる
  • 実装したい処理に対して、まずは小さく分割し段階を踏んで考える
  • とにかく手を動かしてソースコードを書いてみる
  • 時間で区切って、分からなければ質問する
    (指示語はなるべく減らし具体的に話すことで、自分の理解も整理できる)
  • 解決方法をいくつか探す

なんとかWebアプリケーションをつくることができましたが、まだまだ課題はあります。

■今後やってみたいこと

  • ユーザー目線に立ちUI・UXを考える
  • クラス・メソッド・定数など機能の役割を考え、管理や修正がしやすいソースコードを心がける
  • 例外処理で問題を検知し、対処できるようにする

最後に

初心者の私でもWebアプリケーションを作りきることができました。
この記事で1番お伝えしたかったことは、分からなくても間違っていても良いのでアウトプットすること。分からないなりにソースコードを書いたり、自分の理解を口に出したりすると、意外な発見があったりします。
今後は配属されたテスト自動化チームで、新たなアプリケーション開発に挑戦してきたいと思います。

明日、「Misoca+弥生 Advent Calendar 2019」の15日目の記事はykinoshittaさんです!

競技プログラミングのすすめ

はじめに

こんにちは。弥生のミズタカです。
この記事は、Misoca+弥生 Advent Calendar 2019の13日目の記事です。
私は競技プログラミングを少々かじっており、せっかくなので記事にしてみました。
競技プログラミングは特にプログラミング初心者におすすめだと思います。

競技プログラミングって??

競技プログラミングとは、主にオンラインで開催されるプログラミングコンテストの一種です。
参加者全員に同じ問題が出題され、いかに「早く」「正確に」問題を解くコード(コンソール出力アプリ)を書き、多くの点を取得できるか競います。
コンテストを開催するサイトは多々ありますが、どこも問題の出題方式はほとんど変わりません。
問題は「問題文」「制約」「入力形式」「出力形式」「入出力例」で構成されています。

以下に、多くの人がプログラミングを勉強し始めに目にしたであろうフィボナッチ数列を例に、問題のイメージを作ってみました。

■問題
入力として整数a,bが与えられます。
フィボナッチ数列のn番目の項をFnとして、「Fa+Fb」の値を出力しなさい。

■制約
1 <= a < b <= 100

■入力形式
以下の形式で入力が与えられる。
a b

■出力
Fa+Fbを1行で出力し、末尾には改行を入れること。

■入出力例①
入力:2 4
出力:4

■入出力例②
入力:3 7
出力:15

競技プログラミングのポイント

競技プログラミングでは、いかに「早く」コードを書くかが一つのポイントですが、早く書き上げれば何でもいいというわけではありません。
そのコードの「実行時間」や「メモリの使用量」なども判断基準となり、効率的なコードでないと得点が取得できなかったり、減点の対象になってしまいます。

例えば、前述の問題を解くのに、以下のような再帰メソッドを用意したとします。
この再帰メソッドは、フィボナッチ数列の漸化式を単純に再現したものです。

// Fnを求める再帰メソッド
public int fibonacci(int n)
{
    if(n == 0)
    {
        return 0;
    }
    if(n == 1)
    {
        return 1;
    }

    return fibonacci(n-1) + fibonacci(n-2);
}

これは処理速度が遅くて有名な再帰メソッドですが、実際にコードを動かしてみると、a,bの数字が大きくなるほど計算が終わるまでに果てしない時間がかかってしまい、使い物になりません。
なぜかというとメソッドを実行する度に基底のF0までさかのぼって、Fnまでの項をすべて再計算しているから。一度求めた項の値を再利用せずに、何度も計算しなおすため非常に非効率です。
もしこれが本当の出題だったら、この回答で点数は取れません。

もっと処理速度を高速化して問題を解くためには、以下のようにメモ化再帰を利用するなどしてメソッドを用意する必要があります。

public int fibonacci(int n)
{
    // あらかじめarray[0]=0、array[1]=1、array[2]=null、… 、array[n] = nullの配列を作っておく
    // n番目の項を計算したら答えを配列に記録するようにし、2回目からは配列の値を直接返す
    if(array[n] != null)
    {
        return array[n];
    }
    
    array[n] = fibonacci(n-1) + fibonacci(n-2);
    return array[n];
}

上記は簡単な例ですが、競技プログラミングではこういった「ロジックを工夫して効率化する力」を求められます。
レベルの高い問題になるほど単純なコード化やバックトラック(総当たり)では点数が取得できなくなるので、アルゴリズムやデータ構造の組み立ての勉強になります。

競技プログラミングは初心者にもやさしい!

競技プログラミングは以下の点で始めるハードルが低いので、おすすめです。

●コンテストはオンライン開催
コンテストは各競技プログラミングのサイトにてオンラインで開催されるため、自宅からでも気軽に参加することができます。

●短時間で行うことができる
コンテストに参加する場合は決められた時間に1~3時間ほどかかりますが、過去の問題を1問ずつ解くこともできます。 その場合は問題のレベルにもよりますが、10~20分といった短時間で解いたり、隙間時間で少しずつ進めることも可能です。 問題の解答を提出すると瞬時にオンラインジャッジが行われ、結果がすぐにわかるのもうれしいところです。

●問題のレベルが幅広い
競技プログラミングで出題される問題レベルは幅広く、ちょっとやそっとじゃ解けない難問もありますが、逆に四則演算さえ分かれば解けるやさしい問題もあります。自分のレベルに合わせて問題を選ぶことができます。

●自分が得意なプログラミング言語で参加可能
問題を解くための言語は特定の言語に決まっておらず、各サイトやコンテストが定めた言語から好きなものを選んで参加できます。 言語の例としてはJava、C++、C#、D、Ruby、Phython、COBOL、などです。 最初から自分が慣れ親しんだ言語で参加できます。

●他の参加者の提出コードを確認できる
コンテスト終了後は他の参加者が提出したコードを見ることができます。 他の人がどのように問題を解いているのか参考になりますし、自分のコードと比較してみると、自分の癖や好みが見えたりして面白いですよ。

いろんな言語で問題を解いてみよう!

個人的におすすめする競技プログラミングの使い方の1つは、新しいプログラミング言語を勉強する1つの方法として同じ問題を複数のプログラミング言語で解いてみることです。

まずはじめは自分が使い慣れた言語で解き、2回目以降は学習中の言語で解きます。
2回目以降はロジックの内容を理解した上での作業になるので、未習熟な言語でも構文に集中して比較的スムーズにコードを書けます。
過去に提出した解答は履歴として保存されるため、異なるプログラミング言語による同じ内容のコード同士を比較することも容易です。履歴にはコード長、実行時間、メモリ使用量などが保存されるため、そういった点でも簡単に言語間の違いを感じることができます。

おすすめ競技プログラミングサイト

最後にいくつかおすすめの競技プログラミングサイトをピックアップしました!

AtCoder
開催コンテスト数が多いです。 AtCoderのいいところはコンテスト終了後にアップされる解説が丁寧で充実していて、解説見るだけでも勉強になります。 問題は日本語で出題されます。

Aizu Online Judge
福島県にある会津大学が提供するサイトです。 中高生、大学生向けに構築されたサイトで、過去のICPC、パソコン甲子園などに出題された問題などを掲載しています。 問題は英語と日本語で出題されます。

PKU Judge Online
北京大学提供の問題サイトです。 英語と中国語での出題がメインなので言葉の壁を乗り越えられる人は挑戦してみてください。

まとめ

参加者同士で点数を競いあいゲーム感覚で取り組める競技プログラミング、ぜひみなさまも挑戦してみてくださいね。


Misoca+弥生 Advent Calendar 2019の14日目は、rina_matsuzakaさんです。
お楽しみに!