LoginSignup
0

More than 1 year has passed since last update.

nullバグよ、さらば!null safety!null安全!

Posted at

null安全.jpg

エスプリフォートでは、業務内でのシステム開発以外でも、日々アンテナを張りながら仲間同士お互いに技術力を磨き合っています。
そして高めた技術力を顧客のために如何に生かしていくのかということを考え、顧客から頼られる存在としてあり続けています。

そんなアンテナにひっかかった一つとしての「nullの安全な取り扱い」を、言語はC#で紹介いたします。

null安全とは?

エンジニアになってから、「自分の書いたコードでNullReferenceExceptionが起きるのを見たことがない」という人はほとんどいないかと思います。
エンジニアとしてnullによって不具合が発生し、苦しめられた記憶がある人も少なくないかと思います。

これらを事前にできるだけ防止するために、null安全というものがあります。

null安全とは、「null 参照例外(NullReferenceException)が発生しうるコードをコンパイル時にエラーにしてくれる」仕組みです。

null安全が産まれた背景

ある変数が意図せずにnullになった時、(nullをチェックしていないコードでは)実行時に例外発生やバグの原因になってしまうことがありました。
この不具合の原因を突き止めることは、時に困難を伴い、頭痛のタネとなります。

であれば、元から
「この変数にはnullは入ってはいけない」という型
を決めておくことができれば良いのでは、という考えから「null安全」という考えが産まれました。

ちなみに、近年はC#以外の新しい言語でも(Kotlin・Swift等)、null安全は採用されてきています。

C#によるnull安全チェック

C# 8.0からnull安全によるチェックができるようになっています。

プロジェクト全体に対するチェック設定

nullの許容/非許容の設定はプロジェクトの設定(プロパティ内の「ビルド」-「全般」内にある「Null許容」のプルダウン)でプロジェクト全体に設定することができます。
プロジェクト_null許容.jpg
※さらに、.NET6(C# 10.0)では、新規プロジェクトを作成した場合、既定でnull非許容型が有効となる設定が追加になります。
新規プロジェクト_null許容.jpg

では実際に値型、参照型それぞれの場合におけるnull安全について確認してみましょう。

値型によるnull安全チェックを検証

値型では、変数宣言時に型名に?を付けると「null許容型(nullを代入できる値型)」として扱われます。

値型
int a;
a = 100; //代入可能
a = null; //コンパイルエラー

int? b;
b = 100; //代入可能
b = null; //エラーにならずnullが代入される

参照型によるnull安全チェックを検証

従来のC#では、参照型は既定値がnullであるため、参照型は「null許容型」となっていました。
C#8.0から「null許容参照型」と「null非許容参照型」が導入されたことで、nullが代入されることを許容しないようにできるようになりました。

「null許容参照型」としてnullを許容したい場合は、値型と同様に変数宣言時に型名に?を付けます。
?をつけなければ、「null非許容参型」として扱われるようになります。

参照型
string company_name;
company_name = "esprit-fort"; //代入可能
company_name = null; //「Null許容」が有効化されたプロジェクトでは警告が出る

string? office_name;
office_name = "esprit-fort"; //代入可能
office_name = null; //警告は出ない

String? i = null;
String? x = i?.ToLower(); //警告は出ない

String? j = null;
String? y = j.ToLower(); //「Null許容」が有効化されたプロジェクトでは警告が出る

部分的にnull安全チェックを有効にする

「null許容参照型」を特定のファイルや行に対して有効にするには、#nullable ディレクティブを使用します。
指定できる設定は、enabledisablerestoreの3つとなります。

設定値 概要
enable このディレクティブ以降、Null許容参照型の機能を有効に変更します。
disable このディレクティブ以降、Null許容参照型の機能を無効に変更します。
restore このディレクティブ以降、Null許容参照型の機能をプロジェクトのデフォルト値に変更します。
部分的に有効にする
class Program
{
    static void Main(string[] args)
    {

// これ以降Null許容参照型 ON
#nullable enable

        string? s = null;
        if (s?.Length == 0)
        {
            Console.WriteLine("hoge");
        }

#nullable restore
    }
}

Null許容参照型には2つのコンテキストが存在し、文法の有効可否と警告の出力有無を分けて指定することが出来ます。

警告の出力有無について有効にして、コンパイラーが分かる範囲で警告を出力したい場合は、#nullable enable warningsを指定します。

警告のみ有効
class Program
{
    static void Main(string[] args)
    {
#nullable enable warnings
        string s = null;
        if (s.Length == 0)  // [CS8602:null 参照の可能性があるものの逆参照です]と警告が出る
        {
            Console.WriteLine("hoge");
        }
#nullable restore
    }
}

Null許容参照型の文法のみを有効にしたい場合は、#nullable enable annotationsを指定します。

警告のみ有効
class Program
{
    static void Main(string[] args)
    {
#nullable enable annotations
        string s = null;
        if (s.Length == 0)  // 警告が出ずに、ここでNullReferenceExceptionが発生
        {
            Console.WriteLine("hoge");
        }
#nullable restore
   }
}

より便利に利用するために

null安全を導入することでnullを許容する変数は、対象の変数のメソッドやプロパティ、フィールドなどを扱う際には、対象の変数がnullではないことを判定した上での実装をしなければならないという制約が課される。
そのため、null判別する処理が必要となり、コードが冗長になります。
その場合、null合体演算子(??)null条件演算子(?.)を利用することで、簡潔に記述することができます。

null合体演算子

【構文】式1 ?? 式2

式1がnullでない場合は式1の値を、式1がnullの場合は式2の値を返却する。

null合体演算子
string? office_name;

office_name = "esprit-fort";
Console.WriteLine(office_name ?? "office_name = nullです");
// office_nameに値が入っているので
// コンソールには esprit-fort が表示される

office_name = null;
Console.WriteLine(office_name ?? "office_name = nullです");
// office_nameに値が入っていないので
// コンソールには office_name=nullです が表示される

null条件演算子

【構文】式?. メンバー

式がnull以外の場合、プロパティやメソッド、フィールドにアクセスし、それ以外の場合はnullを返却する。

null条件演算子
int? n;
n = 100;
Console.WriteLine(n?.ToString());
// nに値が入っているので
// コンソールには 100 が表示される

n = null;
Console.WriteLine(n?.ToString());
// nに値が入っていないので
// コンソールには何も表示されない

また、null合体演算子とnull条件演算子は、組み合わせて使用することも可能です。

【構文】式1?. メンバー ?? 式2

null合体演算子とnull条件演算子
int? n;
n = 100;
Console.WriteLine(n?.ToString()??"n = nullです");
// nに値が入り、n?.ToString()が"100"となるため
// コンソールには 100 が表示される

n = null;
Console.WriteLine(n?.ToString() ?? "n = nullです");
// nに値が入っていないので、n?.ToString()がnullとなるため
// コンソールには n = nullです が表示される

今回はシステムを作るプログラムに欠かせない「null」の安全な取り扱いについて、ご紹介いたしました。

nullは低コストでそこそこ安全に参照を扱えるため重宝されています。
その一方で、コーディングをしている中で意図的にnullを必要とする場面はあまり多くはありません。
また、意図的にnullを使っているのか、考慮や技術力の不足や単純ミスなどによってnullが残っているだけなのかが分からなくて困るといった問題などもシステム開発に携わっていると経験があるかと思います。

これらを減らす一つの手段として、null安全の導入を検討してみてはいかがでしょうか。
ただし、null安全を取り入れることによって、既存ソースコードを壊しかねない問題も発生する可能性や、導入することによって既存ソースコードを修正することによる再テスト等、本来かからないコストがかかってしまう問題もあります。

しかし、null安全を導入することにより、仕様漏れや人的ミスによる不具合を事前に防止することもできるので、これからのシステム寿命やシステムの品質を考慮すると、導入しないという選択肢がいつまでも避けて通れるものではないかもしれませんね。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0