この記事は初心者向けの学習法ではなく、技術の中心がAIへとシフトする中で、それまでのIT技術者がどうやって学習モチベーションを維持しつつ、AIを有効活用しているかという技術研鑽推奨、啓蒙目的で書いたものです(文章は全部頭の中で考えて手打ちしています)。
まず、自分はAIにアンチテーゼを唱えていた技術者で、そんな自分がAI使用を始めたのは今年になってからです。AIに触れたくなかった理由は、なんでもかんでも機械任せにして思考、記憶能力が衰えていく、コーディング能力(実務感覚なども含め)が錆びついていくリスクを恐れていたからです(実際、昨今ではAI依存による論理思考能力、記憶力の衰弱化とそれに伴う若年性認知症候群というSFフィクションじみた社会問題が既に露顕を始めている)。
ですが、転機が訪れたのはGoogle検索にAIモード(Gemini)が搭載されてからで、随時AIモードを使って質問していった方がWeb上の情報を探っていくより遥かに効率が良いことを知りました。
それと同時にAIモードの弱点も知っていくことになり、一ヶ月ほど質問精度の分析を続けていましたが、自由度の高い動的言語はかなり誤った情報が多いです(特にハード依存も多いPythonやReactなどのJS系フレームワーク。逆に「こうなったら最後の手段です」とか、楽しい回答を得ることもできますが)。それで一時はAIなんて所詮はそんなものかという懐疑的な心を持ち続けていました(自分が依存性の高いプロンプトエンジニアリングをそこまで信用しない理由の一つ)。
それでも昨今は猫も杓子もAI駆動開発、ClaudeやCodex、Cursor、Antigravityなどどんどん依存性の高いライブラリやアプリケーションが席巻するようになり、学習モチベーションに支障をきたすようになりました。そんな中、逆に思いついたことが、多彩な言語やフレームワークの読解力をどんどん上げていってやろう(習得言語をもっと増やそう)という野心です。
自分はWebシステム、Webプログラムを専門的に実務開発しており、時代に合わせPHPとjQueryだけだった乏しい能力も、今は20近く学び、どの言語でも対応し、実装できる準備をしています。ですが、それはいずれも動的言語ばかりでアプリケーション系に弱くもっと静的言語を習得する必要性を感じていました。
そこで今回学習成果、Geminiによる学習能率アップの成果を試すべく学習材料としたのが静的言語のGoとRust、いずれも名前しか聞いたことがなかった存在で、そしてどちらもそれまでの言語と較べ自由度の低い、一定の正確性を求められる言語です。ですが、その分AIから確実性の高い回答を得られるのではないかと思い、Geminiを使って学習してみることにしました。その目論見は見事に大当たりで、特にRust/Axumにおいては、問い合わせた質問に対しほぼ100%正確な回答を得ることができ、Gin以上の吸収速度で次の実務開発に支障ないレベルまで開発スキルを培うことができました。
この記事では便宜上GoogleのAIモードをGeminiと呼んで話を進めていきます(GoogleAIに質問しても「今は同じと解釈して問題ないです」と回答している)。
それから自分は、標準ではGoogleのAI検索をオフにして、必要なときにAIモードを使うようにしています(関連プラグインを使えばできます)。でないと、関係ない事物まで勝手にAIで回答して、ハルシネーションやコンタミネーションによる汚染が進んでいくからで、これはかなり大事な事前作業です。
また、質問の仕方も今までのGoogleのようなキーワード検索ではなく、文章そのもので検索するようにします。
Geminiへの質問
質問にもコツがあります。まず大前提として学習するというスタンスを忘れてはいけません。なので、主題を持たない丸投げだけは絶対にやめましょう。それとGemini構造上の欠点であるハルシネーションとセレブレーション(エンターテインメントモード化)を避けていく必要があります。
1.最初にトピックスを決めて、そこに絞って質問すること
たとえばginの場合は
Goのginにおいて、トップ画面へのルーティングを考えている
このような質問を投げることで、Geminiは(この利用者はginでのシステム構築を企図しているのだなと)、今後はginに対する回答だけに絞り込むことができます。ただ、上記の書き方だけだと、余計な忖度も多くなり、質問内容がぼやけてしまいます。
そこで、次のような質問方法が有効です。
2. 質問は目的を投げるだけではなく、なるべく検証と仮説を付記すること
「●●●」というエラーが返された
これだけでは、いろいろな原因を類推してズラッと回答を返してくるので、本題から逸れるリスクがあります。
「●●●」というエラーが返された。コントローラには▲▲と追記している
このような質問方法です。するとGeminiが学習し、それとの関連性をしっかりと分析し、正しい答えへと導いてくれます(句点、疑問符は不要)。
またEnterを押すと回答に移行してしまうので、質問フォームで改行したい場合はShift+Enter、この習慣を憶えておきましょう。
3. 別トピックは新しいタブで並行に質問すること(重要)
追記質問は、最初の質問と関連付けて無理やり回答を返していく特性があります。なので、本題に逸れた質問をするとそれも無理やり紐付けようとします。たとえば
ブラウザのキャッシュを初期化したい
このような本題から逸れる質問を投げる場合は必ずタブブラウザを使い、そして回答を得たら適宜閉じてしまいましょう。
この無理やり回答に導こうとして事実を捻じ曲げてしまう現象のことをハルシネーション(Geminiに多い)、また関係のない事実同士に対し重みを考慮せずこじつけて接合してしまう現象をコンタミネーション(Claudeに多い)といいます。これは自分に興味のある事柄でAI検索してみるといいでしょう。この現象を体験することによって、AI任せのコーディングがいかに危険か、その危険因子となっているかを身を以て体感することだろうと思います。
残るCopilotですが、この部分に対しては安全装置が働いており、匙を投げてしまう傾向があります(これは性能が低いという錯覚を与えているようですが、間違った回答を返すぐらいなら最初からしないほうがいいという方針によるものです)。
4. トピックスの目的を達成したら適宜リセットする方が良い
AIは人間ではありません。なのでしょーもない些細な質問をしても、バグを消せなかった理由が呆れられるようなイージーミスであったとしても怒られることは全くありませんし、逐一お礼のメッセージを送る必要もありません。利用者が、好きなように利用して、問題解決すれば閉じてやればいいだけです。
ただ、解決したかどうかは記述しておく必要があります(でないと、まだそこを解決していないのかと判断して、次の項目を結びつけようとする)。
●●は解決したが、次は◯◯をしたい。コントローラにはこの書き方で大丈夫か
こんな感じで質問します。これも後述しますが、ただ「◯◯したい」だけでは丸投げになってしまい、学習効率は悪いです。
またログインしておくと閉じても記憶を元に戻すことができますが、自分はログインしていません。また目的を完遂したトピックは逐一リセットした方がいいでしょう。あくまでトピックスは開いたタブブラウザだけを使い追記で回答を求めること、このやり方が確実です(でないと、過去の事例に無理に関連づけようとする。よくいう馬鹿になるAIの原因)。
5.無関係な質問をしてしまった、間違ったタブブラウザで質問した場合
ずばり、某漫画家風に「なかったことにして下さい」でOKです。
厳密には
Rustでの質問は取り消し。Goの場合は
これだけで誤った質問をなかったことしてくれて、今後Rustのケースを回答に交えてくることはなくなります。
6.ソースを貼り付ける前によく確認すること
エラーメッセージを貼り付ける、また因果関係を説明するため、それに関連したコード記述を貼り付けることは非常に多くなります。したがって、絶対に使用するのは学習用プログラムだけにしましょう。間違っても納品予定や開発途中の実務プログラム、他人が作成したプログラムなどを使ってはいけません。
なぜならログインしていない=個人情報に結びつかない、と言い切れないからです。あと学習用でもIPアドレス(ブリッジ接続ならまあ大丈夫でしょうが)やDB情報(ネットワークIDとパスワード)なども気を付けましょう。
あと詳しくは触れませんが、
【❌️警告】
APIキーだけは絶対に貼り付けてはいけません
世界中で大変な目に遭った人が大勢いますので。
具体的な学習方針
自分が新たな言語を学習するときには「実践感覚を身に付ける」ことを意識しています。逆にそれをやらないと、まるで新しい言語を習得できないことも自覚しています。昔にVueやReact、Angular、Svelteの初心者向けマニュアル記事を作ったのも、あくまで自分の備忘録としてです(記事は作っていませんが、Laravel、DjangoやPerl、Railsなども自分用の備忘録を作っています)。
そのために必要な三段階です。
1. 基本文法のお勉強
プログラミング言語と呼ばれるだけあって変数定義、ユーザ定義関数、配列、連想配列(言語によってはディクショナリやハッシュとも)、オブジェクト(これも言語によって色々)やif、forなどの分岐、ループ、その他クラス化、参照などの基本が全く違います。そこで、これらの文法を意識するよう(杓子定規に覚える必要はありません。受験ではないので見ずに書けるのが目的ではなく、納期までにシステムを構築できればいいので大事なのは読解力と応用力鍛錬です)適宜入門サイトを見つけ、1時間ないし2時間程度勉強しておきます。変数も自分の好きな物事をサンプルに使うといいでしょう。まずは、その言語を楽しんで慣れ親しんでおくこと(新しい言語でhello, worldを出力したときの初心を思い出す)です。
2. 最適なフレームワークを見つけ準備をする
基礎的な勉強をしても実践感覚が身に付かないとすぐ忘れてしまいます。なので、実用性を実感すべく各言語に対し最も普及している、あるいは初心者に適したWEBフレームワークを探し、そのセッティングをしていきます。数ある中で自分はGoにおいてはgin、Rustはaxumを選んだのも、Geminiに質問したり、Qiitaの記事を覗いたりした上での選択です。
ここではあまり言語の勉強は必要ないですが、極力キーを打つこと(写経)を心がけて下さい。1で一通り追ってきた文法を具体例を以て観察、入力することによって、目や脳が言語に馴染んでいくというプロセスを体感できるでしょう。
3. 習熟度の高いプログラムで作成した学習プログラムを、その言語で書き写してみる
ここからがGeminiを活用した学習の本番です。自分は基本的にCRUDシステムを作成するようにしています(大元になったものはRailsで作ってみた学習用システムで、それをCakePHP、Laravel、Sinatra、Flask、Django、Vue、React、Angular…とアレンジしていっています)。いずれも、参考サイトではテンプレートファイルへの展開がなかった(JSONに返す前提)ので、MYSQLへの接続を伴うCRUDシステムの作成を一旦の目標としています。
ここからの能率が今までとは段違いになります。AIがなかった頃、自分たち技術者がどうやって新言語を学習していったかを思い出してみて下さい。ネットサーフィンを繰り返し繰り返し、最適な情報を探すのに苦労したと思います。その理由として
- 情報の信頼性が保証できない
- バージョン違いなど、情報のアップデートにおいて混乱させられた
- そもそも欲しい情報が見つからない
- 開発の主体が海外のため、英語読解能力を鍛え直す必要性が求められた
それで質問投稿サイト、Stack OverflowやTeratailのお世話になったことも多々ありました。ですがGeminiだとそんな無駄な時間の浪費や遠回りの手間はもうありません。ひたすら質問を投げて回答を得、構築、検証していくだけです。しかも静的言語においては回答も精度が高い上、Google日本語検索を作っただけのことはあって、Geminiの日本語の解釈能力は格段に優れています(途切れ途切れのプロンプトキーワードではなく、日本語文章そのもので質問できる)。もし、英語で回答が返ってきても、それを「日本語で」と追記質問すればいいだけです。
更に応用した課題
4. 少し背伸びした質問もする
単に質問するだけだと実践的なプログラムではなく、あくまで必要最低限のプログラムしか作れません。なので、この部分が冗長に感じる、この部分を外部ファイルで切り離したい、常にそういう実践的なシステムを作る感覚を培っておくことが大事です。そして、そこをタイミングよく質問していくと、適宜適切な解決方法を教えてくれます。
具体的にはテンプレートのレイアウト統一とコンテンツ切り離し、ルーティング記述の簡素化、モデルの切り離しなどで、いろいろなMVC、MTVフレームワークの体験者なら経験とカンで、そういう発想を随時浮かべることができるでしょう。
回答の締めで提示される「●●について質問しますか?」とかいう補助アドバイスに従う必要は全くありませんし、人間と違って「お前にはまだ早い」とか言われることもありません。自分のペースでスキルアップできます。
5. 随時コメントを付記する
コメントは大きな意味合いを持ちます。それは備忘録であると同時に習熟度の確認にもなるからです。なので、自分が必要だと思った場所は適宜コメントを追記していきましょう。
6. 答えを返してくれても重要だと感じた部分は実際に打って体験する
質問の回答は時折、かゆいところに手が届くほど返してくれることがあります(テンプレートの質問だけしたのに、コントローラの書き方まで返ってきたり)。ですが、自分がまだ習得していない部分はまるまるコピペしようとせずに、しっかりと写経して記述方法を脳と指で体験させていきましょう。
ここでは自分で納得するという過程が重要です。
逆にもうわかりきっているから面倒くさいと感じた場所はコピペでも構いません。言語学習はストレスを感じてしまうと、吸収力が一気に下がります。使い回しのテンプレートや、学習済で記述が酷似した部分はコピペでもそこまで支障はありません。
7. 自分で知識、技術を応用してみる
習熟度を確認するために、自分から記述を足していきましょう。具体的には新規作成ページと登録機能を作れたなら、それを元に編集ページと修正機能及び詳細ページを作る、といった応用です。コンパイルするとそれなりにエラーが多発しますが、自分で解決できるところは解決し、そうでない所は質問していきましょう。
応用能力が格段に上がります。
8.新たな学習課題を見つけ自分で問いてみる
その応用力を使って今度は新たなことに挑戦(ファイルのアップロード、JSONへの保存、APIの連携など)してみましょう。ただ、それをただ質問してコピペするのではなく、自分で書き写してみることが大事です。
学習サンプル
では実際に主にGeminiだけを使って質問、構築していったサンプルをある程度載せてみます。まだ中途の段階ですが、最低限の動作は実現しています。
MYSQLからデータを呼び出し、それを一覧に表示。またそのテーブルに対しCRUDできるという、ごく基本のシステムです。また詳細、編集ページはパスパラメータ、削除機能はクエリパラメータでIDを取得、詳細ページには静的メソッドから画像を表示できるようにしています。
※参考にした学習ページの影響で、ginは書籍参照システム、axumはtodoアプリになっていますが、学習目的とメカニズムは概ね同じです。
後は発展的な学習として、ファイルアップロード機能などを搭載してみるといいと思います(ここには載せていませんが、学習環境では実装済)。
使用環境
- Virtual Box7
- AlmaLinux9.3
- Mysql8
※クローンした同一環境をそれぞれGoとRust用にしている
Go/gin
Ginにおいてネックとなるのはルーティングの記述が少し複雑なのと、DBの呼び出しがけっこう複雑な点です。また経験者ならわかるでしょうが、Goは未使用の変数やライブラリを放置するとコンパイルエラーとなります。なのでデバッグに頻用するfmtは、未使用の場合コメントアウトしています。
プロジェクト名はgo_projです。
メインプログラム(ルーティング情報も格納している)
package main
import(
//"fmt"
"github.com/gin-gonic/gin"
"github.com/gin-contrib/multitemplate"
_ "github.com/go-sql-driver/mysql"
"go_proj/controller" //プロジェクト内のコントローラ
"go_proj/middleware" //プロジェクト内のミドルウェア
//"net/http"
)
//レイアウトの紐づけ
func createLayoutRender() multitemplate.Renderer {
r := multitemplate.NewRenderer()
// layout と各ページをセットにして「名前」をつける
r.AddFromFiles("top", "templates/layout.html", "templates/top.html")
r.AddFromFiles("detail", "templates/layout.html", "templates/detail.html")
r.AddFromFiles("list", "templates/layout.html", "templates/list.html")
r.AddFromFiles("insert", "templates/layout.html", "templates/insert.html")
r.AddFromFiles("edit", "templates/layout.html", "templates/edit.html")
return r
}
func main(){
engine := gin.Default()
engine.Static("/static", "./assets") //CSSをstaticで使用
engine.Use(middleware.RecordUaAndTime)
engine.LoadHTMLGlob("templates/*") //テンプレートフォルダ指定
engine.HTMLRender = createLayoutRender()
//ルーティング設定(パス、関数(c.HTML(ステータス、テンプレートファイル、具体的なメッセージ))
engine.GET("/book",controller.PageList)
//子パス
book := engine.Group("/book")
{
book.GET("/detail/:id",controller.PageDetail) //登録画面
book.GET("/insert",controller.PageInsert) //登録画面
book.GET("/edit/:id",controller.PageEdit) //修正画面
book.POST("/add", controller.BookAdd) //登録
book.POST("/update", controller.BookUpdate) //修正
book.POST("/delete", controller.BookDelete) //削除
}
engine.Run("0.0.0.0:3000")
}
コントローラ
package controller
import(
//"fmt"
"github.com/gin-gonic/gin"
"net/http"
"go_proj/model"
"go_proj/service"
"strconv"
)
//一覧画面
func PageList(c *gin.Context){
bookService := service.BookService{}
BookLists := bookService.GetBookList() //DBから一覧の呼び出し
c.HTML(http.StatusOK, "top",gin.H{
"message": "ok",
"data": BookLists,
})
}
//詳細画面
func PageDetail(c *gin.Context){
id := c.Param("id")
bookService := service.BookService{}
SelBook := bookService.SelectBook(id) //DBから任意の本を呼び出す
c.HTML(http.StatusOK, "detail",gin.H{
"message": "ok",
"Book": SelBook,
})
}
//登録画面
func PageInsert(c *gin.Context){
c.HTML(http.StatusOK, "insert",gin.H{
"message": "ok",
})
}
//修正画面
func PageEdit(c *gin.Context){
id := c.Param("id")
bookService := service.BookService{}
SelBook := bookService.SelectBook(id) //DBから任意の本を呼び出す
c.HTML(http.StatusOK, "edit",gin.H{
"message": "ok",
"Book": SelBook,
})
}
//データの追加
func BookAdd(c *gin.Context){
book := model.Book{}
fmt.Println(&book)
err := c.Bind(&book)
if err != nil{
c.String(http.StatusBadRequest, "Bad Request")
return
}
bookService := service.BookService{}
datum := bookService.SetBook(&book) //値の登録
if datum != nil{
c.String(http.StatusInternalServerError, "Server Error")
}
c.Redirect(302,"/book")
}
//データの更新
func BookUpdate(c *gin.Context){
book := model.Book{}
err := c.Bind(&book)
if err != nil{
c.String(http.StatusBadRequest, "Bad Request")
return
}
bookService := service.BookService{}
err = bookService.UpdateBook(&book)
if err != nil{
c.String(http.StatusInternalServerError, "Server Error")
return
}
c.Redirect(302,"/book")
}
//データの削除
func BookDelete(c *gin.Context){
id := c.Query("id")
intId, err := strconv.ParseInt(id, 10, 0)
if err !=nil{
c.String(http.StatusBadRequest, "Bad request")
return
}
bookService := service.BookService{}
err = bookService.DeleteBook(int(intId))
if err != nil{
c.String(http.StatusInternalServerError, "Server Error")
return
}
c.Redirect(302,"/book")
}
サービス(とりわけDBの機能を格納)
import(
"errors"
"fmt"
"github.com/go-xorm/xorm"
"go_proj/model"
"log"
)
var DbEngine *xorm.Engine
//DBへの接続
func init(){
driverName := "mysql" //DB
DsName := "id:password@(127.0.0.1)/testdb?charset=utf8" //testdbは任意のDB名
err := errors.New("")
DbEngine, err = xorm.NewEngine(driverName, DsName)
if err != nil && err.Error() != ""{
log.Fatal(err.Error())
}
DbEngine.ShowSQL(true)
DbEngine.SetMaxOpenConns(2)
DbEngine.Sync2(new(model.Book))
fmt.Println("init data base ok")
}
//具体的なDB処理
package service
import (
"fmt"
"strconv"
"go_proj/model"
)
type BookService struct {}
//本の一覧
func (BookService) GetBookList() []model.Book{
books := make([]model.Book, 0)
err := DbEngine.Distinct("id", "title", "author","Publisher").Limit(10, 0).Find(&books)
if err != nil{
panic(err)
}
return books
}
//選択した本を取得
func (BookService) SelectBook(idstr string) model.Book{
id,_ := strconv.ParseInt(idstr,10,64)
var book model.Book
_,err := DbEngine.ID(id).Get(&book)
if err != nil{
panic(err)
}
return book
}
//本の登録
func (BookService) SetBook(mst_book *model.Book) error {
fmt.Println("DBINS")
fmt.Println(mst_book)
_, err := DbEngine.Insert(mst_book)
if err != nil{
return err
}
return nil
}
//本の更新
func (BookService) UpdateBook(newBook *model.Book) error{
fmt.Println("DBUPD")
_, err := DbEngine.Id(newBook.Id).Update(newBook)
if err != nil {
return err
}
return nil
}
//本の削除
func (BookService) DeleteBook(id int) error{
book := new(model.Book)
_, err := DbEngine.Id(id).Delete(book)
if err != nil{
return err
}
return nil
}
モデル
package model
//構造体を定義
type Book struct {
Id int64 `xorm:"pk autoincr" form:"id"` //db上もautoincrement制御すること
Title string `xorm:"varchar(40)" form:"title"`
Author string `xorm:"varchar(40)" form:"author"`
Publisher string `xorm:"varchar(40)" form:"publisher"`
Content string `xorm:"varchar(255)" form:"content"`
Pic string `xorm:"char(40)" form:"pic"`
}
//テーブル名の定義
func (b *Book) TableName() string{
return "mst_books"
}
テンプレート
レイアウト
<!DOCTYPE html>
<html lang="ja">
<head>
<link rel="stylesheet" href="/static/css/style.css">
</head>
<body>
<!-- 各コンテンツ -->
{{ template "content" .}}
</body>
</html>
一覧ページ
{{define "content"}}
<div class="flex_c">
<nav>
<div><a href="book/insert"><button type="button" class="bt_large">登録</button></a></div>
</nav>
<article>
<h2>データの一覧</h2>
<table class="tbl_lists">
<thead>
<tr>
<th>ID</th>
<th>名称</th>
<th>作者</th>
<th>出版社</th>
<th>修正</th>
<th>削除</th>
</tr>
</thead>
<tbody>
<tr>
{{ range .data}}
<tr>
<td><a href="/book/detail/{{.Id}}">{{ .Id}}</td>
<td>{{ .Title}}</td>
<td>{{ .Author }}</td>
<td>{{ .Publisher }}</td>
<td>
<a href="/book/edit/{{.Id}}">
<button type="button" class="bt_small bt_upd">修正</button>
</a>
</td>
<td>
<button type="submit" formaction="/book/delete?id={{.Id}}"
onclick="return confirm('削除しますか?')" class="bt_small bt_del">削除</button>
</td>
</tr>
{{ end }}
<tbody>
</table>
</article>
</div>
{{end}}
Rust/Axum
Rustの方が時間がかかるのかな、と思ったのですが堅牢性が高い分、AIの回答精度が非常に高い(RustがAI向けといわれる理由がとくと解りました)ので、Ginより苦労は少なかったです。またDBの接続とクエリ操作がGoよりずっと楽に感じました。逆にコントローラ制御が少し複雑です。
また、Rustの基礎勉強で苦労した変数の借用はあまり問題にしなくていいです(Axumの場合は殆ど使用しない)。
toml
[package]
name = "todorust" #プロジェクト名
version = "0.1.0"
edition = "2024"
[dependencies]
axum = { version = "0.8.1", features = ["multipart"] }
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
uuid = { version = "1", features = ["v7", "serde"] }
tower-http = { version = "0.6.2", features = ["fs", "trace"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
async-trait = "0.1"
dotenvy = "0.15"
chrono = { version = "0.4", features = ["serde"] }
http = "1.2"
sqlx = { version = "0.8", features = ["runtime-tokio", "mysql"]}
askama = "0.12"
メインプログラム
mod controllers; //コントローラ
mod models; //モデル
use axum::{
response::{Html, IntoResponse, Response},
routing::get,
Router,
Extension,
};
use tower_http::trace::TraceLayer;
use tower_http::services::ServeDir; //CSSを静的フォルダで使用
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
use sqlx::mysql::MySqlPoolOptions;
//crateは根っこという意味でルート(src)を示す。以下use crate::ディレクトリ::ファイル名::メソッド
#[tokio::main]
async fn main() {
// ロギングの初期化
tracing_subscriber::registry()
.with(
tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| {
format!("{}=debug,tower_http=debug", env!("CARGO_CRATE_NAME")).into()
}),
)
.with(tracing_subscriber::fmt::layer())
.init();
//DB接続
//使用DBMS://ID:PW@IPaddr:port/dbname
let database_url = "mysql://id:password@127.0.0.1:3306/testdb";
let pool = MySqlPoolOptions::new().connect(database_url).await.expect("dbconfailed!");
// ルーティング
let app = Router::new()
.nest("/todos", crate::controllers::user::router()) //ルーティングを移譲する
.nest_service("/static", ServeDir::new("static")) //静的フォルダの紐づけ
.layer(Extension(pool)); //DB接続の共有
// サーバーの起動
let addr = std::net::SocketAddr::from(([0,0,0,0],8000));
let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
tracing::debug!("listening on {}", listener.local_addr().unwrap());
axum::serve(listener, app).await.unwrap();
}
モジュール(汎用メソッドも格納)
//モジュールファイル
use axum::{
response::{Html, IntoResponse, Response},
http::StatusCode,
extract::Path,
};
use askama::Template;
use sqlx::MySqlPool;
pub mod user; // user.rs を公開
pub struct HtmlTemplate<T>(pub T); //汎用テンプレートの構造体
use crate::models::Todo; //Todoモデル
// テンプレートをレスポンスに変換するためのラッパー
impl<T> axum::response::IntoResponse for HtmlTemplate<T>
where
T: Template
{
fn into_response(self) -> Response {
match self.0.render() {
Ok(html) => Html(html).into_response(),
Err(_) => (StatusCode::INTERNAL_SERVER_ERROR, "Template Error").into_response(),
}
}
}
//idを元にテーブルデータを取得する
pub async fn getTodoFromId(
pool: &MySqlPool,
id: i32,
)-> Todo{
let todo = sqlx::query_as::<_,Todo>("select * from todos where id = ?")
.bind(id)
.fetch_one(pool) //単一のデータを取得
.await.
unwrap();
return todo;
}
コントローラ(ルーティング情報もここに格納)
use axum::{
response::{IntoResponse,Redirect},
Extension,
routing::{get,post},
Router,
Form,
extract::{Path,Query}, //パラメータの取得
};
use askama::Template;
use sqlx::MySqlPool; //DB接続用のラッパー
use super::HtmlTemplate; //汎用テンプレート化ラッパー
use super::getTodoFromId; //IDから取得するユーザ定義メソッド
use crate::models::Todo; //Todoモデル
use serde::Deserialize; //POSTの受取に用いるシリアライザ(無害化処置)
//ルーティング設定
pub fn router() -> Router {
Router::new()
.route("/", get(list_todos)) // 一覧
.route("/", post(create_todo)) // 新規登録 (POST /todos)
.route("/new", get(new_todo)) // 登録画面
.route("/edit/{id}", get(edit_todo)) // 編集画面表示
.route("/update", post(update_todo)) // 更新実行 (POST /todos/update)
.route("/delete", get(delete_todo)) // 削除実行 (GET /todos/delete?id=...)
.route("/detail/{id}", get(detail_todo)) // 詳細
}
#[derive(Template)]
#[template(path="top.html")] //テンプレートファイル
pub struct TodoTemplate{
pub todos: Vec<Todo>, //テンプレートに返すTodoリスト(ベクタで生成する)
}
//テンプレートに返す
pub async fn list_todos(
Extension(pool):Extension<MySqlPool>,
) -> impl IntoResponse {
//モデルと一致させるために全カラムを取得すること(テンプレートの展開有無にかかわらず)
let todos = sqlx::query_as::<_,Todo>("SELECT id,title,content,completed FROM todos")
.fetch_all(&pool).await.unwrap();
HtmlTemplate(TodoTemplate{ todos })
}
//登録画面
#[derive(Template)]
#[template(path="new_todo.html")] //テンプレートファイル
pub struct NewTodoTemplate{} //フォームを表示するだけなので空で問題なし
pub async fn new_todo() -> impl IntoResponse{
HtmlTemplate(NewTodoTemplate {})
}
//修正画面
#[derive(Template)]
#[template(path="edit_todo.html")] //テンプレートファイル
pub struct EditTodoTemplate{
todo: Todo,
}
pub async fn edit_todo(
Path(id): Path<i32>,
Extension(pool): Extension<MySqlPool>,
) -> impl IntoResponse{
let todo = super::getTodoFromId(&pool,id).await; //呼び出し処理
HtmlTemplate(EditTodoTemplate { todo})
}
//詳細画面
#[derive(Template)]
#[template(path="detail_todo.html")] //テンプレートファイル
pub struct DetailTodoTemplate{
todo: Todo,
}
pub async fn detail_todo(
Path(id): Path<i32>,
Extension(pool): Extension<MySqlPool>,
) -> impl IntoResponse{
let todo = super::getTodoFromId(&pool,id).await;
HtmlTemplate(DetailTodoTemplate { todo})
}
//具体的なDB処理
//登録処理
#[derive(Deserialize)]
pub struct CreateTodo{
pub title: String,
pub content: Option<String>,
}
pub async fn create_todo(
Extension(pool): Extension<MySqlPool>,
Form(input): Form<CreateTodo>, //フォームの受取
) -> impl IntoResponse{
//sqlはbindメソッドでプレースホルダとして代入することまたカラム名の省略は推奨されない
sqlx::query("insert into todos(title,content,completed) values(?,?,?)")
.bind(&input.title)
.bind(&input.content)
.bind(false) //bool型completedの初期値
.execute(&pool).await.unwrap();
Redirect::to("/todos")
}
//修正処理
#[derive(Deserialize)]
pub struct UpdateTodo{
pub title: String,
pub content: Option<String>,
#[serde(default)] //送信されなかった場合、falseにする制御が必要になる
pub completed: bool,
pub id: i32,
}
pub async fn update_todo(
Extension(pool): Extension<MySqlPool>,
Form(input): Form<UpdateTodo>, //フォームの受取
) -> impl IntoResponse{
//sqlはbindメソッドでプレースホルダとして代入することまたカラム名の省略は推奨されない
sqlx::query("update todos set title= ?,content=?,completed=? where id = ?")
.bind(&input.title)
.bind(&input.content)
.bind(&input.completed)
.bind(&input.id)
.execute(&pool).await.unwrap();
Redirect::to("/todos")
}
//削除処理
#[derive(Deserialize)]
pub struct DeleteParam{
id: i32,
}
pub async fn delete_todo(
Extension(pool): Extension<MySqlPool>,
Query(params): Query<DeleteParam>,
) -> impl IntoResponse{
sqlx::query("delete from todos where id = ?")
.bind(params.id)
.execute(&pool).await.unwrap();
Redirect::to("/todos")
}
モデル
use sqlx::FromRow; //カラムの取得
#[derive(FromRow)]
pub struct Todo{
pub id: i32, //AutoIncrementを採用する場合
pub title: String,
pub content: Option<String>, //NULLを許容する場合
pub completed: bool, //TinyInt型を採用する場合
}
テンプレート
レイアウト
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>{% block title %}My TODO App{% endblock %}</title>
<link rel="stylesheet" href="/static/css/style.css">
</head>
<body>
<div class="container">
{% block content %}{% endblock %}
</div>
</body>
</html>
一覧ページ
{% extends "layout.html" %}
{% block content %}
<nav>
<a href="/todos/new">登録</a>
</nav>
<table>
<thead>
<tr>
<th>ID</th>
<th>題目</th>
<th>内容</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for todo in todos %}
<tr>
<td><a href="/todos/detail/{{todo.id}}">{{ todo.id}}</a><td>
<td>{{todo.title}}</td>
<td>{{todo.content.as_deref().unwrap_or("")}}</td>
<td>
<a href="/todos/edit/{{todo.id}}"><button type="button">修正</button></a>
<a href="/todos/delete?id={{todo.id}}"><button type="button" onclick='return confirm("削除しますか?")'>削除</button></a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}
従来のネット巡回はもう不要なのか?
結論からいうと言語によってはまだまだ必要です。
というのも、AIによる質問を、最大限に有効活用できたのはあくまでGo、Rustという堅牢性の高い静的言語だったからで、カスタマイズ性の高くGPU依存も多い動的言語Pythonで開発したい場合、開発に役立ったのはStack OverflowのほかRedditやNote(GPU系は素人ギーグ連中がかなり強いので)ですし、同じく自由度の高いReactやVueの場合もQiitaやZennなど投稿型技術サイトが一番学習に役立ちました。またPHPフレームワーク系、PerlやRailsは開発者ブログなども大いに役立ちます(その企業や個人がそれで売上の要、生活の糧にしているので、情報の信頼性が極めて高い)。
なぜならGeminiが有効な情報を見つけてくるけど、その回答の選択肢、手段が多すぎてブレることが多いほか、ルーティング、ネットワーク設定周りの回答が不明瞭(Geminiに「あっ、そうですね」「そう、その通りです。すみません」って返されたことも屡々)、人間社会、業界のリスクマネジメント対策もまだまだ脆弱だからです(現在、Claude利用者で多発しているモラルハザードなど)。また、エラーに対する質問の持続や問題解決しない状況に対し、無理に因果関係を結びつけよう、問題を解決させようとして、やけっぱちに「こうなったら最後の手段です」とコメントを前置きした上で、どマイナーな手法やトンデモ魔改造、リスクギリギリの悪手を使ってまで改修案を提示してきたりすることもあるので、下手すると開発環境やPCまで壊しかねない状況に陥る危険が実際にあります(特にPythonは「まあ落ち着け」と言いたくなるような回答が頻発しました)。Geminiはモラル的な問題が起きたときは強制終了する安全装置が働いています(セキュリティ面に関してはかなり詳しくソースを提示してくる)が、ハード面に対するスペック問題はあまり気にしない傾向があります(個人の開発環境差異があるため)。
したがって軌道修正したい場合、AIでどうしても最適な解決法を得られなかった場合に上記の情報を適宜取り込めば、うまく調節、応用できます(自由度の高い言語は逆に応用力の方が重視されることは体感している)。
総括
この学習法ですが、あくまで一つの言語で実践感覚を身に付けている経験者向けです(5月に実施していた初心者応援記事で投稿しなかった理由)。IT初心者が真似すると、かなりしんどいと思います。PHPでもPythonでもいいので、まずは一つ何かを身につけて下さい(MVCフレームワークのマイルストーンとなったRailsもおすすめで、Railsを履修すると他のフレームワークも芋蔓式に、簡単に理解できるようになります)。
あと自分は前半をGo、後半をRustではなく、数時間ごとにクロスして学習しています。以前Vue、React、Angularを学習したときもそうですが、随時学習内容をまっさらなものにリフレッシュさせることで脳裏の情報整理、分析能力が何倍にも培われる(あと自分は、興味ある事柄には子供の頃から背伸びして寄り道したがる主義だったので、そっちに気を取られ集中力散漫になりがちだった)と感じているからです(受験勉強でも同じ科目を集中的にこなすより、カリキュラムを組んでクロス学習した方が能率が上がる理屈と同じ)。
現在は学習だけでなく、実務でもノウハウを応用しています(開発所要時間は従来の3割未満に短縮。機能API系開発なら2割未満に短縮できます)。つまり、やっていることは今までと変わらないのに、その変わらなかったときに費やしていた時間と労力の無駄をかなり吸収できるおかげで、ここまで爆速、時短で開発できるようになります。これも機会があればまとめて記事にしたいとは思ってはいるのですが、いかに相手に必要な情報を悟られないように貼り付けて質問していくかが肝となります(それでも何をやろうとしているかは、ずば抜けた分析能力で暴いてしまいます)。
そのカラクリを一つ解き明かすと、自動生成によって作られたコードはゼロから生まれたもののため、本当にそれが確実なのかを読解するのに時間を費やすのに対し、コンサルティングを兼ねて自作するコードは既に本人が読んで中身を理解しているということです。自分で考えて話したこと書いた文章を覚えることと、他人に聞いたこと書いた文章を覚えることでは、その脳への吸着と定着が全然違います(ほとんどの人は前者の方が強く働き、その能率は3倍~10倍異なるのだとか)。
無計画に、かつ闇雲に質問するから、Geminiの性能が低いと誤解されているのでしょうが、それは正しく使っていないだけで、自分はGeminiこそが技術汎用性と脳思考能力維持など全てを加味した上で最強のAIエージェントだと思っています(ケースバイケースでCopilotと使い分けています)。
そして最後に、Geminiの精度をClaude並みに、劇的にアップさせる魔法のような言葉があります。それは
「ファクト」
です。ファクト(真実)を示せ、ファクトを交えて、と一言を交えるとAIが完全にエンターテインメントモードをやめるようになります。