5
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

簡単なツール作成を通して各プログラミング言語を比較しつつ学ぶ

Last updated at Posted at 2019-07-28

お題

アプリ実行中だけ機能する簡易Key/Valueストアを各プログラミング言語で実装してみる。
(最近、サービス毎に異なるプログラミング言語で実装されている現場に行くことが多いので、使ったことないプログラミング言語にも、それなりに慣れておくため)

お題のツールとしては(もっと便利なものは山ほどあるし)正直なところ日常的に役に立つものとはとても言えないのだけど、(すぐに挫折しないように)ミニマムに始める(けど、少しずつ各プログラミング言語の機能を使っていけるし、要件を拡張していきやすい)には、これくらいがいいかなと。

ちなみに、各言語のバージョンは以下の通り。
現場で使うことはまだなさそうだけど、Rustも加えてもいいかも。
PHPやTypeScriptも考えたけど、コンソールアプリ用途で選定されるだろうか?と疑問に思ったので止めた。

  • Ruby ... v2.5
  • Golang ... v1.11
  • Java ... v1.8
  • Scala ... v2.11
  • Kotlin ... v1.3
  • Python ... v3.6

更新履歴

  • 【2019/08/03】コメントにならってRubyプログラムを修正
  • 【2019/08/03】コメントでPythonプログラムの事例をもらったので追加

対象読者

JavaやRubyなど1つくらいプログラミング言語をある程度さわっている人。
その中でも、教科書読んだりチュートリアルやったりだといまいち理解できないから、やっぱり何かしらのアプリを実際に作ってみようという人。

注釈

自分としても使ったことないプログラミング言語で実装したりすることから、まずは相当拙いレベルから”とにかく動くものを作る”ことから始めてみる。
(お題の規模だと1ソースファイルで事足りるので、オブジェクト指向言語でも汎用性を考慮してクラス分割などしない。使えそうだとしても関数型の書き方もしない。)
ので、各プログラミング言語を知っている人からすると「こうすべき!」「これはまずい!」といった言いたいことが山ほどあると思うのだけど、そこはグッと堪えてもらいたく。

試行Index

実装・動作確認端末

# OS - Linux(Ubuntu)

$ cat /etc/os-release 
NAME="Ubuntu"
VERSION="18.04.2 LTS (Bionic Beaver)"
ID_LIKE=debian

実践

要件

アプリを起動すると、起動中だけキーバリュー形式でテキスト情報を保存する機能を持つコンソールアプリ。
以下のように振る舞う。

※「$ 」付きの状態がコンソールでコマンドを叩くことを表す。(無しはアプリ実行中。)
※Unix思想を踏まえ、保存や削除などは正常終了時は何も表示しない。

アプリ起動


$ book

バイナリ化していたら「book」と叩いて起動する想定だけど、スクリプト言語のケースもあるし、実際のアプリ起動方法はまちまち。
例えばRubyなら「$ ruby main.rb」だし、Goなら「$ go run main.go」だし。

アプリ終了


end

$

ヘルプ


help

  ★ここに、コマンドの一覧や使い方を表示する。★

保存


save key01 val01

正常に保存できた時は何も出力しない。(UNIX Philosophy ?)

取得


get key01

val01

削除


remove key01

一覧表示


list

"key","value"
"key01","val01"
"11111","22222"
"キー","バリュー"

■Ruby

開発環境

# 言語バージョン

$ ruby -v
ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-linux-gnu]

# IDE - RubyMine

RubyMine 2019.1.2
Build #RM-191.7141.58, built on May 14, 2019

ソース全量(【2019/08/03】コメントにならって修正)

[main.rb]
def usage()
  puts <<~EOB

    [usage]
    キーバリュー形式で文字列情報を管理するコマンドです。
    以下のサブコマンドが利用可能です。

    list   ... 保存済みの内容を一覧表示します。
    save   ... keyとvalueを渡して保存します。
    get    ... keyを渡してvalueを表示します。
    remove ... keyを渡してvalueを削除します。
    help   ... ヘルプ情報(当内容と同じ)を表示します。

  EOB
end

# -------------------------------------------------------------------
# ここからメイン処理
# -------------------------------------------------------------------

cmd_store = Hash.new

puts "Start!"

loop do
  # 改行コードが含まれるので削る。
  cmd, *args = gets.chomp.split /\s+/

  case cmd

  # アプリ終了判定
  when "end"
    puts "End!"
    exit

  # ヘルプ
  when "help"
    usage
    next

  # 保存
  when "save"
    if args.size == 2
      cmd_store[args[0]] = args[1]
    else
      usage
    end

  # 取得
  when "get"
    if args.size == 1
      puts cmd_store[args[0]]
    else
      usage
    end

  # 削除
  when "remove"
    if args.size == 1
      cmd_store.delete(args[0])
    else
      usage
    end

  # 一覧
  when "list"
    puts '"key","value"'
    cmd_store.each {|k, v| puts %("#{k}","#{v}")}

  end

end

1ファイルだから上に載せたのが全量だけど、一応、ソースは下記に置いている。
https://github.com/sky0621/book_rb/tree/v0.2.0

動作確認

アプリ起動 〜 ヘルプ表示(help) 〜 アプリ終了(end)


$ ruby main.rb 
"Start!"
help

[usage]
キーバリュー形式で文字列情報を管理するコマンドです。
以下のサブコマンドが利用可能です。

list   ... 保存済みの内容を一覧表示します。
save   ... keyとvalueを渡して保存します。
get    ... keyを渡してvalueを表示します。
remove ... keyを渡してvalueを削除します。
help   ... ヘルプ情報(当内容と同じ)を表示します。

end
End!
$ 

アプリ起動 〜 1件追加(save) 〜 追加データ確認(get) 〜 アプリ終了(end)


$ ruby main.rb 
"Start!"
save key01 val01
get key01
val01
end
End!

アプリ起動 〜 1件追加(save) 〜 追加データ削除(remove) 〜 追加データ確認(get) 〜 アプリ終了(end)


$ ruby main.rb 
"Start!"
save key01 val01
remove key01
get key01

end
End!

アプリ起動 〜 3件追加(save) 〜 データ一覧確認(list) 〜 アプリ終了(end)


$ ruby main.rb 
"Start!"
save key01 val01
save 11111 22222
save キー バリュー
list
"key","value"
"key01","val01"
"11111","22222"
"キー","バリュー"
end
End!

■Go

開発環境

# 言語バージョン

$ go version
go version go1.11.4 linux/amd64

# IDE - Goland

GoLand 2019.2
Build #GO-192.5728.103, built on July 23, 2019

ソース全量

[main.go]
package main

import (
	"bufio"
	"log"
	"os"
	"strings"
)

func usage() {
	msg := `

[usage]
キーバリュー形式で文字列情報を管理するコマンドです。
以下のサブコマンドが利用可能です。

list   ... 保存済みの内容を一覧表示します。
save   ... keyとvalueを渡して保存します。
get    ... keyを渡してvalueを表示します。
remove ... keyを渡してvalueを削除します。
help   ... ヘルプ情報(当内容と同じ)を表示します。

`
	println(msg)
}

// -------------------------------------------------------------------
// ここからメイン処理
// -------------------------------------------------------------------

var cmdStore = map[string]string{}

func main() {
	println("Start!")
	for {
		s := bufio.NewScanner(os.Stdin)
		for s.Scan() {

			// アプリ終了判定
			if s.Text() == "end" {
				println("End!")
				os.Exit(-1)
			}

			// ヘルプ
			if s.Text() == "help" {
				usage()
			}

			if s.Text() == "" {
				usage()
				continue
			}

			// 以降は、引数ありコマンドの処理
			cmds := strings.Split(s.Text(), " ")

			// 保存
			if cmds[0] == "save" {
				if len(cmds) != 3 {
					usage()
					continue
				}
				cmdStore[cmds[1]] = cmds[2]
			}

			// 取得
			if cmds[0] == "get" {
				if len(cmds) != 2 {
					usage()
					continue
				}
				println(cmdStore[cmds[1]])
			}

			// 削除
			if cmds[0] == "remove" {
				if len(cmds) != 2 {
					usage()
					continue
				}
				delete(cmdStore, cmds[1])
			}

			// 一覧
			if cmds[0] == "list" {
				println(`"key","value"`)
				for k, v := range cmdStore {
					println("\"" + k + "\",\"" + v + "\"")
				}
			}

		}
		if s.Err() != nil {
			log.Fatal(s.Err())
		}
	}
}

1ファイルだから上に載せたのが全量だけど、一応、ソースは下記に置いている。
https://github.com/sky0621/book_go/tree/v0.1.0

■Java

開発環境

# 言語バージョン

$ java -version
openjdk version "1.8.0_202"
OpenJDK Runtime Environment (build 1.8.0_202-20190206132807.buildslave.jdk8u-src-tar--b08)
OpenJDK GraalVM CE 1.0.0-rc14 (build 25.202-b08-jvmci-0.56, mixed mode)

# IDE - IntelliJ IDEA

IntelliJ IDEA 2019.2 (Ultimate Edition)
Build #IU-192.5728.98, built on July 23, 2019

ソース全量

[Main.java]
import java.util.*;

public class Main {
    private static void usage() {
        String msg = "\n" +
                "[usage]\n" +
                "キーバリュー形式で文字列情報を管理するコマンドです。\n" +
                "以下のサブコマンドが利用可能です。\n" +
                "\n" +
                "list   ... 保存済みの内容を一覧表示します。\n" +
                "save   ... keyとvalueを渡して保存します。\n" +
                "get    ... keyを渡してvalueを表示します。\n" +
                "remove ... keyを渡してvalueを削除します。\n" +
                "help   ... ヘルプ情報(当内容と同じ)を表示します。\n" +
                "\n";
        System.out.println(msg);
    }

    // -------------------------------------------------------------------
    // ここからメイン処理
    // -------------------------------------------------------------------

    private static Map<String, String> cmdStore = new HashMap<String, String>();

    public static void main(String... args) {
        System.out.println("Start!");
        while (true) {
            Scanner s = new Scanner(System.in);
            String cmd = s.nextLine();

            // アプリ終了判定
            if (cmd.equals("end")) {
                System.out.println("End!");
                System.exit(-1);
            }

            // ヘルプ
            if (cmd.equals("help")) {
                usage();
                continue;
            }

            if (cmd.equals("")) {
                usage();
                continue;
            }

            // 以降は、引数ありコマンドの処理
            String[] cmds = cmd.split(" ");

            // 保存
            if (cmds[0].equals("save")) {
                if (cmds.length != 3) {
                    usage();
                    continue;
                }
                cmdStore.put(cmds[1], cmds[2]);
            }

            // 取得
            if (cmds[0].equals("get")) {
                if (cmds.length != 2) {
                    usage();
                    continue;
                }
                System.out.println(cmdStore.get(cmds[1]));
            }

            // 削除
            if (cmds[0].equals("remove")) {
                if (cmds.length != 2) {
                    usage();
                    continue;
                }
                cmdStore.remove(cmds[1]);
            }

            // 一覧
            if (cmds[0].equals("list")) {
                System.out.println("\"key\",\"value\"");
                cmdStore.entrySet().stream().map(e -> "\"" + e.getKey() + "\",\"" + e.getValue() + "\"").forEach(System.out::println);
            }
        }
    }
}

1ファイルだから上に載せたのが全量だけど、一応、ソースは下記に置いている。
https://github.com/sky0621/book_java/tree/v0.1.0

■Scala

開発環境

# 言語バージョン

$ scala -version
Scala code runner version 2.11.12 -- Copyright 2002-2017, LAMP/EPFL

# IDE - IntelliJ IDEA

IntelliJ IDEA 2019.2 (Ultimate Edition)
Build #IU-192.5728.98, built on July 23, 2019

ソース全量

[Main.scala]
object Main extends App {
  def usage(): Unit = println(
    """
      |[usage]
      |キーバリュー形式で文字列情報を管理するコマンドです。
      |以下のサブコマンドが利用可能です。
      |
      |list   ... 保存済みの内容を一覧表示します。
      |save   ... keyとvalueを渡して保存します。
      |get    ... keyを渡してvalueを表示します。
      |remove ... keyを渡してvalueを削除します。
      |help   ... ヘルプ情報(当内容と同じ)を表示します。
      |
      |""".stripMargin)

  // -------------------------------------------------------------------
  // ここからメイン処理
  // -------------------------------------------------------------------

  import scala.collection.mutable

  var cmdStore = mutable.Map.empty[String, String]

  println("Start!")
  while (true) {
    val cmds = io.StdIn.readLine().split(" ")

    // アプリ終了判定
    if (cmds(0) == "end") {
      println("End!")
      sys.exit(-1)
    }

    // ヘルプ
    if (cmds(0) == "help") {
      usage()
    }

    if (cmds(0) == "") {
      usage()
    }

    // 保存
    if (cmds(0) == "save") {
      if (cmds.size != 3) {
        usage()
      } else {
        cmdStore += (cmds(1) -> cmds(2))
      }
    }

    // 取得
    if (cmds(0) == "get") {
      if (cmds.size != 2) {
        usage()
      } else {
        if (cmdStore.contains(cmds(1))) {
          println(cmdStore(cmds(1)))
        }
      }
    }

    // 削除
    if (cmds(0) == "remove") {
      if (cmds.size != 2) {
        usage()
      } else {
        cmdStore -= cmds(1)
      }
    }

    // 一覧
    if (cmds(0) == "list") {
      println("\"key\",\"value\"")
      for ((k, v) <- cmdStore) println("\"%s\",\"%s\"".format(k, v))
    }
  }
}

1ファイルだから上に載せたのが全量だけど、一応、ソースは下記に置いている。
https://github.com/sky0621/book_scala/tree/v0.1.0

■Kotlin

開発環境

# 言語バージョン

v1.3

# IDE - IntelliJ IDEA

IntelliJ IDEA 2019.2 (Ultimate Edition)
Build #IU-192.5728.98, built on July 23, 2019

ソース全量

import java.util.*
import kotlin.system.exitProcess

fun usage() {
    val msg = """

[usage]
キーバリュー形式で文字列情報を管理するコマンドです。
以下のサブコマンドが利用可能です。

list   ... 保存済みの内容を一覧表示します。
save   ... keyとvalueを渡して保存します。
get    ... keyを渡してvalueを表示します。
remove ... keyを渡してvalueを削除します。
help   ... ヘルプ情報(当内容と同じ)を表示します。

        """.trimIndent()
    println(msg)
}

// -------------------------------------------------------------------
// ここからメイン処理
// -------------------------------------------------------------------

var cmdStore = mutableMapOf<String, String>()

fun main(vararg args: String) {
    println("Start!")
    while (true) {
        val s = Scanner(System.`in`)
        val cmd = s.nextLine()

        // アプリ終了判定
        if (cmd == "end") {
            println("End!")
            exitProcess(-1)
        }

        // ヘルプ
        if (cmd == "help") {
            usage()
            continue
        }

        if (cmd == "") {
            usage()
            continue
        }

        // 以降は、引数ありコマンドの処理
        val cmds = cmd.split(" ")

        // 保存
        if (cmds[0] == "save") {
            if (cmds.size != 3) {
                usage()
                continue
            }
            cmdStore[cmds[1]] = cmds[2]
        }

        // 取得
        if (cmds[0] == "get") {
            if (cmds.size != 2) {
                usage()
                continue
            }
            println(cmdStore[cmds[1]])
        }

        // 削除
        if (cmds[0] == "remove") {
            if (cmds.size != 2) {
                usage()
                continue
            }
            cmdStore.remove(cmds[1])
        }

        // 一覧
        if (cmds[0] == "list") {
            println("\"key\",\"value\"")
            cmdStore.forEach { k, v -> println("\"$k\",\"$v\"") }
        }
    }
}

1ファイルだから上に載せたのが全量だけど、一応、ソースは下記に置いている。
https://github.com/sky0621/book_kt/tree/v0.1.0

■Python

開発環境

# 言語バージョン

$ python3 --version
Python 3.6.8

# IDE - PyCharm

PyCharm 2019.2 (Professional Edition)
Build #PY-192.5728.105, built on July 24, 2019

ソース全量

[main.py]
def usage():
    msg = """
[usage]
キーバリュー形式で文字列情報を管理するコマンドです。
以下のサブコマンドが利用可能です。

list   ... 保存済みの内容を一覧表示します。
save   ... keyとvalueを渡して保存します。
get    ... keyを渡してvalueを表示します。
remove ... keyを渡してvalueを削除します。
help   ... ヘルプ情報(当内容と同じ)を表示します。

"""
    print(msg)


# -------------------------------------------------------------------
# ここからメイン処理
# -------------------------------------------------------------------

cmd_store = {}

print("Start!")
while True:
    cmd = input()

    # アプリ終了判定
    if cmd == "end":
        print("End!")
        exit()

    # ヘルプ
    if cmd == "help":
        usage()
        continue

    # 以降は、引数ありコマンドの処理
    cmds = cmd.split()
    if len(cmds) < 1:
        usage()
        continue

    # 保存
    if cmds[0] == "save":
        if len(cmds) != 3:
            usage()
            continue
        cmd_store[cmds[1]] = cmds[2]

    # 取得
    if cmds[0] == "get":
        if len(cmds) != 2:
            usage()
            continue
        print(cmd_store[cmds[1]])

    # 削除
    if cmds[0] == "remove":
        if len(cmds) != 2:
            usage()
            continue
        del cmd_store[cmds[1]]

    # 一覧
    if cmds[0] == "list":
        print('"key","value"')
        for k, v in cmd_store.items():
            print(f'"{k}","{v}"')

1ファイルだから上に載せたのが全量だけど、一応、ソースは下記に置いている。
https://github.com/sky0621/book_py/tree/v0.1.0

解説

この規模なら、何かしらのプログラミング言語でコンソールアプリ作ったことあればやってることは理解できそうな気はする。
なので、部分部分だけピックアップ。

◆ヒアドキュメント

モダンな言語では、書かれたままを文字列として表現できるヒアドキュメントが使える。
残念ながらJavaには無いので、文字列の+結合で似た形に寄せる。

Ruby

def usage()
  puts <<~EOB

    [usage]
    キーバリュー形式で文字列情報を管理するコマンドです。
    〜〜〜
    help   ... ヘルプ情報(当内容と同じ)を表示します。

  EOB

Golang

func usage() {
	msg := `

[usage]
キーバリュー形式で文字列情報を管理するコマンドです。
 〜〜〜
help   ... ヘルプ情報(当内容と同じ)を表示します。

`
	println(msg)
}

Java

    private static void usage() {
        String msg = "\n" +
                "[usage]\n" +
                "キーバリュー形式で文字列情報を管理するコマンドです。\n" +
                  〜〜〜
                "help   ... ヘルプ情報(当内容と同じ)を表示します。\n" +
                "\n";
        System.out.println(msg);
    }

Scala

  def usage(): Unit = println(
    """
      |[usage]
      |キーバリュー形式で文字列情報を管理するコマンドです。
     〜〜〜
      |help   ... ヘルプ情報(当内容と同じ)を表示します。
      |
      |""".stripMargin)

Kotlin

fun usage() {
    val msg = """

[usage]
キーバリュー形式で文字列情報を管理するコマンドです。
 〜〜〜
help   ... ヘルプ情報(当内容と同じ)を表示します。

        """.trimIndent()
    println(msg)
}

Python

    msg = """
[usage]
キーバリュー形式で文字列情報を管理するコマンドです。
 〜〜〜
help   ... ヘルプ情報(当内容と同じ)を表示します。

"""
    print(msg)

◆ハッシュマップ生成

Rubyは全てがオブジェクト。newキーワードを使う。
ただ、Javaでは「new HashMap()」となるが、Rubyでは「Hash.new」となる。
そして型は明示化しなくてよい。(JavaもVer.10で型推論が導入されたけど)
対して、Golangではマップは特殊な構文。

Ruby

cmd_store = Hash.new

Golang

var cmdStore = map[string]string{}

Java

private static Map<String, String> cmdStore = new HashMap<String, String>();

Scala

import scala.collection.mutable
var cmdStore = mutable.Map.empty[String, String]

Kotlin

var cmdStore = mutableMapOf<String, String>()

Python

cmd_store = {}

◆無限ループ

処理待ち受けアプリでおなじみにの無限ループ。
他の言語だと「while true」を使うことが多いけど、Rubyには(「while true」も使えるけど)上記の書き方で実現できる。
Golangはとにかく言語自体をシンプルにしているので「for」で無限ループも賄う。

Ruby

  loop do
  〜〜〜 ここの処理が(明示的に終了しない限り)無限ループ 〜〜〜
  end

Golang

  for {
  〜〜〜 ここの処理が明示的に終了しない限り無限ループ 〜〜〜
  }

Java

  while (true) {
  〜〜〜 ここの処理が明示的に終了しない限り無限ループ 〜〜〜
  }

Scala

  while (true) {
  〜〜〜 ここの処理が明示的に終了しない限り無限ループ 〜〜〜
  }

Kotlin

  while (true) {
  〜〜〜 ここの処理が(明示的に終了しない限り)無限ルプ 〜〜〜
  }

Python

while True:
  〜〜〜 ここの処理が明示的に終了しない限り無限ループ 〜〜〜

◆標準入力

要件として、アプリ起動後は標準入力を待ち受ける状態となる。

Ruby

これを実現するのが「gets」。これ書くだけで、この行でユーザからの入力を待ち受ける状態になる。
ちなみに、「chomp」は標準入力から改行コードを除去するコード。

  cmd, *args = gets.chomp.split /\s+/

Golang

OSからの標準入力をScannerを用いて取得。

    s := bufio.NewScanner(os.Stdin)
    for s.Scan() {
      〜〜〜
      cmds := strings.Split(s.Text(), " ")
      〜〜〜
    }

Java

JavaもScannerを使う。

    Scanner s = new Scanner(System.in);
    String cmd = s.nextLine();
      〜〜〜
    // 以降は、引数ありコマンドの処理
    String[] cmds = cmd.split(" ");

Scala

    val cmds = io.StdIn.readLine().split(" ")

Kotlin

    val s = Scanner(System.`in`)
    val cmd = s.nextLine()
      〜〜〜
    // 以降は、引数ありコマンドの処理
    val cmds = cmd.split(" ")

Python

    cmd = input()

◆アプリ終了

Ruby

      exit

Java

      System.exit(-1);

Scala

      sys.exit(-1)

Kotlin

      exitProcess(-1)

Python

        exit()

◆コレクション走査

言語によって特色の出やすいコレクション走査。
Rubyでは「each」で各々の要素を操作する処理を明示し、「|k, v|」という書き方で1件毎のキーとバリューを参照する。
Golangではコレクションはrangeで走査。
Javaでコレクションといったらstreamが登場。ただ、他の言語より書きっぷりが冗長?

Ruby

    cmd_store.each {|k, v| puts %("#{k}","#{v}")}

Golang

      for k, v := range cmdStore {
        println("\"" + k + "\",\"" + v + "\"")
      }

Java

      cmdStore.entrySet().stream().map(e -> "\"" + e.getKey() + "\",\"" + e.getValue() + "\"").forEach(System.out::println);

Scala

      for ((k, v) <- cmdStore) println("\"%s\",\"%s\"".format(k, v))

Kotlin

      cmdStore.forEach { k, v -> println("\"$k\",\"$v\"") }

Python

        for k, v in cmd_store.items():
            print(f'"{k}","{v}"')

まとめ

簡単なツールレベル、かつ、言語特有の強みが出る書き方でなく、どれも似たような書きっぷりになるよう意識してしまった結果、構文としての多少の差異ぐらいしか見えなくなってしまった・・・。
言語の強みがちゃんと出るように書くようにしないと、お題を満たすものにはならないか・・・。

今回みたいな事例だとわかりづらいけど、例えばDBアクセスを伴う(いわゆる)Webシステムを(シビアなパフォーマンス要件はなく)早く作りたいとなると、Ruby + Ruby on Railsの組み合わせは強いんじゃないかと思う。
(自分は仕事で一からこの組み合わせで作ったことはなく、あくまでチュートリアルレベルをやった上での感想)
Golangは、(現場のエンジニアがGoしか使えないとかなければ)Webシステムを作るのに使おうと思った時の必然性には乏しいと個人的には思う。
逆に、マシンリソースを効率よく使ってパフォーマンスを上げたいといった時や、こじんまりとした(でもWindowsでもMacでもバイナリ1つで動く)開発補助ツールを作りたいといった時に、Golangは適していると思う。
(自分は家でも仕事でも実際に上記のようなケースでGolangを使った上での感想)
Java並びにScalaKotlinといったJVM系言語は、個人で何か小さくアプリ作成を始める場合には他により適した言語がありそうだけど、なんといっても、どんなアプリ、システムを作るにしてもそれなりに使いやすい汎用性の高さと、豊富な既存資産が活かせるといった強力な強みがあると思う。
(まあ、既存資産は捉え方によっては弱みとも言えるけど・・・)

以上、お題とも離れ、まとめにもならないまとめ。(余裕が出たら、次はWebシステム作成を通しての比較記事かなぁ。)

5
11
4

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
5
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?