C#でASP.NETばっか書いてた後にサーバサイドKotlinを使い始めて半年の感想
時系列順に
Rails と Go はあんまり好きじゃなかったので僕の母なる言語は C#にあると言っていい
ということで C#(ASP.NET)から Kotlin(SpringBoot)に移った感想を数年後にしみじみと楽しむために書いておく
IDE は VisualStudio から IntelliJ
リプレイスしたわけじゃなくて、転職しただけなのでそういう話は特にないし、
サーバサイドKotlinの話といいつつデプロイとかその辺作り込んでないからJVM言語だぜ!って話もない
因みに C#で使っていたバージョンは主に C# 7.3 で基本的に.NET Framework を使っていた...
.NET Core と C# 8.0 以降はほぼ趣味でしか触っていない
https://docs.microsoft.com/ja-jp/dotnet/csharp/whats-new/csharp-version-history
よいこと
data class が便利
data class で immutable に作れるので健全
便利な copy だの equal もついてくる
Kotlin
data class User(val id: Int, val name: String, private val hoge: String) { fun show() = println("$id $name $hoge") } fun main(args: Array<String>) { val user = User(1, "John", "xxx") val user2 = user.copy(name = "Bob") user.show() // 1 John xxx user2.show() // 1 Bob xxx }
C#
なげ〜〜!
public class User { public int Id { get; private set; } public string Name { get; private set; } private string Hoge { get; set; } public User(int id, string name, string hoge) { Id = id; Name = name; Hoge = hoge; } public void Show() => System.Diagnostics.Debug.WriteLine($"{Id} {Name} {Hoge}"); public User Copy(int? id = null, string name = null, string hoge = null) { var tmp = (User)MemberwiseClone(); tmp.Id = id ?? Id; tmp.Name = (name is null) ? Name : name; tmp.Hoge = (hoge is null) ? Hoge : hoge; return tmp; } } class Program { static void Main(string[] args) { var user = new User(1, "John", "xxx"); var user2 = user.Copy(name: "Bob"); user.Show(); user2.Show(); } }
プライマリコンストラクタ
最初戸惑った(後述)けど、慣れると楽
Kotlin
class User constructor(_id: Int, _name: String) { val id: Int val name: String init { id = _id name = _name } } // ↓省略したやつ class User(val id: Int, val name: String)
C#
安心する長さ
public class User { public int Id { get; private set; } public string Name { get; private set; } public User(int id, string name) { Id = id; Name = name; }
NULL 安全
すごく健全に書ける
大変好ましい
Kotlin
val user: User? = null // val user2: User = null // error! // userがnullじゃない場合だけprint user?.let { println(it.name) } // userがnullならエラー println(user?.name ?: throw Exception())
User user = null; // userがnullじゃない場合だけprint if (user is not null) System.Diagnostics.Debug.WriteLine(user.Name); // userがnullならエラー(ついでにnameも見る) if (user is not null && user.Name is not null) { System.Diagnostics.Debug.WriteLine(user.Name); } else { throw new Exception(); }
スコープ関数が便利
https://kotlinlang.org/docs/scope-functions.html
便利だしかっこいいと思うんだけど、会社では使わなくていいとして let 以外使わせて貰えない
(下手に使うとややこしくなるものは確かに多いけど...)
なんでもかんでも式
便利
Kotlin
// 三項演算子はないのでそこだけちょっと寂しく感じる val x = if (true) { "ttt "} else { "fff" } val user: User? = null val y = user?.let { it.name + "さん" }
C#も 8.0 からは switch でそういうことできるようになってた
https://docs.microsoft.com/ja-jp/dotnet/csharp/whats-new/csharp-8#switch-expressions
when がなんだか便利
C#の switch よりなんか色々できる
https://kotlinlang.org/docs/control-flow.html#when-expression
Kotlin
val value = 10 val z = when { value !is Int -> throw Exception() value > 10 -> "10以上" value == 10 -> "ぴったり10" else -> "10より小さい" }
LINQ っぽいのもちゃんとある
ここが大変わかりやすい
LINQ to Objects と Java8-Stream API と Kotlin の対応表 - Qiita
記載されている通り、素の状態だと kotlin.collections は LINQ っていうか Enumerable みたいに遅延でアレしてくれない
.asSequence()
する必要がある
KotlinのListとSequenceって何が違うの? - Qiita
interface に既定の実装書ける
C#でも 8.0 から書けるのであんまり違いはない
https://docs.microsoft.com/ja-jp/dotnet/csharp/whats-new/csharp-8#default-interface-methods
拡張メソッドもある
https://kotlinlang.org/docs/extensions.html
Kotlin
data class User(val id: Int, val name: String) class Util { companion object { fun User.callName() = println(name + "さん!!") } } fun main(args: Array<String>) { val user = User(1, "Taro") user.callName() }
public class User { public int Id { get; private set; } public string Name { get; private set; } public User(int id, string name) { Id = id; Name = name; } } public static class Util { public static void CallName(this User user) => System.Diagnostics.Debug.WriteLine(user.Name + "さん!!"); } class Program { static void Main(string[] args) { User user = new User(1, "xxx"); user.CallName(); } }
Enum がなんだかすごく便利
すごく便利!!
https://kotlinlang.org/docs/enum-classes.html
enum class Dog(val id: Int, val japaneseName: String, val origin: String) { SHIBA(1, "柴犬", "日本"), GOLDEN_RETRIEVER(2, "ゴールデンレトリバー", "?" ), SAMOYED(3, "サモエド", "ロシア"); fun show() = println("$id $japaneseName $origin") } fun main(args: Array<String>) { val dog = Dog.values().first { it.japaneseName == "柴犬" } dog.show() // 1 柴犬 日本 val dog2 = Dog.valueOf("GOLDEN_RETRIEVER") dog2.show() // 2 ゴールデンレトリバー ? }
C#
めんどくさいので端折った
public enum Dog: int { SHIBA = 1, GOLDEN_RETRIEVER = 2, SAMOYED = 3, } public static class DogUtil { public static void Show(this Dog dog) { // 本当はDisplay attributeとかでいい感じに... object result = dog switch { Dog.SHIBA => "1 柴犬 日本", Dog.GOLDEN_RETRIEVER => "2 ゴールデンレトリバー ?", Dog.SAMOYED => "3 サモエド ロシア", _ => throw new NotImplementedException(), }; System.Diagnostics.Debug.WriteLine(result); } class Program { static void Main(string[] args) { // kotlinのdog相当はDisplay attributeとかでいい感じに... var dog2 = Enum.GetValues(typeof(Dog)).Cast<Dog>().First(x => x.ToString() == "GOLDEN_RETRIEVER"); dog2.Show(); } }
SpringBoot 自体ではそんなに困らない
そんなに使いにくいとかは感じない
コアな実装をそんなしていないから気づかないだけかも...
開発者を集めやすいっぽい
Java みたいなもんだしね
混乱したこと
コンストラクタと init
最初はすごく混乱する
return 書かなくてもいい
inline function の reified
いまだにこれが必要な仕組みがよく分かっていない
型引数をあれこれして難しいことをしようとするときに登場する
https://kotlinlang.org/docs/inline-functions.html#reified-type-parameters
好きじゃないこと
gradle がマジで意味分からん
Kotlin の言語仕様イイヨネとかいう以上に gradle が嫌いで僕はまだ C#の方が好き
よくわからんエラー吐くし... 依存性の解決面倒くさすぎるし... IntelliJ との噛み合わせも完璧というわけではないし...
その点 C#では全部 MS が作ってたからその辺の気持ち悪さは一切なかった
Nuget 返して
でも gradle はビルドということでなんでもかんでも出来るのは便利!
とはいえ個人的にはライブラリ周りとビルド周りは別のモジュールとして管理された方が親切じゃない?!と思っている...
public がデフォルト
やだ~
総評
Kotlin 自体は C#よりも書いていて楽しい
でも gradle が苦行で辛い
僕は Rails を恐怖・忌避しているけど、gradle への気持ちの大きさはそれに比肩する
今後の課題
- gradle を渋々勉強する
- 難しいコアな実装をする
- 認証とかエラーハンドリングとか CSRF とかそういうの