2022年3月23日水曜日

MacBook Pro M1 Pro (2021年モデル) に買い換えた話 (新旧比較表付き)

と言うわけでついにMacBook Proを2009年モデルから2021年モデルに買い換えた!

「どう言うわけか?」と言う方はこちらをどうぞ。

せっかくなので比較しておこう。


BeforeAfter
ModelMacBook Pro
13-inch, Mid 2009
MacBook Pro
14-inch, 2021
Display13.3インチ
クリアワイドスクリーンディスプレイ
14.2インチ
Liquid Retina XDRディスプレイ
CPUIntel Core 2 Duo
2.26GHz
Apple M1 Pro
10 Cores
GPUNVIDIA GeForce 9400M 256MBApple M1 Pro
16 Cores
Memory2GB DDR3
→ 8GBに交換
 2016年 ¥9,300
16GB
LPDDR5
Storage160GB HDD
→240GB SSDに交換
 2016年 ¥10,260
1TB SSD
Battery60Whリチウムポリマーバッテリー70Whリチウムポリマーバッテリー
Height2.41 cm1.55 cm
Width32.5 cm31.26 cm
Depth22.7 cm22.12 cm
Weight2.04 kg1.6 kg
Keyboard"Scissor" Keyboard(※1)バックライトMagic Keyboard, Touch ID
CameraiSightカメラ1080p FaceTime HDカメラ
Wi-Fi802.11n/a/b/g802.11ax Wi-Fi 6
BluetoothBluetooth 2.1 + EDRBluetooth 5.0
Power Adapter60W MagSafe電源アダプタ96W USB-C MagSafe 3電源アダプタ
USBUSB 2.0ポート(最大480 Mbps)×2Thunderbolt 4 (USB-C)ポート x 3
Display OutputMini DisplayPortHDMIポート
Audio I/O3.5mmヘッドフォンジャック3.5mmヘッドフォンジャック
SD Card SlotSD Card SlotSDXC Card Slot
CD Drive8倍速SuperDrive
(DVD±R DL/DVD±RW/CD-RW)
-
FireWireFireWire 800-
※1 … キーボードも Scissor Keyboard (シザーキーボード) から Butterfly Keyboard (バタフライキーボード) の一悶着があって、Scissor Keyboardが復活&改良されてMagic Keyboardとなったらしい。もちろん私はButterfly Keyboardは知らない…
バタフライキーボード: 2015年〜2019年

2009年モデルのスペック及びマニュアル
2021年モデルのスペック及びマニュアル
※マニュアルがかなり簡略化された^^

この後に写真比較も載せるが、昔の13インチモデルが今の14インチモデルと同じ大きさなのも面白い。

2016年にメモリ増強(¥9,300)とSSD化(¥10,260)合わせて¥19,560で6年も延命できたのだから安上がりであった。(というか使おうと思えばまだまだ使える!)
メモリは最初の2GBのものを外して、4GBを2枚挿しにした。
当時の記憶では検索をミスったのかメモリは最大で4GBまでと勘違いしていた。(MacBook 13-inch late 2009モデルと勘違いしてた?)
勘違いしていたため8GBまで乗せれるのか色々検索し、どなたかが8GBでもちゃんと認識されたとか見つけて、大丈夫ということを確信した上で8GBにした様な気がする。証拠を載せておく。(前記事の通り、2009年モデルは OS X El Capitan (10.11)。About This Mac 画面は当時からあまり変化していないことが分かる。)

P.S.2022.10.9
私が「About This Macが少なくとも2009年から10年以上も変化していない」と言ったことから、ついにmacOS Ventura 13.0 にて変更されました!私の指摘がよほど気になったようですね^^またはMacBook Pro 2009年モデルを10年以上も大切に愛用した私の意見を尊重しれくれたんですね。(そんな訳はない。。VenturaにてSystem Setting(システム設定)が ios 風に統合されたので、その一環でAbout This Macも見直されたのでしょう。)



それでは比較写真に移ろう。
トップ 左:2009年モデル 右:2021年モデル※プラスチックカバーを付けてます。


フロント 下:2009年モデル 上:2021年モデル
2009年モデルについては、右から赤外線レシーバ、スリープインジケータ
2021年モデルがコンパクトになった。2021年モデルの方が軽いのだが、手で持って比較すると2021年の方が「詰まってる感」がする。
本記事を書いててわかったが、2009年モデルには赤外線レシーバがあったんだ。。この機能だけは使わずじまいだった。。

右側面 下:2009年モデル 上:2021年モデル
ポートは前方から
2009年モデル:盗難防止用ロックスロット※USB-Cではありません!, CDドライブ
2021年モデル:SDカードスロット, USB-C, HDMI
そう言えば2021年モデルには(というかいつからか)、盗難防止用ロックスロットが無くなった気がする。店舗展示とか会社の事務所に置きっぱなしにする場合にワイヤーチェーンで机とかに結び付けて盗難されない様にするものだが、この辺にも世界標準的に考え方の変化があったのだろうか?ISOとかでリスクアセスメントを理詰めでやると大抵はワイヤーチェーンなどに行き着くのだが、「現実問題との乖離」問題もあるわけで、世界標準もこの辺の乖離に一歩踏み込んで次のレベルの現実解まで考察し始めてくれたのであれば幸いである。
(現実問題との乖離として、何よりもズレているのは、大盗賊であれば当然ワイヤーカッターを持って盗難に来る、ということである^^大盗賊と言わずとも、よほど頭が弱くて世情を知らない盗賊でもなければ、「ワイヤーチェーンで結ばれてるかも知れないからワイヤーカッターも準備しておこう」となると思う。そうするとワイヤーチェーンの主たる目的はむしろ「抑止力」なのかも知れない。)


そして何よりもCDドライブ!当時インターネットは普及していたとはいえソフト購入時の主なメディアだったり、マシン故障時のリカバリメディアだったりした訳で「必要」だったのである。
ソフト購入について、多分どこかでちゃんと考察されているのだろうが、また私を含め一般コンシューマーの多くも心に思ったことだと思うが、スマホが世に出てAppleのApp StoreやGoogleの Play Store が出来てお客さんはバンバンアプリを購入する様になって、AppleとかMicrosoftは「あぁ何でもっと前からPC版Storeを真剣に作らなかったのだろう」と思っていることだろう。順当に考えるだけならスマホ効果でPC側に逆輸入しただけとも捉えられる。まさに黎明期である。

マシン的な話に戻すとこの物理的稼働装置を無くせたことは大きい。後で書こうと思ったがここで書いてしまうが、また前記事でもちょっとだけ触れたが、ポートを最小限まで切り詰めたり、バタフライキーボードにしたり、ファンクションキーをTouch Barにしたりという一連の流れは「現在の技術で最小化、コンパクト化、物理稼働装置の撤去・デジタル化・仮想化をできるところまでしてみよう」的ないわば一大ムーブメントがあった気がする。「今」というのが難しく、本当の現在の最新技術であれば基盤からポートから最新のものを使えば良いのだが、世に出ているものと次世代規格が同時進行で時間は流れている。製品化するには「今」という断面を切らなければならないということ。
当時Appleが目指した「シンプル化・最小コンパクト化」も「現在」技術であれば簡単に到達できるのだろうが、目指していたことはそういうことでは無論あるまい。むしろ一回リスクを負って、シンプル化・最小コンパクト化を目指して、それを実際にやってみないとそこからの景色は見えない、という事であろう。
(世の単純比較からは「Appleは後悔して前の時代に戻った」とか散々言われているが、ここで得たAppleの知見は計り知れない。また、最大の「外せない」物理装置としてキーボードは常に念頭に入れているはずである。誰しもが憶測しただろうが、おそらくキーボードを薄くしていって、最後はスマホのように仮想キーボードでいいんじゃない?に持っていきたかったのだろう。でもキーボードは「反応(レスポンス)」「押した感覚」を人は求めているということ。かといってトラックパッドのクリックは物理的押し込みはしなくてもいいんじゃないかと思う。塩梅が難しい。まさにこういったことの積み重ねなのだろう。)

右側面 下:2009年モデル 上:2021年モデル
ポートは前方から
2009年モデル:ヘッドフォンジャック, SD Card Slot, USB 2.0 × 2, Mini Display Port, FireWire 800, Ethernet, MagSafe電源ポート
2021年モデル:ヘッドフォンジャック, USB-C × 2, MagSafe電源ポート
左右のポートを比較してみると、さすが私が買い替えるために開発された(前記事参照)だけあって、ポート構成が似ている。
・ヘッドフォンジャック復活。(あれ? MacBookだとヘッドフォンジャックは無くなったことはない?iPhoneの話か。)
・四角いUSBから、丸いUSB-Cへ。
・Mini Display PortからHDMIへ。
MagSafe電源ポートは平べったくなった。
・EthernetとFireWireが無くなった。FireWireが懐かしい。

おまけ:2009年モデルのバッテリーインジケータ
1個前の比較画像だと分かりづらいが、2009年モデル前方にはバッテリーインジケータがあった。ボタンを押すとバッテリー残量がわかる。

続いて開いた状態。

左:2009年モデル 右:2021年モデル
比較して気づいたが、トラックパッドが若干大きくなってる。

2009年モデル キーボードとトラックパッド
2009年モデルは訳あって英語配列キーボードにした。

2021年モデル キーボードとトラックパッド ※キーボードカバーを付けている。
右上の2009年モデルではCDイジェクトボタンだったところが、2021年モデルではTouch IDになった。
またキーボードの話になるが、2009年モデルの方がストロークが深いため押しごたえがある。特に矢印キーについては2009年モデルの方が使いやすい。人差し指、中指、薬指を置いた時に手の感覚でどのキーを押しているのかが分かって安心するからだろう。
2021年モデルくらい薄くなると、ちゃんと指が正しい矢印キーを押せているのか何となく不安になる。(慣れの問題かもしれないが。)
この辺の
・英数字キーは「F」と「J」に人差し指を置いたホームポジションからの相対的な感覚
というのと、
・矢印キーはゲームパッドなどと同様に指先の感覚
という点で性質が異なるということである。(人間工学メモ)

背面 左:2009年モデル 右:2021年モデル
2021年モデルにはプラスチックカバーを付けている。

せっかくなので電源アダプターも載せておく。

上:2009年モデル 下:2021年モデル
デザインやサイズは前から変わっていない。2009年モデルではコード一体型だったため巻き取り用ウィング(正式名称不明)があって便利だった。ただし写真のように巻き取った後の格好については、Appleデザイン的に合致しているかどうかは不明である。(巻き方が汚いだけ?)

プラグ部分を取り外した状態
左:2009年モデル 右:2021年モデル
プラグ部分は全く一緒のように見えた。多分2019年モデルのものは2021年モデルにもぴったりとくっ付くと思うが、壊れたら嫌なので試してない。
※写真ではプラグ部分の大きさが違うように見えるかも知れないが、被写体の置き方や角度が違うだけで、つまり私の写真の撮り方が下手なだけである。

以上が比較結果である。こんなに世代がかけ離れたマシンの比較結果内容で買い替えを検討する方はいないと思うが、読み物として読んでいただければ幸いである。

なおFlutter開発について本MacBook Proを以って、ようやく念願のiOS版もリリースすることができた!
ネットを調べるとApple Developer Program購入やアプリの審査など時間がかかると書いてあったが、私の場合は3/15 にApple Developer Programを購入実施、完了して、3/16にはApp審査も通ってリリース(App Store公開)までできてしまった!(結構最小日数記録だと思う。)
これも2009年モデルMacBook ProというApple製品を大切に使ってきたおかげであろう。

2022年3月13日日曜日

Flutter: Androidの場合、showDialog(AlertDialog)内でWebViewを使用するとボタンが見えなくなる現象の対処法

Androidの場合、showDialog(AlertDialog)内でWebViewを使用するとボタンが見えなくなり困っていたが対応できた。

Flutterバージョン
> flutter --version
Flutter 2.10.3 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 7e9793dee1 (10 days ago) • 2022-03-02 11:23:12 -0600
Engine • revision bd539267b4
Tools • Dart 2.16.1 • DevTools 2.9.2

対応前            →  対応後
 


使用しているWebView
webview_flutter: ^3.0.0
※執筆時最新バージョンは 3.0.1 

対処法
initState() に WebView.platform = AndroidWebView() を追加。
@override
void initState() {
  super.initState();

  // Enable virtual display.
  if (Platform.isAndroid) WebView.platform = AndroidWebView();
}

詳細
対応前は、ボタンは見えてないだけで、ボタンがあるあたりをタップするとちゃんとタップイベントが動作していたため、「見えてない」だけのようである。

また不思議なことに、次のページに遷移してそちらでもshowDialog()でダイアログ内にWebViewを使用しているのだが、こちらはちゃんとボタンが表示されていた。

なお、アプリ作成時(2019年3月ごろ)は、全画面ともダイアログのボタンは表示されていたため、webview_flutterをバージョン3.0.0に上げてからの症状だと思われる。

ネット上でもWebView関連の症状は結構あるようなので、その辺の問題であって、Flutterライブラリの更新はまだまだ目まぐるしい。
Flutterはその様に目まぐるしい状況なので、調査される方はあまり古い情報にハマらない様ご注意ください。(正攻法は「最新ライブラリのリファレンスを読む」ことなのだが、どうしても急いで対処療法を探すとネットに頼ってしまうこともあるかと思うため、念の為。)
(加えて言うならば、例えば.NET やJava だともう大きく変化しない箇所があって、「こういう時は、こう対応」みたいなものができていて、検索エンジンもそういったものを上位に持ってきてくれるので、古い情報でも気にせずに検索上位から当たっていく、みたいなやり方もアリといえばアリなのだろうが、マルチプラットフォーム開発の期待の星・または大本命であるFlutterはまだまだガンガン発展・進化を遂げていくであろうから、前者のやり方に慣れてしまった方は注意である。例えば.NETやJavaから、いきなりFlutter担当になった方など。「基本は本家リファレンスをあたる」という基本習慣が身について良いかも知れない。)

ちなみに、私のアプリもwebview_flutterをバージョン3.0.0にした時に、描画されるHTMLが全体的に小さくなったため、HTML側で以下を追加して対応してた。
<meta name="viewport" content="width=device-width, initial-scale=1.0">

webview_flutterバージョン3.0.0の変更履歴を見ると、BREAKING CHANGE(破壊的変更)とあって、ちょうどこの箇所が関係していたようだ。
BREAKING CHANGE: On Android, hybrid composition (SurfaceAndroidWebView) is 
now the default. The previous default, virtual display, can be specified with 
WebView.platform = AndroidWebView()
(出典: pub.dev webview_flutter)
それにしてもSurfaceAndroidWebViewがデフォルトになることで、なぜWebViewがダイアログのTextButtonに影響しているのかは不思議である。(私はこの症状にはなったことはないが、ネット上に「showDialogでWebViewを使うとWebViewがダイアログをはみ出す」とかもあったので、やはりWeb画面は色々できるので強力であり特殊なのだろう。)

※そもそもアプリの更新を半年くらいしてなかったら、DartがNull Safety正式版になってたりとか、ライブラリもWebView含め、Firebaseとか結構な変更があったので諸々対応して大変だった^^
 みなさんも放置しすぎない様に注意しましょう!(バージョンアップ対応に追われて、なかなかやりたかった更新作業に辿り着けない。。。)


(おまけ) コード抜粋(buildの骨組みだけ)
※Null Safetyは突貫で直しているので、_doShowDialog の String? contentStr は、String contentStrでいいんじゃないか、とかはご愛嬌^^
①問題が発生した画面
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text(_appTitle),
          actions: <Widget>[
            PopupMenuButton(
              itemBuilder: (BuildContext context) {
                return [
                  PopupMenuItem(child: Text(AppLocalizations.of(context).aboutXxxTitle), value: 1),
                  PopupMenuItem(child: Text(AppLocalizations.of(context).aboutYyyTitle), value: 2),
                ];
              },
              onSelected: (menuId) {
                // ダイアログ表示
                if (menuId == 1) {
                  _doShowDialog(AppLocalizations.of(context).aboutXxxTitle, AppLocalizations.of(context).aboutXxx);
                } else if (menuId == 2) {
                  _doShowDialog(AppLocalizations.of(context).aboutYyyTitle, AppLocalizations.of(context).aboutYyy);
                }
              },
            ),
          ],
        ), // AppBar
        body: SafeArea(
          ...(省略)

  // ダイアログ表示
  _doShowDialog(String title, String? contentStr) {
    showDialog(
      context: context,
      builder: (_) {
        return AlertDialog(
          title: Text(title),
          content: Container(
            width: MediaQuery.of(context).size.width - 10,
            height: MediaQuery.of(context).size.height - 350,
            // 以下のWebViewは実際は別クラスにしてます。リソース(assets)テキストを表示するだけのWebView。
            child: WebView(
              onWebViewCreated: (WebViewController webViewController) async {
                // return the uri data from raw html string.
                String htmlString = '<!DOCTYPE html><body>' + contentStr! + '</body></html>';
                await webViewController.loadUrl(Uri.dataFromString(htmlString, encoding: Encoding.getByName('utf-8')).toString());
              },
            ),
          ),
          actions: <Widget>[
            // ↓これが見えなくなったボタン
            TextButton(
              child: Text("OK"),
              onPressed: () => Navigator.of(context, rootNavigator: true).pop(context), // これでダイアログが閉じられる
            ),
          ],
        );
      }
    );
  }

②問題が発生しなかった画面
  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Expanded(
            child: StreamBuilder<QuerySnapshot<Map<String, dynamic>>>(
              // ページデータを読み込む
              stream: _pageData,
              builder: (BuildContext contextPageData,
                AsyncSnapshot<QuerySnapshot<Map<String, dynamic>>> snapshot) {
                  if (snapshot.hasError) {
                    return Center(child: Text('Error: ${snapshot.error}'));
                  } else if (snapshot.connectionState == ConnectionState.none ||
                    snapshot.connectionState == ConnectionState.waiting) {
                    // ロード中
                    return _getLoadingText();
                  } else if (!snapshot.hasData) {
                    return _getLoadingText();
                  }

                  switch (snapshot.connectionState) {
                  case ConnectionState.waiting:
                    return _getLoadingText();
                  default:
                    // ページ情報
                    List<DocumentSnapshot<Map<String, dynamic>>> pages = snapshot.data!.docs;
                    return DefaultTabController(
                      length: pages.length,
                      initialIndex: _currPage!,
                      child: StatefulBuilder(
                        builder: (BuildContext contextTab, setState) =>
                          WillPopScope(
                              onWillPop: () {
                                // 前画面へ戻る
                                Navigator.of(context).pop();
                                return Future.value(false);
                              },
                              child: Scaffold(
                                appBar: AppBar(
                                  title: Text(widget.textDoc.data()!["title"]),
                                  actions: <Widget>[
                                    IconButton(
                                      icon: const Icon(Icons.info, color: Colors.white, size: 30,),
                                      onPressed: () {
                                        // 説明をダイアログ表示
                                        _doShowDialog(widget.textDoc.data()!["title"], widget.textDoc.data()!["text_info"]);
                                      },
                                    ),
                                  ],
                                ),

おまけなので、アプリの詳細は割愛するが、
①問題が発生した画面 では、appBarまたは、bodyの情報ボタンからダイアログを表示している。
②問題が発生しなかった画面 では、appBarからダイアログを表示している。
やっていることは、_pageDataとして外部からページデータを取得して、StreamBuilderでタブに表示している。そこそこありがちな構成だと思う。

構造としては、①だと Scaffoldだけだが、
②だと SafeArea → Expanded  DefaultTabController  StatefulBuilder  WillPopScope  Scaffold
となっており、この辺の構造も関係しているのだろうか?
ネット上にもScaffoldがどうのこうとと書いてあった様な。。。

2022年2月2日水曜日

ジョークコマンド・ミームコマンド : please

ものすごいジョークコマンド(ミームコマンド)を思いついてしまった。

それは、SQLクエリに「please」を使えるようにする、というものである。

(例)
please select * from table1;
please insert into table1 values (...);
please update table1 set ...;
please delete from table1 where ...;
と思ったら、
2021年4月に日本MySQLユーザ会の副代表である坂井 恵さん
に先を越されていた!残念!

1年遅かったか!そしてまさか実装までされているとは!

せっかくなので、転んでもただでは起きずに
・シェルコマンド  →P.S. 2023.1.2 作りました!
・DOSコマンド
・PowerShellコマンド
・…
などのCUIコマンド辺りにも広めるアイデアを書いておこう。。
※GUI版もあり得るといえばあり得るので、ついでに書いておく。

シェルはディストリビューションがたくさんあるので既にあるかどうか分からないが、少なくとも macOS Monterey にはないことを確認。
GNU bash, version 3.2.57(1)-release (arm64-apple-darwin21)
 
DOS/PowerShellはWindows 10にはないことを確認。


なお、SQLのplease句は文頭でも文末でも使えるようだ。これはデータベースエンジンという閉じた世界で文法解釈するためよいが、シェルとなると関連するところが一気に広がるため大変そうである。
(例)
please ls
ls |please
echo `please date`
please please please please please ...
コマンドの機能としては、何もしない、またはコマンド全体には何の作用もしないが、何かしら気の利いたことをしてくれる機能😅

例えば、後続コマンドが「--please」オプションを実装している場合は、「--please」オプションをつけて実行してくれる、など。
「--please」オプションの実装は各コマンド次第ということ。
既に「--please」オプションが別の意味・機能で実装済みの場合は注意が必要。

ヌルコマンド「:」に似ているが、ヌルコマンドはそれ自体が終了コード0を返す1つのコマンドである。つまり
: ls
を実行すると、lsの結果は出力されず、コメントのように使用されるが、pleaseコマンドの場合は、lsの結果の出力及びlsの終了コードを返す。
つまり結果には介在しない。うーん、いかにも危険なコマンドである。

※「気の利いたこと」として、例えば出力に「You are welcome.」を返したり、「fortune」コマンドのようにランダムな気の利いた言葉を返す機能を想定するならば、「結果出力には介在するが、終了コードのは介在しない」機能となる。
※なお、「fortune」コマンドについては、昔、どこの何の環境では覚えていないが、シェル起動時に「その日の言葉」を出してくれていたので覚えているだけで、それが果たして「fortune」コマンドだったのか、それとも別のコマンドだったのかは今では分からない。
その環境のデフォルトrcスクリプトであったのだろう。
please fortune

※よって、pleaseのオプションとして「気の利いたこと」をしてくれる連携コマンドを指定できそう。
please -p fortune
please --partner fortune

ただし、「please」コマンドがSQLのように「自然言語」に寄り添う思想の言語には親和性があるのに対し、シェルコマンドだと自然言語側に寄せれない問題が発生し、人々はそれに直面し困惑することになる。
please -p fortune ls
please --partner fortune ls
ls |please -p fortune
ls |please --partner fortune
echo `please -p fortune date`
echo `please --partner fortune date`
please please -p fortune please please please ...
please please --partner fortune please please please ...
「please」効果が大激減である。そればかりか反対の感情にさえなりかねない状況に陥る。

よってpleaseコマンドにはオプションを持たせないのがとりあえずの対処。
(fortuneと連携したい場合は設定ファイルか何かで連携するしかない。)
please [command ... ]
command : コマンド全般。ただし自然言語系コマンド※だとより親和性がある。
※自然言語系コマンド:please を含め、シェルで自然言語に近いコマンドを構成する/できる言語体系。文脈を構成する場合は、コマンドの前後など文脈としての整合性も求められる。
という、シェルコマンドに自然言語を乗せるという壮大な話になってくるため、話はここら辺までにしておこう。

(please の他にも do/force で、後続コマンドに --force を付与や、can/could you ... などなど。住む世界が違いすぎる。しかしこれまで培われてきたように、異なる世界でもお互いに解釈できる言語技術やプロット技術、変換技術によって繋ぎ合わせることができるのである。変な扉を開かなければ良いが。。。)


。。。
いずれにせよ、混乱とバグとセキュリティ上の脆弱性をもたらしそうである。。

ーーーーー
P.S. 2023.1.2 
というわけで、せっかくなのでshell版(UNIX版)のpleaseコマンドを作成しました^^✌️
shell版と言ってもシェルスクリプトではなくてC言語で作ったバイナリです。(多分シェルスクリプトでもできるかもしれないが。。)


Android Studio Bumblebee にアップデートしたら、Flutterでios版がWarning: CocoaPods not installed.になった場合の対応方法



Android Studio Bumblebee にアップデートしたところ、Flutterでios版を動かそうとするとバージョンアップ前は普通に動いていたのに、以下のエラーが発生した。
Warning: CocoaPods not installed. 
Skipping pod install. CocoaPods is used to retrieve the iOS and macOS platform 
side's plugin code that responds to your plugin usage on the Dart side. 
Without CocoaPods, plugins will not work on iOS or macOS. For more info, 
see https://flutter.dev/platform-plugins 
To install see https://guides.cocoapods.org/using/getting-started.html#installation for instructions.

以下リンクに「ちらっ」と書いてあるのが、根本的な解決法だと思う。


つまり、
> chmod +x /Applications/Android\ Studio.app/Contents/bin/printenv
上記実行した上でAndroid Studioを再起動。再度
> flutter doctor
を実行して、以下のようにCocoaPodsでエラー解消されてばOK
[✓] Xcode - develop for iOS and macOS (Xcode 13.2.1)
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • CocoaPods version 1.11.2

/Applications/Android\ Studio.app/Contents/bin を見てみるとprintenvだけownerがadminになっていた。(Bumblebeeより前はどうなっていたのだろう?)(以下は chmod +x 後の状態)
-rwxr-xr-x   1 user_x  admin  152080 Jan 28 07:23 printenv
(2022.2.6 追記)
Bumblebee にPatch 1 が出ていた。
Android Studio Bumblebee | 2021.1.1 Patch 1
そして、Patch 1 更新後に再度/Applications/Android\ Studio.app/Contents/binを見てみると、printenvを含めて、全てadminになっていた^^。
ただし今回はuserにも実行権限(x)が付いているため、ios版も問題なく動作した。
(この記事の問題も含めて対応してくれたっぽい。よかったよかった。)
-rw-r-xr-x   1 user_x  admin  152080 Jan 28 07:23 printenv
↓
-rwxr-xr-x@  1 user_x  admin  152080 Feb  2 10:38 printenv

※余談だが、「About Android Studio」中の「Android Studio Bumblebee | 2021.1.1」とあるが、
本当に2021で合っているのか気になって調べてみたところ、合っていた。
(リリースタイミング(2022年1月)的にちょうど1年前だったので。流石にこんなところをミスる訳がないか。。。)
(ただし、Android Studioの歴史とかに詳しくない人にとっては(私も含め)、変な誤解を招きかねない^^。こういうとこから歴史に興味を持ってもらえれば幸いである。(私が言う事ではないが。))

2021年10月22日金曜日

M行障害について その2ー実態編ー

前回はそもそもの組織論やベクトルの視点での話となったが、せっかくなのでもう少し実態に目を向けた見方をしてみよう。

(その1はこちら

ネット情報では、
・巨大な化け物のようで誰も全体を掌握できてない
・旧行の各ベンダーが据え置かれたので話がややこしくなってる
など言われており、前者はむしろ調査報告で指摘されて明るみになるポイントだと思うが、後者は確かに日本のIT界では多くの人が直接的または間接的にお世話になった企業が名を揃えるような形になっている。

もしも要点が前者である場合は、そもそも誰も全体掌握できてないのにシステム刷新をしてしまったということになるので、確かにこれでは「ベクトル」以前の話であって、むしろ分かりやすい結論であろう。
(敢えて言えば、これだけのシステム刷新をするには成熟した組織・チームと、それをまとめ上げる力量を持った正に人たらしたるPMかつ経営陣とも100%ベクトルが揃ったPMが必要だったが、それよりも先に必要に迫られ、組織と人材の確認をする間もなく動き出さざるをえなかった、と言ったところだろうか?)

前回、システム刷新については極論すると「設計書は無いものと思え」と書いたが、例え設計書がすべて完璧に揃っていたとしても、その事を忘れると痛い目を見るのである。(その教訓を学ぶのに4,000億円はあまりに高すぎるが。また「システム刷新は慎重に行うべき」とさらっと書いてしまうと、その重みもなかなか伝わらないが、こういう事例を目の当たりにしてこの言葉の意味が再認識されるのだろうな。)

また、プログラムソースコードの言語の切り替え(乗せ替え)も軽く考えられがちである。特にシステムに詳しく無い経営陣には「プログラムなのだから機械的に1対1で新しい言語に変換すれば良いだろう」とか考えられたり、逆に開発陣が安易にそういった説明をしたりして、プロジェクトが走ってしまい実際蓋を開けてみると一筋縄ではいかず、結局昔のソースを追って手動で新しいソースを書き起こす羽目になり、当初予定をはるかにオーバーしたりする。
しかもCOBOLなど古い言語だと、現在の言語では整備されたライブラリがあったり1行で書けたりする処理であっても、古い言語は昔ながらの書き方しかできないのでせっかく新しい言語にしても回りくどい書き方で書かざるをえず、無駄だらけのソースになったりする。
また、業務的にも古いやり方やデッドコードが残っていて、ちゃんと業務視線で見直せば削ったりスッキリ書き直せる箇所も、意味もなく1対1で新しいソースに整形しなければならない。
更に言うと、1対1変換の方針とは旧システムをブラックボックスとみなして、それをそっくりそのまま新しいブラックボックスに乗せ替えるようなものであり、いざある程度うまく1対1変換できたとしても、試験の段階で何かバグがあると結局旧システムのソースを当たらなければならなくなるのである。どれだけ1対1変換がうまくいったか次第ではあるが、例えば見直し箇所が全体の5割りとかになってしまうと、最初から全体を見直してた方が良かったと「悟る」だろう。(そしてその時点で悟ってもかなり手遅れということにも気づく。)
そこまで行くと「安易に1対1変換方針にせずに、肝を据えて要件定義からやってた方が良かった」と後悔することになる。先ほどの例で言うと試験時点で実質要件定義の見直しをする羽目になってるにも関わらず、1対1変換方針であるから「改善」も許されず意味の無い(とまで言わないまでもかなり無駄な)ソースを作り続けなければいけないのである。これほど後ろ向きな作業も無いわけで、1対1変換という機械的作業を人がやってるような作業になってしまう。

そして最後は必ず試験を実施するのだが、例え1対1変換で旧ブラックボックスから新ブラックボックスに乗せ替えられたとしても、結局は試験の段階で、そのブラックボックスの中身に関わる所を見ていかなければならないのである。
システムがものすごく単純であれば、INとOUTだけチェックすれば良いだろうが、複雑なシステムだと様々なパターンや、システム全体を通しての試験をする必要があり、そういった試験をこなす時に技術者は「ここはブラックボックスなので動きは分かりません」とは言えるわけが無いのである。
つまり試験段階で最上位レベルの要件やシステムの「意味」まで把握する作業は当然である訳で、それを当然知っているのであれば、どちらの方式で対応するか判断するときの重要な要素になるだろう。
試験段階でどっちみち現在把握しきれてないシステムのソースレベルの意味まで調べることになるので、それならば要件定義・基本設計から業務を洗いなおして、古臭いやり方をしてる箇所でまとめられる箇所はまとめ、デッドコードも削ってすっきりと作り直した方が良いと判断するか。
または、試験段階でどっちみち現在把握しきれてないシステムのソースレベルの意味まで調べることになるが、工数削減のためあくまで1対1変換でいくと判断するか、である。

この工数というのも、ベテランPMであれば分かると思うが、隠れパラメータとして「どれくらい幅があるか」というポイントがあり、ある種取り扱いが危険な数値だと知っているだろう。
先ほどの例だと、前者は要件定義・設計・製造と大きな見積もりとなるが、幅としては「それほどぶれないだろう」と分かる。
後者だと、確かに設計・製造は小さい見積もりが出てくるだろうが、幅としての不確実性はものすごく大きい。(と、少なくとも嗅ぎ分けられるPMや、経営者側の人間がいなければならない。)
これまで説明した通り、蓋を開けるとうまく行かず、見直し箇所の方が大きい割合を占めると、当初見積もりの何倍にもなって、下手すると素直に前者でやってた方が安上がりだったという、目も当てられない結果になる。(おまけに作業は「こっちの穴を塞げば、あっちから水が出る」とう、凄まじく後ろ向きな作業となる。「士気」とかに直接結びつける気は無いが、前回書いた「ベクトル」という意味でいくと、果たしてこんなことになったとしたら、現場の一人一人は果たしてこのシステム刷新に対する熱量やベクトルを保ったまま作業できていたのだろうか?と思う。また、そこまで行くと「負の逆伝搬」が発生する。つまり現場が燃え上がると、それこそ「見栄」とか「ばれたら怒られる」という、「何とか抑え込もう」という非健全な力が発生するのである。小さなプロジェクトならもちろん押さえこめるのだが、今回のような超大規模プロジェクト、超多段階層となると、上層に行けば行くほど「もう手遅れ」になって、さらなる「蓋に蓋をする」事態になってしまうのである。(さすがにこれも基本事項としてPM教科書に書いてそうだが。)特にこういう時に「言われた通りやっており、その点は言われてないので当然やってません」という言い訳を準備しがちである。)

2021年10月13日水曜日

M行障害について

システム刷新プロジェクトに携わったことがある人であれば分かると思うが、これだけ問題が起こる原因の要点をまとめると以下になるのでは無いか。

・ベクトルが本当に揃っていたか。

つまり
・M行とベンダーのベクトルが本当に揃っていたか。
・経営陣と開発陣のベクトルが本当に揃っていたか。
ということ。

2021年9月30日木曜日

家からVPNが繋がらない場合の対処法:ISPのIPv6オプション(IPoE)でVPN接続

最近はISP(Internet Service Provider)によって、IPv6オプション(IPoE)というものがある。

従来までのPPPoEは、ダイヤルアップ方式接続(PPP)をイーサネット(IPv4)上で実現しましょう、的なもので詳細は他ページに譲るが、要点としては、網終端装置を通るためそこがボトルネックになっている、ということ。

そこで出てきたIPv6オプション(IPoE)は、網終端装置を通らないためPPPoEと比較すると通信がスムーズになるというメリットがある。

私の契約してるISPもIPv6オプション(IPoE)対応なので、早速使ってみたがテレワーク用PCから会社へのVPNが接続できなくなってしまった。

ネットを見てみると同じような話がたくさんあるため、これについても原因等は他ページを参照いただきたい。
(要はIPv4 over IPv6のあたり)

ここでは、IPv6オプション(IPoE)を使いながらも、VPN接続する方法があったためそれを紹介する。

【環境】
VDSL方式(マンション)で、ブロードバンドルータ兼Wi-Fiルータでインターネット接続(一応メーカーはBuffalo)。
このルータはデフォルトでIPv6接続方法:「インターネット@スタートを行う」になっていて、そのままIPv6オプション(IPoE)接続になってくれる。
※IPv6オプション(IPoE)開通連絡を受け取った後にルータの再起動だけ実施。
※なお、この設定を「IPv6を使用しない」にすると、VPNも繋がった。つまりIPv6オプション(IPoE)を諦めてPPPoEに戻すやり方。

ISPで、IPv4接続かIPv6接続かの確認ページが用意されていたので、ページを開いてちゃんとIPv6接続になっている事を確認。

テレワーク用PC含めて家庭内の機器は、ブロードバンドルータ兼Wi-FiルータにWi-Fiで接続する形。

【VPNに接続できてなかった時の症状】
テレワーク用PCで会社へVPN接続しようとすると、VPN接続時のIKEパケットでタイムアウトになって接続できない、という状態だった。

ネットを見ると「IPoEにはこういう問題があって無理なのでPPPoEに戻した」と言うのが大半なので「残念だけどIPv6オプション(IPoE)は無理か」と諦めかけた。


【解決策】
試行錯誤した結果、テレワーク用PCでPPPoE接続することで会社にもVPN接続できた。

「別途LANケーブルを繋いだのか?」と思われるかもしれないが、そうではなく、Wi-Fi経由でである。

他のページでは別のルータを間に挟んだりする解決策もあったが、このページで紹介する対策は追加機器も追加配線もなく、すっきり解決できたと思う。

【詳細】
接続形態としては
インターネット - 回線 - BBR -(Wi-Fi)- テレワーク用PC
である。(BBR: ブロードバンドルータ兼Wi-Fiルータ)

テレワーク用PC(Windows 10)上で、ネットワーク設定から「ダイヤルアップ」に新しい接続を作成。
BBRに設定した、ISP接続情報と同じ情報で作成。
「アダプターのオプションを変更する」を開き、作成したダイヤルアップ接続のプロパティを開いて、「ネットワーク」タブのTCP/IPv6のチェックを外す。