[{"data":1,"prerenderedAt":740},["ShallowReactive",2],{"/en-us/blog/tags/careers":3,"navigation-ja-jp":19,"banner-ja-jp":435,"footer-ja-jp":448,"footer-source-/en-us/blog/tags/careers/":658,"careers-tag-page-ja-jp":661},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"content":8,"config":10,"_id":12,"_type":13,"title":14,"_source":15,"_file":16,"_stem":17,"_extension":18},"/en-us/blog/tags/careers","tags",false,"",{"tag":9,"tagSlug":9},"careers",{"template":11},"BlogTag","content:en-us:blog:tags:careers.yml","yaml","Careers","content","en-us/blog/tags/careers.yml","en-us/blog/tags/careers","yml",{"_path":20,"_dir":21,"_draft":6,"_partial":6,"_locale":7,"data":22,"_id":431,"_type":13,"title":432,"_source":15,"_file":433,"_stem":434,"_extension":18},"/shared/ja-jp/main-navigation","ja-jp",{"logo":23,"freeTrial":28,"sales":33,"login":38,"items":43,"search":375,"minimal":409,"duo":422},{"config":24},{"href":25,"dataGaName":26,"dataGaLocation":27},"/ja-jp/","gitlab logo","header",{"text":29,"config":30},"無料トライアルを開始",{"href":31,"dataGaName":32,"dataGaLocation":27},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com&glm_content=default-saas-trial/","free trial",{"text":34,"config":35},"お問い合わせ",{"href":36,"dataGaName":37,"dataGaLocation":27},"/ja-jp/sales/","sales",{"text":39,"config":40},"サインイン",{"href":41,"dataGaName":42,"dataGaLocation":27},"https://gitlab.com/users/sign_in/","sign in",[44,88,186,191,297,357],{"text":45,"config":46,"cards":48,"footer":71},"プラットフォーム",{"dataNavLevelOne":47},"platform",[49,55,63],{"title":45,"description":50,"link":51},"最も包括的かつAIで強化されたDevSecOpsプラットフォーム",{"text":52,"config":53},"プラットフォームを詳しく見る",{"href":54,"dataGaName":47,"dataGaLocation":27},"/ja-jp/platform/",{"title":56,"description":57,"link":58},"GitLab Duo（AI）","開発のすべてのステージでAIを活用し、ソフトウェアをより迅速にビルド",{"text":59,"config":60},"GitLab Duoのご紹介",{"href":61,"dataGaName":62,"dataGaLocation":27},"/ja-jp/gitlab-duo/","gitlab duo ai",{"title":64,"description":65,"link":66},"GitLabが選ばれる理由","GitLabが大企業に選ばれる理由10選",{"text":67,"config":68},"詳細はこちら",{"href":69,"dataGaName":70,"dataGaLocation":27},"/ja-jp/why-gitlab/","why gitlab",{"title":72,"items":73},"利用を開始：",[74,79,84],{"text":75,"config":76},"プラットフォームエンジニアリング",{"href":77,"dataGaName":78,"dataGaLocation":27},"/ja-jp/solutions/platform-engineering/","platform engineering",{"text":80,"config":81},"開発者の経験",{"href":82,"dataGaName":83,"dataGaLocation":27},"/ja-jp/developer-experience/","Developer experience",{"text":85,"config":86},"MLOps",{"href":87,"dataGaName":85,"dataGaLocation":27},"/ja-jp/topics/devops/the-role-of-ai-in-devops/",{"text":89,"left":90,"config":91,"link":93,"lists":97,"footer":168},"製品",true,{"dataNavLevelOne":92},"solutions",{"text":94,"config":95},"すべてのソリューションを表示",{"href":96,"dataGaName":92,"dataGaLocation":27},"/ja-jp/solutions/",[98,124,146],{"title":99,"description":100,"link":101,"items":106},"自動化","CI/CDと自動化でデプロイを加速",{"config":102},{"icon":103,"href":104,"dataGaName":105,"dataGaLocation":27},"AutomatedCodeAlt","/ja-jp/solutions/delivery-automation/","automated software delivery",[107,111,115,120],{"text":108,"config":109},"CI/CD",{"href":110,"dataGaLocation":27,"dataGaName":108},"/ja-jp/solutions/continuous-integration/",{"text":112,"config":113},"AIアシストによる開発",{"href":61,"dataGaLocation":27,"dataGaName":114},"AI assisted development",{"text":116,"config":117},"ソースコード管理",{"href":118,"dataGaLocation":27,"dataGaName":119},"/ja-jp/solutions/source-code-management/","Source Code Management",{"text":121,"config":122},"自動化されたソフトウェアデリバリー",{"href":104,"dataGaLocation":27,"dataGaName":123},"Automated software delivery",{"title":125,"description":126,"link":127,"items":132},"セキュリティ","セキュリティを損なうことなくコードをより迅速に完成",{"config":128},{"href":129,"dataGaName":130,"dataGaLocation":27,"icon":131},"/ja-jp/solutions/application-security-testing/","security and compliance","ShieldCheckLight",[133,137,142],{"text":134,"config":135},"Application Security Testing",{"href":129,"dataGaName":136,"dataGaLocation":27},"Application security testing",{"text":138,"config":139},"ソフトウェアサプライチェーンの安全性",{"href":140,"dataGaLocation":27,"dataGaName":141},"/ja-jp/solutions/supply-chain/","Software supply chain security",{"text":143,"config":144},"Software Compliance",{"href":145,"dataGaName":143,"dataGaLocation":27},"/ja-jp/solutions/software-compliance/",{"title":147,"link":148,"items":153},"測定",{"config":149},{"icon":150,"href":151,"dataGaName":152,"dataGaLocation":27},"DigitalTransformation","/ja-jp/solutions/visibility-measurement/","visibility and measurement",[154,158,163],{"text":155,"config":156},"可視性と測定",{"href":151,"dataGaLocation":27,"dataGaName":157},"Visibility and Measurement",{"text":159,"config":160},"バリューストリーム管理",{"href":161,"dataGaLocation":27,"dataGaName":162},"/ja-jp/solutions/value-stream-management/","Value Stream Management",{"text":164,"config":165},"分析とインサイト",{"href":166,"dataGaLocation":27,"dataGaName":167},"/ja-jp/solutions/analytics-and-insights/","Analytics and insights",{"title":169,"items":170},"GitLabが活躍する場所",[171,176,181],{"text":172,"config":173},"Enterprise",{"href":174,"dataGaLocation":27,"dataGaName":175},"/ja-jp/enterprise/","enterprise",{"text":177,"config":178},"スモールビジネス",{"href":179,"dataGaLocation":27,"dataGaName":180},"/ja-jp/small-business/","small business",{"text":182,"config":183},"公共機関",{"href":184,"dataGaLocation":27,"dataGaName":185},"/ja-jp/solutions/public-sector/","public sector",{"text":187,"config":188},"価格",{"href":189,"dataGaName":190,"dataGaLocation":27,"dataNavLevelOne":190},"/ja-jp/pricing/","pricing",{"text":192,"config":193,"link":195,"lists":199,"feature":284},"関連リソース",{"dataNavLevelOne":194},"resources",{"text":196,"config":197},"すべてのリソースを表示",{"href":198,"dataGaName":194,"dataGaLocation":27},"/ja-jp/resources/",[200,233,256],{"title":201,"items":202},"はじめに",[203,208,213,218,223,228],{"text":204,"config":205},"インストール",{"href":206,"dataGaName":207,"dataGaLocation":27},"/ja-jp/install/","install",{"text":209,"config":210},"クイックスタートガイド",{"href":211,"dataGaName":212,"dataGaLocation":27},"/ja-jp/get-started/","quick setup checklists",{"text":214,"config":215},"学ぶ",{"href":216,"dataGaLocation":27,"dataGaName":217},"https://university.gitlab.com/","learn",{"text":219,"config":220},"製品ドキュメント",{"href":221,"dataGaName":222,"dataGaLocation":27},"https://docs.gitlab.com/","product documentation",{"text":224,"config":225},"ベストプラクティスビデオ",{"href":226,"dataGaName":227,"dataGaLocation":27},"/ja-jp/getting-started-videos/","best practice videos",{"text":229,"config":230},"インテグレーション",{"href":231,"dataGaName":232,"dataGaLocation":27},"/ja-jp/integrations/","integrations",{"title":234,"items":235},"検索する",[236,241,246,251],{"text":237,"config":238},"お客様成功事例",{"href":239,"dataGaName":240,"dataGaLocation":27},"/ja-jp/customers/","customer success stories",{"text":242,"config":243},"ブログ",{"href":244,"dataGaName":245,"dataGaLocation":27},"/ja-jp/blog/","blog",{"text":247,"config":248},"リモート",{"href":249,"dataGaName":250,"dataGaLocation":27},"https://handbook.gitlab.com/handbook/company/culture/all-remote/","remote",{"text":252,"config":253},"TeamOps",{"href":254,"dataGaName":255,"dataGaLocation":27},"/ja-jp/teamops/","teamops",{"title":257,"items":258},"つなげる",[259,264,269,274,279],{"text":260,"config":261},"GitLabサービス",{"href":262,"dataGaName":263,"dataGaLocation":27},"/ja-jp/services/","services",{"text":265,"config":266},"コミュニティ",{"href":267,"dataGaName":268,"dataGaLocation":27},"/community/","community",{"text":270,"config":271},"フォーラム",{"href":272,"dataGaName":273,"dataGaLocation":27},"https://forum.gitlab.com/","forum",{"text":275,"config":276},"イベント",{"href":277,"dataGaName":278,"dataGaLocation":27},"/events/","events",{"text":280,"config":281},"パートナー",{"href":282,"dataGaName":283,"dataGaLocation":27},"/ja-jp/partners/","partners",{"backgroundColor":285,"textColor":286,"text":287,"image":288,"link":292},"#2f2a6b","#fff","ソフトウェア開発の未来への洞察",{"altText":289,"config":290},"ソースプロモカード",{"src":291},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758208064/dzl0dbift9xdizyelkk4.svg",{"text":293,"config":294},"最新情報を読む",{"href":295,"dataGaName":296,"dataGaLocation":27},"/ja-jp/the-source/","the source",{"text":298,"config":299,"lists":301},"会社情報",{"dataNavLevelOne":300},"company",[302],{"items":303},[304,309,315,317,322,327,332,337,342,347,352],{"text":305,"config":306},"GitLabについて",{"href":307,"dataGaName":308,"dataGaLocation":27},"/ja-jp/company/","about",{"text":310,"config":311,"footerGa":314},"採用情報",{"href":312,"dataGaName":313,"dataGaLocation":27},"/jobs/","jobs",{"dataGaName":313},{"text":275,"config":316},{"href":277,"dataGaName":278,"dataGaLocation":27},{"text":318,"config":319},"経営陣",{"href":320,"dataGaName":321,"dataGaLocation":27},"/company/team/e-group/","leadership",{"text":323,"config":324},"チーム",{"href":325,"dataGaName":326,"dataGaLocation":27},"/company/team/","team",{"text":328,"config":329},"ハンドブック",{"href":330,"dataGaName":331,"dataGaLocation":27},"https://handbook.gitlab.com/","handbook",{"text":333,"config":334},"投資家向け情報",{"href":335,"dataGaName":336,"dataGaLocation":27},"https://ir.gitlab.com/","investor relations",{"text":338,"config":339},"トラストセンター",{"href":340,"dataGaName":341,"dataGaLocation":27},"/ja-jp/security/","trust center",{"text":343,"config":344},"AI Transparency Center",{"href":345,"dataGaName":346,"dataGaLocation":27},"/ja-jp/ai-transparency-center/","ai transparency center",{"text":348,"config":349},"ニュースレター",{"href":350,"dataGaName":351,"dataGaLocation":27},"/company/contact/","newsletter",{"text":353,"config":354},"プレス",{"href":355,"dataGaName":356,"dataGaLocation":27},"/press/","press",{"text":34,"config":358,"lists":359},{"dataNavLevelOne":300},[360],{"items":361},[362,365,370],{"text":34,"config":363},{"href":36,"dataGaName":364,"dataGaLocation":27},"talk to sales",{"text":366,"config":367},"サポートを受ける",{"href":368,"dataGaName":369,"dataGaLocation":27},"/support/","get help",{"text":371,"config":372},"カスタマーポータル",{"href":373,"dataGaName":374,"dataGaLocation":27},"https://customers.gitlab.com/customers/sign_in/","customer portal",{"close":376,"login":377,"suggestions":384},"閉じる",{"text":378,"link":379},"リポジトリとプロジェクトを検索するには、次にログインします",{"text":380,"config":381},"GitLab.com",{"href":41,"dataGaName":382,"dataGaLocation":383},"search login","search",{"text":385,"default":386},"提案",[387,390,395,397,401,405],{"text":56,"config":388},{"href":61,"dataGaName":389,"dataGaLocation":383},"GitLab Duo (AI)",{"text":391,"config":392},"コード提案（AI）",{"href":393,"dataGaName":394,"dataGaLocation":383},"/ja-jp/solutions/code-suggestions/","Code Suggestions (AI)",{"text":108,"config":396},{"href":110,"dataGaName":108,"dataGaLocation":383},{"text":398,"config":399},"GitLab on AWS",{"href":400,"dataGaName":398,"dataGaLocation":383},"/ja-jp/partners/technology-partners/aws/",{"text":402,"config":403},"GitLab on Google Cloud",{"href":404,"dataGaName":402,"dataGaLocation":383},"/ja-jp/partners/technology-partners/google-cloud-platform/",{"text":406,"config":407},"GitLabを選ぶ理由",{"href":69,"dataGaName":408,"dataGaLocation":383},"Why GitLab?",{"freeTrial":410,"mobileIcon":414,"desktopIcon":419},{"text":29,"config":411},{"href":412,"dataGaName":32,"dataGaLocation":413},"https://gitlab.com/-/trials/new/","nav",{"altText":415,"config":416},"GitLabアイコン",{"src":417,"dataGaName":418,"dataGaLocation":413},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203874/jypbw1jx72aexsoohd7x.svg","gitlab icon",{"altText":415,"config":420},{"src":421,"dataGaName":418,"dataGaLocation":413},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203875/gs4c8p8opsgvflgkswz9.svg",{"freeTrial":423,"mobileIcon":427,"desktopIcon":429},{"text":424,"config":425},"GitLab Duoの詳細について",{"href":61,"dataGaName":426,"dataGaLocation":413},"gitlab duo",{"altText":415,"config":428},{"src":417,"dataGaName":418,"dataGaLocation":413},{"altText":415,"config":430},{"src":421,"dataGaName":418,"dataGaLocation":413},"content:shared:ja-jp:main-navigation.yml","Main Navigation","shared/ja-jp/main-navigation.yml","shared/ja-jp/main-navigation",{"_path":436,"_dir":21,"_draft":6,"_partial":6,"_locale":7,"title":437,"button":438,"config":443,"_id":445,"_type":13,"_source":15,"_file":446,"_stem":447,"_extension":18},"/shared/ja-jp/banner","GitLab Duo Agent Platformがパブリックベータ版で利用可能になりました！",{"text":439,"config":440},"ベータ版を試す",{"href":441,"dataGaName":442,"dataGaLocation":27},"/ja-jp/gitlab-duo/agent-platform/","duo banner",{"layout":444},"release","content:shared:ja-jp:banner.yml","shared/ja-jp/banner.yml","shared/ja-jp/banner",{"_path":449,"_dir":21,"_draft":6,"_partial":6,"_locale":7,"data":450,"_id":654,"_type":13,"title":655,"_source":15,"_file":656,"_stem":657,"_extension":18},"/shared/ja-jp/main-footer",{"text":451,"source":452,"edit":458,"contribute":463,"config":468,"items":473,"minimal":646},"GitはSoftware Freedom Conservancyの商標です。当社は「GitLab」をライセンスに基づいて使用しています",{"text":453,"config":454},"ページのソースを表示",{"href":455,"dataGaName":456,"dataGaLocation":457},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/","page source","footer",{"text":459,"config":460},"このページを編集",{"href":461,"dataGaName":462,"dataGaLocation":457},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/content/","web ide",{"text":464,"config":465},"ご協力をお願いします",{"href":466,"dataGaName":467,"dataGaLocation":457},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/CONTRIBUTING.md/","please contribute",{"twitter":469,"facebook":470,"youtube":471,"linkedin":472},"https://twitter.com/gitlab","https://www.facebook.com/gitlab","https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg","https://www.linkedin.com/company/gitlab-com",[474,497,551,583,618],{"title":45,"links":475,"subMenu":480},[476],{"text":477,"config":478},"DevSecOpsプラットフォーム",{"href":54,"dataGaName":479,"dataGaLocation":457},"devsecops platform",[481],{"title":187,"links":482},[483,487,492],{"text":484,"config":485},"プランの表示",{"href":189,"dataGaName":486,"dataGaLocation":457},"view plans",{"text":488,"config":489},"Premiumを選ぶ理由",{"href":490,"dataGaName":491,"dataGaLocation":457},"/ja-jp/pricing/premium/","why premium",{"text":493,"config":494},"Ultimateを選ぶ理由",{"href":495,"dataGaName":496,"dataGaLocation":457},"/ja-jp/pricing/ultimate/","why ultimate",{"title":498,"links":499},"ソリューション",[500,505,508,510,515,520,524,527,530,535,537,539,541,546],{"text":501,"config":502},"デジタルトランスフォーメーション",{"href":503,"dataGaName":504,"dataGaLocation":457},"/ja-jp/topics/digital-transformation/","digital transformation",{"text":506,"config":507},"セキュリティとコンプライアンス",{"href":129,"dataGaName":136,"dataGaLocation":457},{"text":121,"config":509},{"href":104,"dataGaName":105,"dataGaLocation":457},{"text":511,"config":512},"アジャイル開発",{"href":513,"dataGaName":514,"dataGaLocation":457},"/ja-jp/solutions/agile-delivery/","agile delivery",{"text":516,"config":517},"クラウドトランスフォーメーション",{"href":518,"dataGaName":519,"dataGaLocation":457},"/ja-jp/topics/cloud-native/","cloud transformation",{"text":521,"config":522},"SCM",{"href":118,"dataGaName":523,"dataGaLocation":457},"source code management",{"text":108,"config":525},{"href":110,"dataGaName":526,"dataGaLocation":457},"continuous integration & delivery",{"text":159,"config":528},{"href":161,"dataGaName":529,"dataGaLocation":457},"value stream management",{"text":531,"config":532},"GitOps",{"href":533,"dataGaName":534,"dataGaLocation":457},"/ja-jp/solutions/gitops/","gitops",{"text":172,"config":536},{"href":174,"dataGaName":175,"dataGaLocation":457},{"text":177,"config":538},{"href":179,"dataGaName":180,"dataGaLocation":457},{"text":182,"config":540},{"href":184,"dataGaName":185,"dataGaLocation":457},{"text":542,"config":543},"教育",{"href":544,"dataGaName":545,"dataGaLocation":457},"/ja-jp/solutions/education/","education",{"text":547,"config":548},"金融サービス",{"href":549,"dataGaName":550,"dataGaLocation":457},"/ja-jp/solutions/finance/","financial services",{"title":192,"links":552},[553,555,557,559,562,564,567,569,571,573,575,577,579,581],{"text":204,"config":554},{"href":206,"dataGaName":207,"dataGaLocation":457},{"text":209,"config":556},{"href":211,"dataGaName":212,"dataGaLocation":457},{"text":214,"config":558},{"href":216,"dataGaName":217,"dataGaLocation":457},{"text":219,"config":560},{"href":221,"dataGaName":561,"dataGaLocation":457},"docs",{"text":242,"config":563},{"href":244,"dataGaName":245},{"text":565,"config":566},"お客様の成功事例",{"href":239,"dataGaLocation":457},{"text":237,"config":568},{"href":239,"dataGaName":240,"dataGaLocation":457},{"text":247,"config":570},{"href":249,"dataGaName":250,"dataGaLocation":457},{"text":260,"config":572},{"href":262,"dataGaName":263,"dataGaLocation":457},{"text":252,"config":574},{"href":254,"dataGaName":255,"dataGaLocation":457},{"text":265,"config":576},{"href":267,"dataGaName":268,"dataGaLocation":457},{"text":270,"config":578},{"href":272,"dataGaName":273,"dataGaLocation":457},{"text":275,"config":580},{"href":277,"dataGaName":278,"dataGaLocation":457},{"text":280,"config":582},{"href":282,"dataGaName":283,"dataGaLocation":457},{"title":584,"links":585},"Company",[586,588,590,592,594,596,598,602,607,609,611,613],{"text":305,"config":587},{"href":307,"dataGaName":300,"dataGaLocation":457},{"text":310,"config":589},{"href":312,"dataGaName":313,"dataGaLocation":457},{"text":318,"config":591},{"href":320,"dataGaName":321,"dataGaLocation":457},{"text":323,"config":593},{"href":325,"dataGaName":326,"dataGaLocation":457},{"text":328,"config":595},{"href":330,"dataGaName":331,"dataGaLocation":457},{"text":333,"config":597},{"href":335,"dataGaName":336,"dataGaLocation":457},{"text":599,"config":600},"Sustainability",{"href":601,"dataGaName":599,"dataGaLocation":457},"/sustainability/",{"text":603,"config":604},"ダイバーシティ、インクルージョン、ビロンギング（DIB）",{"href":605,"dataGaName":606,"dataGaLocation":457},"/ja-jp/diversity-inclusion-belonging/","Diversity, inclusion and belonging",{"text":338,"config":608},{"href":340,"dataGaName":341,"dataGaLocation":457},{"text":348,"config":610},{"href":350,"dataGaName":351,"dataGaLocation":457},{"text":353,"config":612},{"href":355,"dataGaName":356,"dataGaLocation":457},{"text":614,"config":615},"現代奴隷制の透明性に関する声明",{"href":616,"dataGaName":617,"dataGaLocation":457},"https://handbook.gitlab.com/handbook/legal/modern-slavery-act-transparency-statement/","modern slavery transparency statement",{"title":34,"links":619},[620,622,624,626,631,636,641],{"text":34,"config":621},{"href":36,"dataGaName":37,"dataGaLocation":457},{"text":366,"config":623},{"href":368,"dataGaName":369,"dataGaLocation":457},{"text":371,"config":625},{"href":373,"dataGaName":374,"dataGaLocation":457},{"text":627,"config":628},"ステータス",{"href":629,"dataGaName":630,"dataGaLocation":457},"https://status.gitlab.com/","status",{"text":632,"config":633},"利用規約",{"href":634,"dataGaName":635,"dataGaLocation":457},"/terms/","terms of use",{"text":637,"config":638},"プライバシーに関する声明",{"href":639,"dataGaName":640,"dataGaLocation":457},"/ja-jp/privacy/","privacy statement",{"text":642,"config":643},"Cookieの設定",{"dataGaName":644,"dataGaLocation":457,"id":645,"isOneTrustButton":90},"cookie preferences","ot-sdk-btn",{"items":647},[648,650,652],{"text":632,"config":649},{"href":634,"dataGaName":635,"dataGaLocation":457},{"text":637,"config":651},{"href":639,"dataGaName":640,"dataGaLocation":457},{"text":642,"config":653},{"dataGaName":644,"dataGaLocation":457,"id":645,"isOneTrustButton":90},"content:shared:ja-jp:main-footer.yml","Main Footer","shared/ja-jp/main-footer.yml","shared/ja-jp/main-footer",{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"content":659,"config":660,"_id":12,"_type":13,"title":14,"_source":15,"_file":16,"_stem":17,"_extension":18},{"tag":9,"tagSlug":9},{"template":11},[662,690,716],{"_path":663,"_dir":245,"_draft":6,"_partial":6,"_locale":7,"seo":664,"content":672,"config":683,"_id":686,"_type":13,"title":687,"_source":15,"_file":688,"_stem":689,"_extension":18},"/ja-jp/blog/five-fast-facts-about-docs-as-code-at-gitlab",{"title":665,"description":666,"ogTitle":665,"ogDescription":666,"noIndex":6,"ogImage":667,"ogUrl":668,"ogSiteName":669,"ogType":670,"canonicalUrls":668,"schema":671},"GitLabにおける「Docs as Code」の5つのポイント","この記事では、GitLabのテクニカルライターがDocs-as-Codeワークフローを用いてGitLabをどのように利用しているのかを、5つのポイントに分けてご紹介します。","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749660257/Blog/Hero%20Images/pen.jpg","https://about.gitlab.com/blog/five-fast-facts-about-docs-as-code-at-gitlab","https://about.gitlab.com","article","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"GitLabにおける「Docs as Code」の5つのポイント\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Suzanne Selhorn\"},{\"@type\":\"Person\",\"name\":\"Susan Tacker\"},{\"@type\":\"Person\",\"name\":\"Diana Logan\"}],\n        \"datePublished\": \"2022-10-12\",\n      }",{"title":665,"description":666,"authors":673,"heroImage":667,"date":677,"body":678,"category":679,"tags":680},[674,675,676],"Suzanne Selhorn","Susan Tacker","Diana Logan","2022-10-12","\n\nGitLabでは、「Docs-as-Code」ワークフローを使うことで、単一のプラットフォームとしてGitLabを使ってGitLabのドキュメントを作成・管理しています。少しわかりづらいでしょうか？\n\nGitLabのテクニカルライティングチームは、GitLabを使って[GitLabドキュメント](https://docs.gitlab.com/)の計画、作成、レビュー、編集、公開までを一貫して行っています。Docs-as-Codeワークフローを導入することで、熱意ある少人数の効率的な備えたチームで膨大な量のコンテンツを生み出すことが可能になっています。\n\nDocs-as-Codeに馴染みがない方のために、簡単にご説明します。\n\n[Docs-as-Code](https://idratherbewriting.com/trends/trends-to-follow-or-forget-docs-as-code.html#what-is-docs-as-code)とは、製品ドキュメントの開発や公開を、ソフトウェアコードの開発と同じツールやプロセスを用いて行う手法です。ドキュメントファイルはコードファイルと同じリポジトリで管理され、バージョン管理も可能です。\n\nご自身が所属する組織でGitLabにDocs-as-Codeワークフローを採用できるか気になっている場合は、ぜひこの記事を読み進めてください。GitLabチームが実践している5つの方法をまとめてご紹介します。\n\n## GitLabを使ってGitLabの機能とドキュメントの更新を計画\n\nプロダクトマネージャー、UXデザイナー、エンジニア、品質管理チームは連携して機能の開発計画を立てます。リリースを計画する際に、Kanbanボードを使ったり、サードパーティのツールでイシューを作成したりするチームも多いかと思います。\n\nGitLabでは、エピックとイシュー[イシュー](https://gitlab.com/gitlab-org/technical-writing/-/issues/680)を使って作業計画を立て、[イシューボード](https://gitlab.com/groups/gitlab-org/-/boards/4340643?label_name%5B%5D=Category%3ADocs%20Site)を用いて進捗を管理します。GitLabでは透明性を重視しているため、計画に関するディスカッションも含め、こうした情報には誰もがアクセスできるようになっています。そのため、テクニカルライティングチームは開発の進行状況をいつでも把握できる環境にあります。\n\n![イシューの計画](https://about.gitlab.com/images/blogimages/planning_issue.png)\n\n大規模なドキュメント作業を行う場合、その進捗をGitLabで管理し、変更はGitLabを使用して行い、イシューが完了したらGitLabで完了マークを付けます。1年後に変更の理由を振り返りたい場合、GitLabで検索すれば、誰がどのような理由で変更を加えたのかを確認できます。現在、複数のツールを使って作業している方は、一度にすべてを一元的に管理できる環境を想像してみてください。より迅速かつ効率的に作業を進められると思いませんか？通常ならメールやウェブサイト、Slackを見て回って見失ったディスカッションを探すのに時間を費やしていたかもしれませんが、それがすべてGitLabに集約されているため、無駄な時間を省けます。\n\nもしWikiを愛用していて、不可欠だという場合でもご安心ください。GitLabにはWiki機能も備わっています。\n\n## GitLabを使ってドキュメントのフィードバックをやりとり\n\nライターとして一定の経験がある方なら、コンテンツのレビューを依頼する際の手間をよくご存じでしょう。\n\nGitLabでは、デベロッパーがすべての新機能に関するコンテンツの初稿を作成します。そして、そのコンテンツはコードと同じリポジトリに保存されます。機能に関するドキュメントは、GitLabの開発プロセスにおける「完了の定義（DoD）」の一部です。デベロッパーはその初稿をライターに割り当て、ライターはそれをレビューし、提案を加え、アイデアや必要な編集内容を作成者であるライターに返します。\n\nライター自身もコンテンツの変更を行う場合は、マージリクエスト（MR）を作成します。MRを作成するのがライターであれ、デベロッパーであれ、サポートエンジニアであれ、コミュニティのコントリビューターであれ、誰でもお互いの作業に簡単にコメントできる仕組みになっています。\n\nマージリクエストでは、「提案」ボタンを選択するだけで簡単にコメントできます。1行、または複数行単位でコメントを付けることができ、変更や編集を提案できます。MRを作成した本人は、その変更を簡単に適用したり、別の提案を作成し議論したりできます。他のユーザーを会話に招待するには、コメントにユーザー名を入力します。すると、相手にコメントがGitLabのTo Doアイテムとして表示されます。このようにして、あらゆる変更について議論でき、透明性があり、誰もが参加できる環境が整っています。\n\n![提案](https://about.gitlab.com/images/blogimages/suggestion.png)\n\nドキュメントの内容はMarkdown形式で記述されており、プレーンテキストに似ているため、ファイルのバージョン間の違いを簡単に確認でき、誰がどの変更をコミットしたのかも把握できます。\n\nPDFやWordドキュメント、Googleドキュメントでコメント機能を使ってレビューを行った経験がある方も多いでしょう。このワークフローを試すと、どれだけ効率的に作業が進むかを実感できるはずです。古いバージョンのドキュメントが使われることもなく、誰かのコメントを意図せず削除してしまうようなこともありません。\n\nまた、変更の理由を知りたい場合も、ページの履歴を簡単に確認でき、特定の行について誰が「担当者」であるかもすぐに確認できます。\n\n![担当者の表示](https://about.gitlab.com/images/blogimages/blame.png)\n\n複数のバージョンのPDFドキュメントを管理したり、誰がどの変更を提案したのかを探したりする必要はもうありません。すべての管理がGitLab内で完結するため、手間が省けます。\n\n## GitLabを使ってドキュメントの内容をプレビュー\n\nGitLabでは、ドキュメントサイトのコンテンツをローカルで生成するツールを提供していますが、マージリクエストから直接ドキュメントサイトを簡単に共有することもできます。新しいアイデアを試していて、それを誰かに見せたい場合は、マージリクエストを開き、「アプリレビュー」を生成すると、変更されたドキュメントサイトを公開URLで確認できるようになります。\n\n![アプリレビュー](https://about.gitlab.com/images/blogimages/view_app.png)\n\n変更内容を確認でき、イテレーションを行うことも、そのままコミットすることも可能です。次にこれに関連する、GitLabの便利な機能をもうひとつご紹介します。\n\n## GitLabを使ってすべてのコンテンツの変更をテスト\n\nドキュメント内のリンクをテストしたり、スペルや文法のルールを確認したりするために、サードパーティーのツールが使っている方も多いかと思います。\n\nGitLabでは、Nanoc（リンクのテスト用）やVale（スペル・文法の確認用）などのサードパーティツールを使用していますが、これらのツールもGitLabに統合でき、ライターのワークフローにも組み込むことができます。\n\n各ライターは、自身のローカル環境でこれらのツールを使用し、ドキュメントの読解レベルや文法修正などをすべて確認できます。これらのツールを持っていないコントリビューターには、パイプラインで自動的にテストを実行し、すべてのコミットに対して結果を表示する仕組みを導入しています。\n\n![Lintエラー](https://about.gitlab.com/images/blogimages/lint_error_2.png)\n\nデベロッパーとして、文章の専門知識に自信がない場合、マージリクエストのパイプラインで重要な文法やブランディングルールに従っていないために処理が進まないことがあります。GitLabでは、さまざまなルールを定め、それぞれに優先順位を付けています。そのため、[スタイルガイド](https://docs.gitlab.com/ee/development/documentation/styleguide/)や[単語リスト](https://docs.gitlab.com/ee/development/documentation/styleguide/word_list.html)を用意しているだけでなく、テストを実行してコンテンツがそうしたルールに沿ったものになっているかを確認しています。\n\n## GitLabを使ってHTML出力を生成し、その出力をGitLab Pagesでホスト\n\nGitLabのCI/CDパイプラインは、Markdown形式のコンテンツをHTMLに変換してコンパイルします。その後、出力内容をGitLab Pagesを介して、[docs.gitlab.com](http://docs.gitlab.com)にホストします。\n\n![パイプライン](https://about.gitlab.com/images/blogimages/pipeline2.png)\n\nパイプラインによって出力を生成することで、必要なときにいつでもドキュメントサイトを更新できます。製品は月に一度リリースされますが、ドキュメントサイトは1時間ごとに更新されます。そのため、docs.gitlab.comでは常に最新の情報が提供されており、時にはリリース前の情報も公開されます。開発計画や実装に関するイシューはGitLabの透明性の方針に基づき公開されているため、機能の事前発表を行うことに問題はありません。\n\nこのように、GitLabが「Docs-as-Code」ワークフローを重視する理由は数多くあります。最初はドキュメント作成を1つのツールで完結させることに慣れずに戸惑うかもしれませんが、GitLabはライター全員のワークフローを最初から最後までサポートしており、長年の実績がその効果を証明しています。\n\nGitLabでのテクニカルライティングにおける「Docs-as-Code」ワークフローについて詳しくは、次の動画をご視聴ください。\n\n\u003C!-- blank line -->\n\u003Cfigure class=\"video_container\">\n  \u003Ciframe src=\"https://www.youtube.com/embed/ZlabtdA-gZE\" frameborder=\"0\" allowfullscreen=\"true\"> \u003C/iframe>\n\u003C/figure>\n\u003C!-- blank line -->\nオープンソースドキュメントへのコントリビュート方法は、「[ドキュメントの更新方法](https://docs.gitlab.com/ee/development/documentation/workflow.html#how-to-update-the-docs)」でご確認いただけます。ぜひご参加ください。\n","insights",[9,681,682],"contributors","inside GitLab",{"slug":684,"featured":6,"template":685},"five-fast-facts-about-docs-as-code-at-gitlab","BlogPost","content:ja-jp:blog:five-fast-facts-about-docs-as-code-at-gitlab.yml","Five Fast Facts About Docs As Code At Gitlab","ja-jp/blog/five-fast-facts-about-docs-as-code-at-gitlab.yml","ja-jp/blog/five-fast-facts-about-docs-as-code-at-gitlab",{"_path":691,"_dir":245,"_draft":6,"_partial":6,"_locale":7,"seo":692,"content":698,"config":710,"_id":712,"_type":13,"title":713,"_source":15,"_file":714,"_stem":715,"_extension":18},"/ja-jp/blog/learn-advanced-rust-programming-with-a-little-help-from-ai-code-suggestions",{"title":693,"description":694,"ogTitle":693,"ogDescription":694,"noIndex":6,"ogImage":695,"ogUrl":696,"ogSiteName":669,"ogType":670,"canonicalUrls":696,"schema":697},"AIを活用して学ぶ、Rustの高度なプログラミング","このガイド付きチュートリアルでは、AIを搭載したGitLab Duoのコード提案を活用しながら、Rustの高度なプログラミングを学ぶことができます。","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749662439/Blog/Hero%20Images/codewithheart.png","https://about.gitlab.com/blog/learn-advanced-rust-programming-with-a-little-help-from-ai-code-suggestions","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"AIを活用して学ぶ、Rustの高度なプログラミング\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Michael Friedrich\"}],\n        \"datePublished\": \"2023-10-12\",\n      }",{"title":693,"description":694,"authors":699,"heroImage":695,"date":701,"body":702,"category":703,"tags":704,"updatedDate":709},[700],"Michael Friedrich","2023-10-12","20年以上前に新しいプログラミング言語を学び始めたとき、私たちは6枚のCD-ROMからインストールしたVisual Studio\n6のMSDNライブラリにアクセスしていました。ペンと紙でアルゴリズムを記録し、設計パターンの本を読み漁り、MSDNで正しい型を調べていましたが、こうした作業に時間がかかることが多々ありました。しかし、リモートコラボレーションや人工知能（AI）の時代が到来し、プログラミング言語の学び方は根本的に変わりました。今では[リモート開発環境](https://about.gitlab.com/blog/quick-start-guide-for-gitlab-workspaces/)をすばやく立ち上げて画面を共有し、グループでのプログラミングセッションを行えるようになりました。[GitLab\nDuoのコード提案](https://about.gitlab.com/ja-jp/gitlab-duo/)を使用すれば、AIというインテリジェントなパートナーにいつでも頼ることができます。コード提案機能は、ユーザーのプログラミングのスタイルと経験に基づいて学習します。この機能は、インプットとコンテキストさえあれば、最も効率的な提案を提供してくれるのです。\n\n\nこのチュートリアルでは、[入門編のブログ記事](/blog/learning-rust-with-a-little-help-from-ai-code-suggestions-getting-started/)（英語）からさらに一歩踏み込み、シンプルなフィードリーダーアプリケーションの設計と作成に取り組みます。\n\n\n- 準備\n    - コード提案\n- Rustの学習の継続\n    - 「Hello, Reader!」アプリ\n    - プロジェクトの初期化\n    - RSSフィードURLの定義\n- モジュール\n    - main() 関数によるモジュール関数の呼び出し\n- クレート\n    - feed-rs：XMLフィードの解析\n- ランタイム設定：プログラム引数\n    - ユーザー入力のエラーハンドリング\n- 永続性とデータ保存\n\n- 最適化\n    - 非同期実行\n    - スレッドの生成\n    - 関数スコープ、スレッド、クロージャ\n- フィードのXMLの解析およびオブジェクト型への変換\n    - 汎用的なフィードデータ型のマッピング\n    - Option::unwrap()によるエラーハンドリング\n- ベンチマーク\n    - 逐次実行と並列実行のベンチマークの比較\n    - Rustのキャッシュを使用したCI/CD\n- 次のステップ\n    - 非同期学習の演習\n    - フィードバックの共有\n\n## 準備\n\nソースコードを参照する前に、[VS\nCode](/blog/learning-rust-with-a-little-help-from-ai-code-suggestions-getting-started/#vs-code)と[Rustの開発環境](/blog/learning-rust-with-a-little-help-from-ai-code-suggestions-getting-started/#development-environment-for-rust)をセットアップしてください。\n\n\n### コード提案\n\n実際に提案機能を検証する前に、まずはこの機能の使い方を理解しましょう。GitLab\nDuoのコード提案は、特定のキーボードショートカットを必要とせず、キーを入力するだけで使用できます。たとえば、コード提案を受け入れるには、`Tab`\nキーを押します。また、新しく作成したコードの方が、既存のコードをリファクタリングしたものよりもエラー発生率が低くなる点も覚えておきましょう。AIは非決定的であるため、一度削除したコード提案は再び同じ形で提示されない可能性があります。コード提案は現在ベータ版であり、GitLabは、同機能が生成するコンテンツの全体的な精度向上に取り組んでいます。\n\n\n**ヒント**：コード提案の最新リリースでは、複数行の指示文に対応しています。ニーズに合わせて指示文を調整することで、よりよい提案を得ることができます。\n\n\n```rust\n    // Create a function that iterates over the source array\n    // and fetches the data using HTTP from the RSS feed items.\n    // Store the results in a new hash map.\n    // Print the hash map to the terminal.\n```\n\n\nVS Code拡張機能のオーバーレイは、提案を提示する際に表示されます。提案された行を受け入れるには `Tab` キーを使用し、一単語だけ受け入れるには\n`Cmd + 右カーソル` キーを使用します。さらに、三点リーダーメニューからツールバーを常に表示するオプションを選択することも可能です。\n\n\n![VS CodeにおけるGitLab\nDuoの指示文とコード提案のオーバーレイ](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_code_suggestions_options_overlay_keep_toolbar.png){:\n.shadow}\n\n\n## Rustの学習の継続\n\nでは、引き続きRustについて学んでいきましょう。Rustは[コード提案でサポートされている言語](https://docs.gitlab.com/ee/user/project/repository/code_suggestions/)のひとつです。[Rust\nby\nExample](https://doc.rust-lang.org/rust-by-example/)（英語）では、初心者に最適なチュートリアルが提供されており、公式の[Rust\nBook](https://doc.rust-lang.org/book/)（英語）を確認しながら進めると効果的です。どちらのリソースもこのブログの執筆の際に参考にしています。\n\n\n### 「Hello, Reader!」アプリ\n\nアプリケーションの作成やRustの学習には、さまざまなアプローチがあります。その中には、既存のRustライブラリ、いわゆる `Crates`\nを利用するものがあり、このブログ記事の後半でも使用します。たとえば、画像を処理して、その結果をファイルに書き込むコマンドラインアプリを作成することができます。また、昔ながらの迷路をクリアしたり、数独を解いたりするアプリケーションを作成するのも楽しいでしょうし、ゲーム開発を行うこともできます。たとえば、[Hands-on\nRust](https://hands-on-rust.com/)（英語）というガイドブックでは、ダンジョンクローラー（迷宮探検）ゲームを作りながら、Rustを体系的に学習できるコースを提供しています。筆者の同僚であるFatima\nSarah Khalidは、[AIを活用してC++でDragon\nRealmの制作](/blog/building-a-text-adventure-using-cplusplus-and-code-suggestions/)（英語）を始めたそうです。ぜひそちらもご覧ください。\n\n\nここで、実際の問題を解決するのに役立つ実用的なユースケースをひとつご紹介します。それは、異なるソースから重要な情報をRSSフィードに集約するというものです。RSSフィードには、セキュリティリリース、ブログ記事、およびソーシャルディスカッションフォーラム（Hacker\nNewsなど）の最新情報が含まれます。アップデートに含まれる特定のキーワードやバージョンを絞り込んで検索したいと考えることがよくあります。こうしたニーズを基に、アプリケーションの要件をリスト化できます。具体的には、次のような要件です。\n\n\n1. HTTPウェブサイト、REST API、RSSフィードなどの異なるソースからデータをフェッチ（取得）する（最初の段階ではRSSフィードを使用）。\n\n1. データを解析する。\n\n1. データをユーザーに提示する、またはディスクに書き込む。\n\n1. パフォーマンスを最適化する。\n\n\nこのブログ記事の学習ステップを完了すると、以下のサンプルアプリケーションの出力が得られます。\n\n\n![VS Codeのターミナルでのcargo\nrunの実行と、形成されたフィードエントリの出力](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_terminal_cargo_run_formatted_output_final.png)\n\n\nアプリケーションはモジュール化されている必要があり、また、将来的にデータ型やフィルター、アクションをトリガーするフックを追加できる基盤も整えておく必要があります。\n\n\n### プロジェクトの初期化\n\nリマインダー：`cargo init` をプロジェクトのルートディレクトリで実行すると、 `main ()`\nエントリポイントを含んだファイル構造が作成されます。これを踏まえ、次のステップでは、Rustのモジュールの作成と使用方法を学びます。\n\n\n`learn-rust-ai-app-reader` という名前のディレクトリを新規作成し、そのディレクトリに移動したら、`cargo init`\nを実行します。このコマンドは、暗黙的に `git init`\nを実行し、新しいGitリポジトリをローカルで初期化します。残りのステップでは、Gitリモートリポジトリのパスを設定していきます。たとえば、`https://gitlab.com/gitlab-de/use-cases/ai/learn-with-ai/learn-rust-ai-app-reader`\nのように設定します（ご自身のネームスペースに合わせてパスを置き換えてください）。[Gitリポジトリをプッシュ](https://about.gitlab.com/ja-jp/blog/mastering-the-basics-of-git-push-tag/)すると、[GitLabで新しいプロジェクトが自動的に作成](https://docs.gitlab.com/ee/user/project/#create-a-new-project-with-git-push)されます。\n\n\n```shell\n\nmkdir learn-rust-ai-app-reader\n\ncd learn-rust-ai-app-reader\n\n\ncargo init\n\n\ngit remote add origin\nhttps://gitlab.com/gitlab-de/use-cases/ai/learn-with-ai/learn-rust-ai-app-reader.git\n\ngit push --set-upstream origin main\n\n```\n\n\n新しく作成されたディレクトリからVS Codeを開きます。`code` コマンドラインインターフェース（CLI）によって、macOS上で新しいVS\nCodeのウィンドウが起動します。\n\n\n```shell\n\ncode .\n\n```\n\n\n### RSSフィードURLの定義\n\n新しいHashMapを追加して、`src/main.rs` ファイル内の `main()`\n関数にRSSフィードのURLを保存します。複数行の指示コメントを入力することで、GitLab\nDuoのコード提案に対し、[`HashMap`](https://doc.rust-lang.org/stable/std/collections/struct.HashMap.html)\nオブジェクトを作成して、Hacker\nNewsやTechCrunchのデフォルト値で初期化するよう指示できます。注：提案に含まれるURLが正しいことを確認してください。\n\n\n```rust\n\nfn main() {##$_0A$##    // Define RSS feed URLs in the variable\nrss_feeds##$_0A$##    // Use a HashMap##$_0A$##    // Add Hacker News and\nTechCrunch##$_0A$##    // Ensure to use String as type##$_0A$####$_0A$##}\n\n```\n\n\nコードコメントでは以下の点を指示しています。\n\n\n1. 変数名 `rss_feeds`\n\n2. `HashMap` タイプ\n\n3. 3. 初期のseedキー/バリューペア\n\n4. String 型（`to_string ()` 呼び出しで確認可能）\n\n\n考えられるコードの例は次の通りです。\n\n\n```rust\n\nuse std::collections::HashMap;\n\n\nfn main() {##$_0A$##    // Define RSS feed URLs in the variable\nrss_feeds##$_0A$##    // Use a HashMap##$_0A$##    // Add Hacker News and\nTechCrunch##$_0A$##    // Ensure to use String as type##$_0A$##    let\nrss_feeds = HashMap::from([##$_0A$##        (\"Hacker News\".to_string(),\n\"https://news.ycombinator.com/rss\".to_string()),##$_0A$##       \n(\"TechCrunch\".to_string(),\n\"https://techcrunch.com/feed/\".to_string()),##$_0A$##   \n]);##$_0A$####$_0A$##}\n\n```\n\n\n![VS Codeにおける、コード提案を活用したHacker\nNewsとTechCrunchのRSSフィードURLの提案](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_main_array_rss_feed_urls_suggested.png)\n\n\nVS Codeで新しいターミナルを開き（cmd + shift + p で `terminal` を検索）、`cargo build`\nを実行して変更をビルドします。エラーメッセージが表示され、`use std::collections::HashMap;`\nのインポートを追加するよう指示されます。\n\n\n次のステップでは、RSSフィードのURLを使用して操作を行います。[以前のブログ記事](/blog/learning-rust-with-a-little-help-from-ai-code-suggestions-getting-started/)（英語）では、コードを関数に分割する方法を解説しました。今回は、リーダーアプリケーションのコードをよりモジュール化して整理し、Rustのモジュールを使用します。\n\n\n## モジュール\n\n[モジュール](https://doc.rust-lang.org/rust-by-example/mod.html)は、\nコードの整理に役立ちます。また、関数をモジュールのスコープ内に隠し、main()スコープからのアクセスを制限することも可能です。リーダーアプリケーションでは、RSSフィードのコンテンツを取得し、XMLレスポンスを解析したいため、`main()`\nからは、`get_feeds()` 関数のみにアクセスできるようにし、それ以外の機能はモジュール内のみで使用できるように制限します。\n\n\n`src/` ディレクトリに `feed_reader.rs` という名前の新しいファイルを作成します。コード提案に、`feed_reader`\nという名前の公開モジュールと、String HashMapをインプットとして受け取る `get_feeds()`\nという公開関数を作成するよう指示します。重要：[Rustのモジュール構造](https://doc.rust-lang.org/book/ch07-02-defining-modules-to-control-scope-and-privacy.html)に従って、ファイル名とモジュール名を同じにする必要があります。\n\n\n![コード提案：関数と入力型を使った公開モジュールの作成](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_public_module_function_input.png){:\n.shadow}\n\n\nコード提案に入力変数名と型を指定すると、必要な `std::collections::HashMap`\nモジュールが自動的にインポートされます。ヒント：最適な結果を得られるよう、コメントを使って変数の型を調整してみましょう。また、Rustでは関数のパラメータをオブジェクト参照として渡すのがベストプラクティスとされています。以下に例を示します。\n\n\n```rust\n\n// Create public module feed_reader\n\n// Define get_feeds() function which takes rss_feeds as String HashMap\nreference as input\n\npub mod feed_reader {##$_0A$##    use\nstd::collections::HashMap;##$_0A$####$_0A$##    pub fn get_feeds(rss_feeds:\n&HashMap\u003CString, String>) {##$_0A$##        // Do something with the RSS\nfeeds##$_0A$##    }##$_0A$##}\n\n```\n\n\n![コード提案：`get_feeds()`\n関数と提案された入力変数を含む公開モジュール](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_public_module_function_input.png){:\n.shadow}\n\n\n関数内では、コード提案に以下の手順を指示します。\n\n\n1. `// Iterate over the RSS feed URLs` （RSSフィードURLを反復処理する）\n\n2. `// Fetch URL content` （URLコンテンツを取得する）\n\n3. `// Parse XML body` （XMLの本文を解析する）\n\n4. `// Print the result`  (結果を出力する)\n\n\n![コード提案：`get_feeds()`\n関数を含む公開モジュール、ステップ1：イテレート](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_module_function_01_iterate.png){:\n.shadow}\n\n\n![コード提案：`get_feeds()`\n関数を含む公開モジュール、ステップ2：URLコンテンツの取得](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_module_function_02_fetch_content.png){:\n.shadow}\n\n\n![コード提案：`get_feeds()`\n関数を含む公開モジュール、ステップ3：XML本文の解析](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_module_function_03_parse_body.png){:\n.shadow}\n\n\n![コード提案：`get_feeds()`\n関数を含む公開モジュール、ステップ4：結果の出力](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_module_function_04_print_result.png){:\n.shadow}\n\n\n次のコードが提案されます。\n\n\n```rust\n\n// Create public module feed_reader\n\n// Define get_feeds() function which takes rss_feeds as String HashMap\nreference as input\n\npub mod feed_reader {##$_0A$##    use\nstd::collections::HashMap;##$_0A$####$_0A$##    pub fn get_feeds(rss_feeds:\n&HashMap\u003CString, String>) {##$_0A$##        // Iterate over the RSS feed\nURLs##$_0A$##        for (name, url) in rss_feeds {##$_0A$##           \nprintln!(\"{}: {}\", name, url);##$_0A$####$_0A$##            // Fetch URL\ncontent##$_0A$##            let body =\nreqwest::blocking::get(url).unwrap().text().unwrap();##$_0A$####$_0A$##           \n// Parse XML body##$_0A$##            let parsed_body =\nroxmltree::Document::parse(&body).unwrap();##$_0A$####$_0A$##            //\nPrint the result##$_0A$##            println!(\"{:#?}\",\nparsed_body);##$_0A$##        }##$_0A$##    }##$_0A$##}\n\n```\n\n\nここで新しいキーワード\n[`unwrap()`](https://doc.rust-lang.org/rust-by-example/error/option_unwrap.html)が登場します。Rustは\n`null` 値をサポートしておらず、すべての値に対して [`Option`\n型](https://doc.rust-lang.org/rust-by-example/std/option.html)を使用します。たとえば、`Text`\nや `String` といった特定のラップされた型を使用することが確定している場合、`unwrap()`\nメソッドを呼び出してその値を取得できます。ただし、値が `None` の場合、`unwrap()` メソッドはパニックを起こします。\n\n\n**注意**：コード提案は、`// Fetch URL content` のコメント指示に従って、`reqwest::blocking::get`\n関数を参照します。[`reqwest`](https://docs.rs/reqwest/latest/reqwest/)というクレーと名は意図的なものであり、タイポではありません。非同期リクエストとブロッキングリクエストの処理に役立つ、優れた利便性と高レベルのHTTPクライアントの機能を提供します。\n\n\nXMLの本文の解析は難しく、異なる結果が得られることがあります。また、スキーマはRSSフィードURLごとに異なる可能性があります。まずは\n`get_feeds()` 関数を呼び出し、その後でコードの改善に取り組みましょう。\n\n\n### main() 関数によるモジュール関数の呼び出し\n\n\n現在、main() 関数は `get_feeds()`\n関数を認識していないため、まずそのモジュールをインポートする必要があります。他のプログラミング言語では `include` や `import`\nといったキーワードを目にすることがありますが、Rustのモジュールシステムは異なります。\n\n\nモジュールはパスディレクトリに整理されます。今回の例では、両方のソースファイルが同じディレクトリレベルに存在しています。`feed_reader.rs`\nはクレートとして解釈され、その中に `feed_reader` というモジュールがあり、そのモジュールが `get_feeds()`\n関数を定義しています。\n\n\n```\n\nsrc/\n  main.rs\n  feed_reader.rs\n```\n\n\n`feed_reader.rs` ファイルの `get_feeds()` 関数にアクセスするためには、まず `main.rs`\nのスコープに[モジュールパスを取り込み](https://doc.rust-lang.org/book/ch07-04-bringing-paths-into-scope-with-the-use-keyword.html)、その後フルパスで関数を呼び出します。\n\n\n```rust\n\nmod feed_reader;\n\n\nfn main() {\n\n    feed_reader::feed_reader::get_feeds(&rss_feeds);\n\n```\n\n\nあるいは、`use` キーワードを使って関数のフルパスをインポートし、その後短い関数名で呼び出すこともできます。\n\n\n```rust\n\nmod feed_reader;\n\nuse feed_reader::feed_reader::get_feeds;\n\n\nfn main() {\n\n    get_feeds(&rss_feeds);\n\n```\n\n\n**ヒント**：Rustのモジュールシステムを視覚的によりよく理解するために、[Rustモジュールシステムについてわかりやすく説明したブログ記事](https://www.sheshbabu.com/posts/rust-module-system/)（英語）をお読みください。\n\n\n```diff\n\n\nfn main() {\n    // ...\n\n    // Print feed_reader get_feeds() output\n    println!(\"{}\", feed_reader::get_feeds(&rss_feeds));\n```\n\n\n```rust\n\nuse std::collections::HashMap;\n\n\nmod feed_reader;\n\n// Alternative: Import full function path\n\n//use feed_reader::feed_reader::get_feeds;\n\n\nfn main() {##$_0A$##    // Define RSS feed URLs in the variable\nrss_feeds##$_0A$##    // Use a HashMap##$_0A$##    // Add Hacker News and\nTechCrunch##$_0A$##    // Ensure to use String as type##$_0A$##    let\nrss_feeds = HashMap::from([##$_0A$##        (\"Hacker News\".to_string(),\n\"https://news.ycombinator.com/rss\".to_string()),##$_0A$##       \n(\"TechCrunch\".to_string(),\n\"https://techcrunch.com/feed/\".to_string()),##$_0A$##   \n]);##$_0A$####$_0A$##    // Call get_feeds() from feed_reader\nmodule##$_0A$##   \nfeed_reader::feed_reader::get_feeds(&rss_feeds);##$_0A$##    // Alternative:\nImported full path, use short path here.##$_0A$##   \n//get_feeds(&rss_feeds);##$_0A$##}\n\n```\n\n\nターミナルで `cargo build` を再実行しコードをビルドします。\n\n\n```shell\n\ncargo build\n\n```\n\n\n以下は、HTTPリクエストやXML解析に関する一般的なコードを参照した際に発生する可能性のあるビルドエラーの例です。\n\n\n1. エラー： `could not find blocking in reqwest`\n\n解決策：`Config.toml` ファイルで `reqwest` クレートの `blocking` 機能を有効にします（`reqwest = {\nversion = \"0.11.20\", features = [\"blocking\"] }`）\n\n2. エラー：`failed to resolve: use of undeclared crate or module reqwest`\n\n解決策：`reqwest` クレートを追加します\n\n3. エラー：`failed to resolve: use of undeclared crate or module roxmltree`\n\n解決策：`roxmltree` クレートを追加します\n\n\n```shell\n\nvim Config.toml\n\n\nreqwest = { version = \"0.11.20\", features = [\"blocking\"] }\n\n```\n\n\n```shell\n\ncargo add reqwest\n\ncargo add roxmltree\n\n```\n\n\n**ヒント**：エラーメッセージの文字列を `Rust \u003Cerror message>`\nとしてブラウザで検索し、欠落しているクレートが利用可能であるかどうか確認しましょう。通常、検索結果に「crates.io」が表示され、そこから不足している依存関係を追加できます。\n\n\nビルドが成功したら、`cargo run` でコードを実行し、Hacker NewsのRSSフィードの出力を確認します。\n\n\n![VS Codeのターミナルでcargo runを実行して、Hacker\nNewsのXMLフィードを取得](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_terminal_fetch_rss_feed_output_hacker_news.png){:\n.shadow}\n\n\nXML本文を人間が読める形式に解析するにはどうすればいいでしょうか？次のセクションでは、既存の解決策とRustのクレートがどのように機能するかを説明します。\n\n\n## クレート\n\nRSSフィードは共通のプロトコルと仕様に基づいており、XMLを解析して下位のオブジェクト構造を理解するのは、例えば車輪のように、すでに存在しているものを再び深く掘り下げて再発明するかのようです。。こういったタスクに対するおすすめのアプローチは、過去に同じ問題に直面した人がいないか、また、その問題を解決するためのコードがすでに作られていないかを調べることです。\n\n\nRustでの再利用可能なライブラリコードは\n[`Crates`](https://doc.rust-lang.org/rust-by-example/crates.html)と呼ばれる単位で整理され、パッケージで提供されます。これらはcrates.ioのパッケージレジストリで利用可能です。これらの依存関係をプロジェクトに追加するには、`Config.toml`\nファイルの `[dependencies]` セクションを編集するか、`cargo add \u003Cname>` コマンドを使用します。\n\n\nリーダーアプリケーションでは、[feed-rs\nクレート](https://crates.io/crates/feed-rs)を使用したいため、新しいターミナルを開き、次のコマンドを実行してください。\n\n\n```shell\n\ncargo add feed-rs\n\n```\n\n\n![VS\nCodeのターミナル：クレートを追加し、Config.tomlで確認](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_rust_crate_add_feed-rs_explained.png)\n\n\n### feed-rs：XMLフィードの解析\n\n`src/feed_reader.rs` に移動し、XML本文を解析する部分に対して修正を加えます。コード提案は、`feed-rs` クレートの\n`parser::parse` 関数をどのように呼び出すか理解していますが、ひとつだけ特別な点があります。`feed-rs`\nは文字列をrawバイトとして入力し、自らエンコーディングを判断します。ただし、コメントに指示を追加することで、期待の結果を得ることは可能です。\n\n\n```rust\n            // Parse XML body with feed_rs parser, input in bytes\n            let parsed_body = feed_rs::parser::parse(body.as_bytes()).unwrap();\n```\n\n\n![コード提案：`get_feeds()`\n関数を含む公開モジュール、ステップ5：XMLパーサーをfeed-rsに変更](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_module_function_05_use_feed_rs_to_parse.png){:\n.shadow}\n\n\n`feed-rs` を使用するメリットは即座に可視化できるものでなく、`cargo run`\nで出力を確認するとその効果が明らかになります。すべてのキーと値がそれぞれのRustオブジェクト型にマッピングされ、さらに高度な処理に利用できるようになります。\n\n\n![VS Codeのターミナル、cargo runを実行してHacker\nNewsのXMLフィードを取得](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_terminal_fetch_rss_feed_output_hacker_news_feed_rs.png){:\n.shadow}\n\n\n## ランタイム設定：プログラム引数\n\nここまで、コンパイル時にバイナリに埋め込まれ、ハードコードされたRSSフィードの値を使用してプログラムを実行してきました。次のステップでは、実行時にRSSフィードを設定できるようにします。\n\n\nRustでは、標準miscライブラリに[プログラム引数](https://doc.rust-lang.org/rust-by-example/std_misc/arg.html)を処理するための機能が用意されています。[プログラム引数を解析](https://doc.rust-lang.org/rust-by-example/std_misc/arg/matching.html)することで、高度なプログラム引数パーサー（たとえば[clap](https://docs.rs/clap/latest/clap/)クレート）を使用したり、プログラムパラメータを構成ファイルやフォーマット（[TOML](https://toml.io/en/)やYAML）に移したりするよりも、簡単かつ効率的に学習を進めることができます。実際にいくつかの方法を試した結果、学習効果を最大限に高めるには、この方法が最適であると判断しましたが、唯一の方法ではありません。他の方法でRSSフィードの設定を試してみる価値もあります。\n\n\n単純な解決策としては、コマンドパラメータを `\"name,url\"` の文字列ペアとして渡してから、`,`\nで分割して名前とURLの値を抽出します。コード提案に、これらの操作を実行して新しい値で `rss_feeds`\nハッシュマップを拡張するようコメントで指示します。変数が変更可能でない可能性があるため、`let rss_feeds` を `let mut\nrss_feeds` に変更する必要がある点にご注意ください。\n\n\n`src/main.rs` に移動し、`rss_feeds` 変数の後に次のコードを `main()`\n関数の追加します。まずはコメントでプログラム引数を定義し、提案されたコードスニペットを確認します。\n\n\n```rust\n    // Program args, format \"name,url\"\n    // Split value by , into name, url and add to rss_feeds\n```\n\n\n![プログラム引数に関するコード提案、および rss_feeds\n変数のために名前とURLの値を分割するコード提案](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_program_args_boring_solution.png){:\n.shadow}\n\n\nコード全体の例は次のようになります。\n\n\n```rust\n\nfn main() {##$_0A$##    // Define RSS feed URLs in the variable\nrss_feeds##$_0A$##    // Use a HashMap##$_0A$##    // Add Hacker News and\nTechCrunch##$_0A$##    // Ensure to use String as type##$_0A$##    let mut\nrss_feeds = HashMap::from([##$_0A$##        (\"Hacker News\".to_string(),\n\"https://news.ycombinator.com/rss\".to_string()),##$_0A$##       \n(\"TechCrunch\".to_string(),\n\"https://techcrunch.com/feed/\".to_string()),##$_0A$##   \n]);##$_0A$####$_0A$##    // Program args, format \"name,url\"##$_0A$##    //\nSplit value by , into name, url and add to rss_feeds##$_0A$##    for arg in\nstd::env::args().skip(1) {##$_0A$##        let mut split =\narg.split(\",\");##$_0A$##        let name =\nsplit.next().unwrap();##$_0A$##        let url =\nsplit.next().unwrap();##$_0A$##        rss_feeds.insert(name.to_string(),\nurl.to_string());##$_0A$##    }##$_0A$####$_0A$##    // Call get_feeds()\nfrom feed_reader module##$_0A$##   \nfeed_reader::feed_reader::get_feeds(&rss_feeds);##$_0A$##    // Alternative:\nImported full path, use short path here.##$_0A$##   \n//get_feeds(&rss_feeds);##$_0A$##}\n\n```\n\n\nプログラムの引数を `cargo run` コマンドに直接渡すことができます。その際は引数の前に `--`をつけます。\nすべての引数をダブルクォートで囲み、名前の後にカンマを付けてRSSフィードのURLを引数として渡します。引数は空白で区切ります。\n\n\n```\n\ncargo build\n\n\ncargo run -- \"GitLab Blog,https://about.gitlab.com/atom.xml\"\n\"CNCF,https://www.cncf.io/feed/\"\n\n```\n\n\n![VS\nCodeターミナル、GitLabブログのRSSフィード出力例](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_terminal_gitlab_blog_rss_feed_example.png){:\n.shadow}\n\n\n### ユーザー入力のエラーハンドリング\n\n提供されたユーザー入力がプログラムで想定される内容と異なる場合、[エラーを発生](https://doc.rust-lang.org/rust-by-example/error.html)させ、呼び出し元がプログラム引数を修正できるようにする必要があります。たとえば、不正なURL形式が渡された場合、それをランタイムエラーとして処理する必要があります。コード提案に対し、URLが無効な場合はエラーをスローするようコメントで指示します。\n\n\n```rust\n    // Ensure that URL contains a valid format, otherwise throw an error\n```\n\n\nひとつの解決策として、`url` 変数が `http://` または `https://` で始まっているかを確認し、そうでなければ [panic!\nマクロ](https://doc.rust-lang.org/rust-by-example/std/panic.html)を使ってエラーをスローする方法があります。コード全体の例は次のようになります。\n\n\n```rust\n    // Program args, format \"name,url\"\n    // Split value by , into name, url and add to rss_feeds\n    for arg in std::env::args().skip(1) {\n        let mut split = arg.split(\",\");\n        let name = split.next().unwrap();\n        let url = split.next().unwrap();\n\n        // Ensure that URL contains a valid format, otherwise throw an error\n        if !url.starts_with(\"http://\") && !url.starts_with(\"https://\") {\n            panic!(\"Invalid URL format: {}\", url);\n        }\n\n        rss_feeds.insert(name.to_string(), url.to_string());\n    }\n```\n\n\n任意のURL文字列から `:` を削除してエラーハンドリングをテストします。`RUST_BACKTRACE=full`\n環境変数を追加すると、`panic()` 呼び出しが発生した際に詳細な出力を取得できます。\n\n\n```\n\nRUST_BACKTRACE=full cargo run -- \"GitLab\nBlog,https://about.gitlab.com/atom.xml\" \"CNCF,https//www.cncf.io/feed/\"\n\n```\n\n\n![間違ったURL形式によるパニックエラーのバックトレースが表示されたVS\nCodeターミナル](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_terminal_url_format_error_panic_backtrace.png){:\n.shadow}\n\n\n## 永続性とデータ保存\n\nフィードデータを保存する単純な解決策は、解析されたデータを新しいファイルに書き出すことです。コード提案に、RSSフィードの名前と現在のISO日付を含むパターンでファイルを保存するよう指示します。\n\n\n```rust\n    // Parse XML body with feed_rs parser, input in bytes\n    let parsed_body = feed_rs::parser::parse(body.as_bytes()).unwrap();\n\n    // Print the result\n    println!(\"{:#?}\", parsed_body);\n\n    // Dump the parsed body to a file, as name-current-iso-date.xml\n    let now = chrono::offset::Local::now();\n    let filename = format!(\"{}-{}.xml\", name, now.format(\"%Y-%m-%d\"));\n    let mut file = std::fs::File::create(filename).unwrap();\n    file.write_all(body.as_bytes()).unwrap();\n```\n\n\nこの処理の一例として、[chronoクレート](https://crates.io/crates/chrono)を使用する方法があります。`cargo\nadd chrono` を実行して追加し、その後 `cargo build` と `cargo run` を再度実行します。\n\n\nファイルは `cargo run` が実行されたディレクトリに保存されます。バイナリを `target/debug/`\nディレクトリで直接実行している場合、すべてのファイルはそのディレクトリにダンプされます。\n\n\n![CNCFのRSSフィード内容を含むファイルがディスクに保存された状態のVS Code](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_cncf_rss_feed_saved_on_disk.png)\n\n\n## 最適化\n\n`rss_feeds`\n変数内のエントリは逐次実行されています。もし100件以上のURLがリストに設定されている場合、データの取得と処理にはかなりの時間がかかるでしょう。複数のフェッチリクエストを並行して実行できたらどうでしょうか？\n\n\n### 非同期実行\n\nRustでは、[スレッド](https://doc.rust-lang.org/book/ch16-01-threads.html)を使用した非同期実行が可能です。\n\n\n最も簡単な解決策は、各RSSフィードURLごとにスレッドを生成することです。最適化戦略については後のセクションで説明します。並行実行を開始する前に、\n`time` コマンドの前に`cargo run` をつけて、逐次実行コードの実行時間を測定しましょう。\n\n\n```\n\ntime cargo run -- \"GitLab Blog,https://about.gitlab.com/atom.xml\"\n\"CNCF,https://www.cncf.io/feed/\"\n\n\n0.21s user 0.08s system 10% cpu 2.898 total\n\n```\n\n\nなお、この演習では、手動のコーディング作業が多くなる場合があります。並列実行の影響をより効果的に比較するには、逐次実行の動作状態を新しいGitコミットとブランチ\n`sequential-exec` に保存してください。\n\n```shell\n\ngit commit -avm \"Sequential execution working\"\n\ngit checkout -b sequential-exec\n\ngit push -u origin sequential-exec\n\n\ngit checkout main\n\n```\n\n\n### スレッドの生成\n\n`src/feed_reader.rs` を開き、`get_feeds()`\n関数をリファクタリングします。まず、現在の状態をGitコミットで保存し、その後、関数のスコープ内の内容を削除します。次に、コード提案への指示と以下のコードコメントを入力します。\n\n\n1. `// Store threads in\nvector`：スレッドのハンドルをベクターに格納し、関数呼び出しの最後にスレッドが終了するまで待機できるようにします。\n\n2. `// Loop over rss_feeds and spawn\nthreads`：すべてのRSSフィードを反復処理するためのコードを作成し、新しいスレッドを作成します。\n\n\n`thread` モジュールと `time` モジュールを扱うには、次の `use` 文を追加します。\n\n\n```rust\n    use std::thread;\n    use std::time::Duration;\n```\n\n\nその後、forループを閉じるまでコードを書き進めます。コード提案は、自動的にスレッドハンドルを `threads`\nベクター変数に追加し、関数の最後でスレッドを結合（join）するよう提案します。\n\n\n```rust\n    pub fn get_feeds(rss_feeds: &HashMap\u003CString, String>) {\n\n        // Store threads in vector\n        let mut threads: Vec\u003Cthread::JoinHandle\u003C()>> = Vec::new();\n\n        // Loop over rss_feeds and spawn threads\n        for (name, url) in rss_feeds {\n            let thread_name = name.clone();\n            let thread_url = url.clone();\n            let thread = thread::spawn(move || {\n\n            });\n            threads.push(thread);\n        }\n\n        // Join threads\n        for thread in threads {\n            thread.join().unwrap();\n        }\n    }\n```\n\n\n次に、`thread` クレートを追加し、もう一度コードをビルドし実行します。\n\n\n```shell\n\ncargo add thread\n\n\ncargo build\n\n\ncargo run -- \"GitLab Blog,https://about.gitlab.com/atom.xml\"\n\"CNCF,https://www.cncf.io/feed/\"\n\n```\n\n\nこの段階では、データの処理や出力は行われていません。次のステップに移る前に、ここで新たに導入されたキーワードについて解説します。\n\n\n### 関数スコープ、スレッド、クロージャ\n\n提案されるコードには新しいキーワードやデザインパターンが含まれることもあるため、これらについて理解しておくことが重要になります。スレッドハンドルは\n`thread::JoinHandle`\nという型で、スレッドが終わるのを待機するために使用します（[join()](https://doc.rust-lang.org/book/ch16-01-threads.html#waiting-for-all-threads-to-finish-using-join-handles)メソッドを使います）。\n\n\n`thread::spawn()`\nは新しいスレッドを生成し、このスレッド内では、関数オブジェクトを渡すことができます。この場合、[クロージャ](https://doc.rust-lang.org/book/ch13-01-closures.html)式が無名関数として渡されます。また、クロージャ入力は\n`||` 構文を使用して渡されますが、この際には[`move`\nクロージャ](https://doc.rust-lang.org/book/ch16-01-threads.html#using-move-closures-with-threads)が関数スコープの変数をスレッドスコープに移動します。これにより、どの変数を新しい関数やクロージャスコープに渡すかを手動で指定する必要がなくなります。\n\n\nただし、これには制限があります。`rss_feeds` は参照 `&` で、`get_feeds()`\n関数の呼び出し元からパラメータとして渡されています。この変数は関数スコープ内でのみ有効です。このエラーを引き起こすには、次のコードスニペットを使用します。\n\n\n```rust\n\npub fn get_feeds(rss_feeds: &HashMap\u003CString, String>) {##$_0A$####$_0A$##   \n// Store threads in vector##$_0A$##    let mut threads:\nVec\u003Cthread::JoinHandle\u003C()>> = Vec::new();##$_0A$####$_0A$##    // Loop over\nrss_feeds and spawn threads##$_0A$##    for (key, value) in rss_feeds\n{##$_0A$##        let thread = thread::spawn(move || {##$_0A$##           \nprintln!(\"{}\", key);##$_0A$##        });##$_0A$##    }##$_0A$##}\n\n```\n\n\n![VS Codeターミナル、参照とスレッドのmoveクロージャに関する変数スコープエラー](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_terminal_cargo_build_error_function_threads_variable_scopes.png){:\n.shadow}\n\n\n`key` 変数は関数スコープ内で作成されていますが、`rss_feeds`\n変数を参照しているため、スレッドスコープに移動することはできません。関数パラメータ `rss_feeds`\nのハッシュマップからアクセスされる値は、`clone()` を使ってローカルコピーを作成する必要があります。\n\n\n![VS\nCodeターミナル、cloneを使用したスレッドの生成](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_thread_spawn_clone.png){:\n.shadow}\n\n\n```rust\n\npub fn get_feeds(rss_feeds: &HashMap\u003CString, String>) {\n\n    // Store threads in vector\n    let mut threads: Vec\u003Cthread::JoinHandle\u003C()>> = Vec::new();\n\n    // Loop over rss_feeds and spawn threads\n    for (name, url) in rss_feeds {\n        let thread_name = name.clone();\n        let thread_url = url.clone();\n        let thread = thread::spawn(move || {\n            // Use thread_name and thread_url as values, see next chapter for instructions.\n```\n\n\n## フィードのXMLの解析およびオブジェクト型への変換\n\n次のステップは、スレッド内のクロージャでRSSフィードの解析手順を繰り返すことです。コード提案の指示とともに以下のコードコメントを追加します。\n\n\n1. `// Parse XML body with feed_rs parser, input in\nbytes`：コード提案に対し、RSSフィードのURLコンテンツを取得し、`feed_rs` クレートの関数で解析したいという要望を伝えます。\n\n2. `// Check feed_type attribute feed_rs::model::FeedType::RSS2 or Atom and\nprint its name`：`feed_type` 属性を\n[`feed_rs::model::FeedType`](https://docs.rs/feed-rs/latest/feed_rs/model/enum.FeedType.html)\nと照合してフィードの種類を抽出します。この場合、コード提案に対して、照合の対象とする正確なEnum値を指示する必要があります。\n\n\n![コード提案に特定のフィードタイプと照合するよう指示](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_feed_rs_type_condition.png){:\n.shadow}\n\n\n```rust\n            // Parse XML body with feed_rs parser, input in bytes\n            let body = reqwest::blocking::get(thread_url).unwrap().bytes().unwrap();\n            let feed = feed_rs::parser::parse(body.as_ref()).unwrap();\n\n            // Check feed_type attribute feed_rs::model::FeedType::RSS2 or Atom and print its name\n            if feed.feed_type == feed_rs::model::FeedType::RSS2 {\n                println!(\"{} is an RSS2 feed\", thread_name);\n            } else if feed.feed_type == feed_rs::model::FeedType::Atom {\n                println!(\"{} is an Atom feed\", thread_name);\n            }\n```\n\n\nプログラムをもう一度ビルドして実行し、出力を確認します。\n\n\n```\n\ntime cargo run -- \"GitLab Blog,https://about.gitlab.com/atom.xml\"\n\"CNCF,https://www.cncf.io/feed/\"\n\n\nCNCF is an RSS2 feed\n\nTechCrunch is an RSS2 feed\n\nGitLab Blog is an Atom feed\n\nHacker News is an RSS2 feed\n\n```\n\n\nフィードURLをブラウザで開くか、以前にダウンロードしたファイルを調べて、この出力を確認します。\n\n\nHacker News はRSS\n2.0をサポートしており、`channel(title,link,description,item(title,link,pubDate,comments))`\nの構造を持っています。TechCrunchとCNCFブログは同様の構造をしています。\n\n```xml\n\n\u003Crss version=\"2.0\">\u003Cchannel>\u003Ctitle>Hacker\nNews\u003C/title>\u003Clink>https://news.ycombinator.com/\u003C/link>\u003Cdescription>Links for\nthe intellectually curious, ranked by\nreaders.\u003C/description>\u003Citem>\u003Ctitle>Writing a debugger from scratch:\nBreakpoints\u003C/title>\u003Clink>https://www.timdbg.com/posts/writing-a-debugger-from-scratch-part-5/\u003C/link>\u003CpubDate>Wed,\n27 Sep 2023 06:31:25\n+0000\u003C/pubDate>\u003Ccomments>https://news.ycombinator.com/item?id=37670938\u003C/comments>\u003Cdescription>\u003C![CDATA[\u003Ca\nhref=\"https://news.ycombinator.com/item?id=37670938\">Comments\u003C/a>]]>\u003C/description>\u003C/item>\u003Citem>\n\n```\n\n\nGitLabブログには[Atom](https://datatracker.ietf.org/doc/html/rfc4287)フィード形式が使用されています。RSSに類似していますが異なる解析ロジックが必要です。\n\n```xml\n\n\u003C?xml version='1.0' encoding='utf-8' ?>\n\n\u003Cfeed xmlns='http://www.w3.org/2005/Atom'>\n\n\u003C!-- / Get release posts -->\n\n\u003C!-- / Get blog posts -->\n\n\u003Ctitle>GitLab\u003C/title>\n\n\u003Cid>https://about.gitlab.com/blog\u003C/id>\n\n\u003Clink href='https://about.gitlab.com/blog/' />\n\n\u003Cupdated>2023-09-26T00:00:00+00:00\u003C/updated>\n\n\u003Cauthor>\n\n\u003Cname>The GitLab Team\u003C/name>\n\n\u003C/author>\n\n\u003Centry>\n\n\u003Ctitle>Atlassian Server ending: Goodbye disjointed toolchain, hello\nDevSecOps platform\u003C/title>\n\n\u003Clink\nhref='https://about.gitlab.com/blog/atlassian-server-ending-move-to-a-single-devsecops-platform/'\nrel='alternate' />\n\n\u003Cid>https://about.gitlab.com/blog/atlassian-server-ending-move-to-a-single-devsecops-platform/\u003C/id>\n\n\u003Cpublished>2023-09-26T00:00:00+00:00\u003C/published>\n\n\u003Cupdated>2023-09-26T00:00:00+00:00\u003C/updated>\n\n\u003Cauthor>\n\n\u003Cname>Dave Steer, Justin Farris\u003C/name>\n\n\u003C/author>\n\n```\n\n\n### 汎用的なフィードデータ型のマッピング\n\n[`roxmltree::Document::parse`](https://docs.rs/roxmltree/latest/roxmltree/struct.Document.html)\nを使用する場合、XMLノードツリーとその特定のタグ名を理解する必要があります。幸い、[`feed_rs::model::Feed`](https://docs.rs/feed-rs/latest/feed_rs/model/struct.Feed.html)\nはRSSとAtomフィードの統合モデルを提供しているため、引き続き `feed_rs` クレートを使用して進めましょう。\n\n\n1. Atom：Feed->Feed、Entry->Entry\n\n2. RSS：Channel->Feed、Item->Entry\n\n\n上記のマッピングに加えて、必要な属性を抽出し、それらのデータ型をマッピングする必要があります。[feed_rs::modelのドキュメント](https://docs.rs/feed-rs/latest/feed_rs/model/index.html)を開き、構造体やそのフィールド、実装について理解しながら進めることをお勧めします。これは、`feed_rs`\nの実装に特有の型変換エラーやコンパイルエラーが発生するのを防ぐためです。\n\n\n[`Feed`](https://docs.rs/feed-rs/latest/feed_rs/model/struct.Feed.html) 構造体は\n`title` を提供しますが、その型は `Option\u003CText>`\nで、値が設定されているか、何もないかのいずれかです。[`Entry`](https://docs.rs/feed-rs/latest/feed_rs/model/struct.Entry.html)\n構造体は以下の情報を提供します。\n\n\n1. `title`：`Option\u003CText>`\n型で、[`Text`](https://docs.rs/feed-rs/latest/feed_rs/model/struct.Text.html)\nには `content` フィールドが `String` 型として含まれています。\n\n2. `published`：`Option\u003CDateTime\u003CUtc>>`\n型で、[`DateTime`](https://docs.rs/chrono/latest/chrono/struct.DateTime.html) は\n[`format()`\nメソッド](https://docs.rs/chrono/latest/chrono/struct.DateTime.html#method.format)を持ちます。\n\n3. `summary`：`Option\u003CText>` \n型で、[`Text`](https://docs.rs/feed-rs/latest/feed_rs/model/struct.Text.html)\nには `content` フィールドが `String` 型として含まれています。\n\n4. `links`：`Vec\u003CLink>`\n型で、[`Link`](https://docs.rs/feed-rs/latest/feed_rs/model/struct.Link.html)\n項目のベクターです。`href` 属性が生のURL文字列を提供します。\n\n\n1から4に従って、フィードエントリから必要なデータを抽出します。繰り返しますが、すべての `Option` 型には `unwrap()`\nを呼び出す必要があります。このため、コード提案に対してより直接的な指示が求められます。\n\n\n```rust\n                // https://docs.rs/feed-rs/latest/feed_rs/model/struct.Feed.html\n                // https://docs.rs/feed-rs/latest/feed_rs/model/struct.Entry.html\n                // Loop over all entries, and print\n                // title.unwrap().content\n                // published.unwrap().format\n                // summary.unwrap().content\n                // links href as joined string\n                for entry in feed.entries {\n                    println!(\"Title: {}\", entry.title.unwrap().content);\n                    println!(\"Published: {}\", entry.published.unwrap().format(\"%Y-%m-%d %H:%M:%S\"));\n                    println!(\"Summary: {}\", entry.summary.unwrap().content);\n                    println!(\"Links: {:?}\", entry.links.iter().map(|link| link.href.clone()).collect::\u003CVec\u003CString>>().join(\", \"));\n                    println!();\n                }\n```\n\n\n![特定の要件に基づいてフィードエントリの型を出力するためのコード提案](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_print_feed_entries_fields_with_rust_type_specifics.png){:\n.shadow}\n\n\n### Option::unwrap()によるエラーハンドリング\n\nプログラムを再ビルドして実行した後、複数行の指示を引き続き処理します。補足：`unwrap()` は空の値に遭遇すると `panic!`\nマクロを呼び出し、プログラムを強制終了させます。これは、フィードデータ内に `summary` のようなフィールドが設定されていない場合に発生します。\n\n\n```shell\n\nGitLab Blog is an Atom feed\n\nTitle: How the Colmena project uses GitLab to support citizen journalists\n\nPublished: 2023-09-27 00:00:00\n\nthread '\u003Cunnamed>' panicked at 'called `Option::unwrap()` on a `None`\nvalue', src/feed_reader.rs:40:59\n\n```\n\n\n解決策のひとつとして、[`std::Option::unwrap_or_else`](https://doc.rust-lang.org/std/option/enum.Option.html#method.unwrap_or_else)\nを使用し、デフォルト値として空の文字列を設定することが挙げられます。この構文には、空の `Text` 構造体のインスタンスを返すクロージャが必要です。\n\n\n問題を解決する上で、正しい初期化方法を見つけるために試行錯誤を重ねました。単に空の文字列を渡すだけではカスタム型ではうまく機能しませんでした。以下に、筆者が試したすべてのアプローチとリサーチの過程をまとめました。\n\n\n```rust\n\n// Problem: The `summary` attribute is not always initialized. unwrap() will\npanic! then.\n\n// Requires use mime; and use feed_rs::model::Text;\n\n/*\n\n// 1st attempt: Use unwrap() to extraxt Text from Option\u003CText> type.\n\nprintln!(\"Summary: {}\", entry.summary.unwrap().content);\n\n// 2nd attempt. Learned about unwrap_or_else, passing an empty string.\n\nprintln!(\"Summary: {}\", entry.summary.unwrap_or_else(|| \"\").content);\n\n// 3rd attempt. summary is of the Text type, pass a new struct\ninstantiation.\n\nprintln!(\"Summary: {}\", entry.summary.unwrap_or_else(|| Text{}).content);\n\n// 4th attempt. Struct instantiation requires 3 field values.\n\nprintln!(\"Summary: {}\", entry.summary.unwrap_or_else(|| Text{\"\", \"\",\n\"\"}).content);\n\n// 5th attempt. Struct instantation with public fields requires key: value\nsyntax\n\nprintln!(\"Summary: {}\", entry.summary.unwrap_or_else(|| Text{content_type:\n\"\", src: \"\", content: \"\"}).content);\n\n// 6th attempt. Reviewed expected Text types in\nhttps://docs.rs/feed-rs/latest/feed_rs/model/struct.Text.html and created\nMime and String objects\n\nprintln!(\"Summary: {}\", entry.summary.unwrap_or_else(|| Text{content_type:\nmime::TEXT_PLAIN, src: String::new(), content: String::new()}).content);\n\n// 7th attempt: String and Option\u003CString> cannot be casted automagically.\nCompiler suggested using `Option::Some()`.\n\nprintln!(\"Summary: {}\", entry.summary.unwrap_or_else(|| Text{content_type:\nmime::TEXT_PLAIN, src: Option::Some(), content: String::new()}).content);\n\n*/\n\n\n// xth attempt: Solution. Option::Some() requires a new String object.\n\nprintln!(\"Summary: {}\", entry.summary.unwrap_or_else(|| Text{content_type:\nmime::TEXT_PLAIN, src: Option::Some(String::new()), content:\nString::new()}).content);\n\n```\n\n\nこのアプローチは、コードが複雑で読みにくく、さらにコード提案の支援がなかったため、手動でコーディングする必要があり、満足のいくものではありませんでした。一旦立ち止まり、アプローチを見直しました。`Option`\nが `none` の場合に`unwrap()`\nはエラーをスローするのであれば、そのエラーを処理する簡単な方法があるのではと考え、コード提案に新しいコメントで質問しました。\n\n\n```\n                // xth attempt: Solution. Option::Some() requires a new String object.\n                println!(\"Summary: {}\", entry.summary.unwrap_or_else(|| Text{content_type: mime::TEXT_PLAIN, src: Option::Some(String::new()), content: String::new()}).content);\n\n                // Alternatively, use Option.is_none()\n```\n\n\n![コード提案によるOptions.is_noneを使った代替案](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_after_complex_unwrap_or_else_ask_for_alternative_option.png){:\n.shadow}\n\n\n結果として、読みやすさが向上し、`unwrap()`\nによる無駄なCPUサイクルが減りました。複雑な問題を解決することから単純な解決策を使用することまでの学習過程を大幅に短縮できました。まさにウィンウィンです。\n\n\n忘れないうちに、XMLデータをディスクに保存する指示を再度追加して、リーダーアプリを完成させましょう。\n\n\n```rust\n                // Dump the parsed body to a file, as name-current-iso-date.xml\n                let file_name = format!(\"{}-{}.xml\", thread_name, chrono::Local::now().format(\"%Y-%m-%d-%H-%M-%S\"));\n                let mut file = std::fs::File::create(file_name).unwrap();\n                file.write_all(body.as_ref()).unwrap();\n```\n\n\nプログラムをビルドして実行し、出力を確認します。\n\n\n```shell\n\ncargo build\n\n\ntime cargo run -- \"GitLab Blog,https://about.gitlab.com/atom.xml\"\n\"CNCF,https://www.cncf.io/feed/\"\n\n```\n\n\n![VS Codeターミナルでcargo\nrunを実行し、フォーマットされたフィードエントリの出力](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_terminal_cargo_run_formatted_output_final.png)\n\n\n## ベンチマーク\n\n\n### 逐次実行と並列実行のベンチマークの比較\n\nそれぞれ5つのサンプルを作成して、実行時間のベンチマークを比較します。\n\n\n1. 逐次実行\n\n2. 並列実行\n\n\n```shell\n\n# Sequential\n\ngit checkout sequential-exec\n\n\ntime cargo run -- \"GitLab Blog,https://about.gitlab.com/atom.xml\"\n\"CNCF,https://www.cncf.io/feed/\"\n\n\n0.21s user 0.08s system 10% cpu 2.898 total\n\n0.21s user 0.08s system 11% cpu 2.585 total\n\n0.21s user 0.09s system 10% cpu 2.946 total\n\n0.19s user 0.08s system 10% cpu 2.714 total\n\n0.20s user 0.10s system 10% cpu 2.808 total\n\n```\n\n\n```shell\n\n# Parallel\n\ngit checkout parallel-exec\n\n\ntime cargo run -- \"GitLab Blog,https://about.gitlab.com/atom.xml\"\n\"CNCF,https://www.cncf.io/feed/\"\n\n\n0.19s user 0.08s system 17% cpu 1.515 total\n\n0.18s user 0.08s system 16% cpu 1.561 total\n\n0.18s user 0.07s system 17% cpu 1.414 total\n\n0.19s user 0.08s system 18% cpu 1.447 total\n\n0.17s user 0.08s system 16% cpu 1.453 total\n\n```\n\n\n4つのRSSフィードスレッドを並列実行した場合、CPU使用率は増加しましたが、逐次実行に比べて全体の実行時間はほぼ半減しました。この点を踏まえて、Rustの学習を続け、コードと機能を最適化していきます。\n\n\nなお、ここではCargoを使ってデバッグビルドを実行しており、最適化されたリリースビルドはまだ実行していません。また、並列実行にはいくつか注意点があります。一部のHTTPエンドポイントはレート制限を設けており、並列処理がこの制限に引っかかりやすくなる可能性があります。\n\n\n複数のスレッドを並列実行するシステムも過負荷になる可能性があります。これは、カーネル内でのコンテキストスイッチ（スレッド間の切り替え）を通じて各スレッドにリソースを割り当てる必要があるためです。1つのスレッドが計算リソースを使用している間、他のスレッドは待機状態になります。スレッドが多すぎると、システム全体が遅くなり、処理がスピードアップするどころか逆に遅くなることもあります。解決策としては、呼び出し元がキューにタスクを追加し、定義された数のワーカースレッドが非同期実行のためにタスクを処理する[ワークキュー](https://docs.rs/work-queue/latest/work_queue/)などの設計パターンがあります。\n\n\nRustでは、スレッド間のデータ同期を行う[チャンネル](https://doc.rust-lang.org/rust-by-example/std_misc/channels.html)を利用することもできます。競合状態を防ぐために、セーフロックを提供する[ミューテックス](https://doc.rust-lang.org/std/sync/struct.Mutex.html)も利用可能です。\n\n\n### Rustのキャッシュを使用したCI/CD\n\n以下のCI/CD構成を `.gitlab-ci.yml` ファイルに追加します。この `run-latest` ジョブは `cargo run`\nをRSSフィードURLの例とともに呼び出し、実行時間を継続的に計測します。\n\n\n```\n\nstages:\n  - build\n  - test\n  - run\n\ndefault:\n  image: rust:latest\n  cache:\n    key: ${CI_COMMIT_REF_SLUG}\n    paths:\n      - .cargo/bin\n      - .cargo/registry/index\n      - .cargo/registry/cache\n      - target/debug/deps\n      - target/debug/build\n    policy: pull-push\n\n# Cargo data needs to be in the project directory for being cached.\n\nvariables:\n  CARGO_HOME: ${CI_PROJECT_DIR}/.cargo\n\nbuild-latest:\n  stage: build\n  script:\n    - cargo build --verbose\n\ntest-latest:\n  stage: build\n  script:\n    - cargo test --verbose\n\nrun-latest:\n  stage: run\n  script:\n    - time cargo run -- \"GitLab Blog,https://about.gitlab.com/atom.xml\" \"CNCF,https://www.cncf.io/feed/\"\n```\n\n\n![Rust用のGitLab CI/CDパイプライン、cargo\nrunの出力](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/gitlab_cicd_pipeline_rust_cargo_run_output.png){:\n.shadow}\n\n\n## 次のステップ\n\nこのブログ記事の執筆は、筆者自身が高度なRustプログラミング技術を学びつつ、コード提案を使って最適な学習過程を見出すという点で難しいものでした。コード提案は、単なる定型的なコードだけでなく、ローカルコンテキストを理解し、記述されたコードが多いほどアルゴリズムの目的や範囲をよりよく把握した迅速なコード生成にも大いに役立ちます。このブログ記事を読むことで、全てではないにしろ、いくつかの課題や解決策についてご理解いただけたかと思います。\n\n\nRSSフィードの解析は、外部HTTPリクエストや並列最適化を伴うデータ構造が関わるため、ハードルが高めです。経験豊富なRustユーザーであれば、`なぜstd::rssクレートを使わなかったのか？`\nと疑問に思うかもしれません。その理由は、std::rssは高度な非同期実行に最適化されており、このブログ記事でご説明したさまざまなRustの機能を示したり説明したりすることができないためです。ぜひ、非同期の演習として、[`rss`\nクレート](https://docs.rs/rss/latest/rss/)を使ってコードを書き直してみてください。\n\n\n### 非同期学習のエクササイズ\n\nこのブログ記事で学んだ内容は、永続的なデータ保存やデータ表示に関する理解を今後も深めていく上で基礎となります。引き続きRustを学びながら、リーダーアプリを最適化していくためのアイデアをいくつかご紹介します。\n\n\n1. データ保存：sqliteなどのデータベースを使用し、RSSフィードの更新履歴を追跡する。\n\n2. 通知：子プロセスを生成して、Telegramなどに通知を送る機能を追加する。\n\n3. 機能：リーダータイプを拡張して、REST APIをサポートする。\n\n4. 構成：RSSフィードやAPIなどの構成ファイルのサポートを追加する。\n\n5. 効率性：フィルタリングやサブスクライブしたタグのサポートを追加する。\n\n6.\nデプロイ：Webサーバーを使用して、Prometheusメトリクスを収集して[Kubernetes](https://about.gitlab.com/ja-jp/blog/what-is-kubernetes/)にデプロイする。\n\n\n今後のブログ記事では、これらのアイデアのいくつかを取り上げ、その実装方法について説明します。既存のRSSフィードの実装を詳しく調べ、どのように既存のコードをリファクタリングしてRustのライブラリ（`crates`）を活用できるか学びましょう。\n\n\n### フィードバックの共有\n\n[GitLab\nDuo](https://about.gitlab.com/ja-jp/gitlab-duo/)のコード提案をご使用になった方は、ぜひ[ご意見をフィードバックイシューにお寄せください](https://gitlab.com/gitlab-org/gitlab/-/issues/405152)。\n\n\n*監修：佐々木 直晴 [@naosasaki](https://gitlab.com/naosasaki) \u003Cbr>\n\n（GitLab合同会社 ソリューションアーキテクト本部 シニアソリューションアーキテクト）*\n","ai-ml",[705,9,706,707,708],"DevSecOps","tutorial","workflow","AI/ML","2025-01-24",{"slug":711,"featured":6,"template":685},"learn-advanced-rust-programming-with-a-little-help-from-ai-code-suggestions","content:ja-jp:blog:learn-advanced-rust-programming-with-a-little-help-from-ai-code-suggestions.yml","Learn Advanced Rust Programming With A Little Help From Ai Code Suggestions","ja-jp/blog/learn-advanced-rust-programming-with-a-little-help-from-ai-code-suggestions.yml","ja-jp/blog/learn-advanced-rust-programming-with-a-little-help-from-ai-code-suggestions",{"_path":717,"_dir":245,"_draft":6,"_partial":6,"_locale":7,"seo":718,"content":724,"config":734,"_id":736,"_type":13,"title":737,"_source":15,"_file":738,"_stem":739,"_extension":18},"/ja-jp/blog/we-need-to-talk-no-proxy",{"title":719,"description":720,"ogTitle":719,"ogDescription":720,"noIndex":6,"ogImage":721,"ogUrl":722,"ogSiteName":669,"ogType":670,"canonicalUrls":722,"schema":723},"no_proxyを標準化する方法：お客様事例で徹底解説","環境変数“no proxy”が原因で問題発生したことは？お客様事例を取り上げ、標準化の方法を考えてみました。","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749659507/Blog/Hero%20Images/AdobeStock_623844718.jpg","https://about.gitlab.com/blog/we-need-to-talk-no-proxy","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"no_proxyを標準化する方法：お客様事例で徹底解説\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Stan Hu\"}],\n        \"datePublished\": \"2021-01-27\",\n      }",{"title":719,"description":720,"authors":725,"heroImage":721,"date":727,"body":728,"category":729,"tags":730,"updatedDate":733},[726],"Stan Hu","2021-01-27","ウェブプロキシサーバーを使用した経験がある方なら、環境変数`http_proxy`や`HTTP_PROXY`をよくご存知でしょう。しかし、`no_proxy`（ノープロキシ）に関しては、どうもわかりにくい、と感じていらっしゃる方も多いのではないでしょうか。\n\nno proxyとは、あるホスト宛のトラフィックでプロキシを経由させないようにする環境変数です。世界基準が存在するHTTPと違い、ウェブクライアントがno proxyを処理する方法に「標準」は存在しません。その結果、ウェブクライアントは場合により異なる方法で処理を行います。\n\nその違いが原因でサービスが通信を停止し、その原因を突き止めるために週末返上で作業する羽目になったGitLabのお客様もいらっしゃいます。\n\nそこで、この記事ではGitLabのお客様が直面した問題について、具体例を挙げながら根本原因を探り、「no proxyを標準化する方法」というテーマを掘り下げてみます。\n\n### no proxyはなぜ「わかりにくい」のか\n\nno proxyがなぜわかりにくいのか、具体例を挙げて説明します。\n\n現在、ほとんどのウェブクライアントは環境変数を介してプロキシサーバーへの接続をサポートしています。環境変数には大文字表記と小文字表記があります。\n\n- `http_proxy` / `HTTP_PROXY`\n- `https_proxy` / `HTTPS_PROXY`\n- `no_proxy` / `NO_PROXY`\n\nこれらの変数は、プロキシサーバーにアクセスするのにどのURLを使用するか、またどういった例外を作っているか、クライアントに指示するものです。\n\nたとえば、ある企業で田中さんが`http://tanaka.example.com:8080` でリッスンしているプロキシサーバーの場合、次のようになります。\n\n```sh\nexport http_proxy=http://tanaka.example.com:8080\n```\n\n一方、同僚の斎藤さんも、次のように大文字バージョンの`HTTP_PROXY` で定義していたとします。\n\n```sh\nexport HTTP_PROXY=http://saito.example.com:8080\n```\n\nこの場合、どちらのプロキシサーバーが使用されることになるのでしょうか？答えは「状況によって異なる」です。ある場合は田中さんのプロキシサーバーが有効になる場合もあれば、ある場合は斎藤さんのプロキシサーバーが有効になる場合があります。\n\nこの場合、どちらのプロキシサーバーが使用されることになるのでしょうか？答えは「状況によって異なる」です。ある場合は田中さんのプロキシサーバーが有効になる場合もあれば、ある場合は斎藤さんのプロキシサーバーが有効になる場合があります。\n\nでは、例外を設定したい場合はどうなるでしょうか。たとえば、`internal.example.com`と`internal2.example.com`以外のすべてで、プロキシサーバーを経由したい場合です。このような場合が`no_proxy`変数の出番です。`no_proxy`を次のように定義します。\n\n```sh\nexport no_proxy=internal.example.com,internal2.example.com\n```\n\nでは、IPアドレスを除外したい場合はどうすればよいでしょうか？アスタリスクやワイルドカードは使用できるのでしょうか？CIDRブロック（例:`192.168.1.1/32`）は？\n\nこれらの答えも、「状況によって異なる」です。つまり「使用言語やツールという”PC環境”によって、proxy変数の処理方法が異なる」のが、no proxyがわかりにくいとされている理由です。次の項では、proxy変数の処理方法の違いについてさらに掘り下げます。\n\n### なぜno proxyはこんなに複雑なのか？\n\nこの問題の理解を深めるため、no proxyを巡るこれまでの経緯を説明しておきます。\n\n1994年においてほとんどのウェブクライアントは、[`http_proxy`と`no_proxy`環境変数をサポートするCERNの](https://courses.cs.vt.edu/~cs4244/spring.09/documents/Proxies.pdf)`libwww`を使用していました。`libwww`は、`http_proxy`の小文字形式のみを使用し、[`no_proxy`構文は以下のようにシンプルでした。](https://github.com/w3c/libwww/blob/8678b3dcb4191065ca39caea54bb1beba809a617/Library/src/HTAccess.c#L234-L239)\n\n```\nno_proxy is a comma- or space-separated list of machine\nor domain names, with optional :port part.  If no :port\npart is present, it applies to all ports on that domain.\n\nExample:\n\t\tno_proxy=\"cern.ch,some.domain:8001\"\n```\n\nつまり、元々「小文字表記のみ」で始まったのですが、その後新しいクライアントである`wget`や`curl`の登場により、`no proxy`の大文字が使用可になったり、不可とされたりと変遷しているのです。\n\n1996年1月にHrvoje Niksicが、`libwww`をリンクせずに独自のHTTP実装を追加する新しいクライアント、`geturl`（現在の`wget`の前身）をリリースしました。翌月には`geturl`が[バージョン1.1でhttp\\_proxyのサポートを追加](https://ftp.sunet.se/mirror/archive/ftp.sunet.se/pub/www/utilities/wget/old-versions/)され、同年5月には`geturl`バージョン1.3で`no_proxy`のサポートが追加されました。ここでは`libwww`と同様に、`geturl`では小文字形式`no_proxy`のみのサポートでした。\n\n1998年1月には、Daniel Stenbergが`curl`v5.1をリリースし、[`http_proxy`および`no_proxy`](https://github.com/curl/curl/blob/ae1912cb0d494b48d514d937826c9fe83ec96c4d/CHANGES#L929-L944)変数をサポート。また、大文字の形式の`HTTP_PROXY`および`NO_PROXY`も許可されました。\n\n2009年3月にはcurl v7.19.4がセキュリティ上の懸念から、大文字`HTTP_PROXY`のサポートを廃止します。`curl`では`HTTP_PROXY`は無視されますが、`HTTPS_PROXY`は現在でも動作します。\n\n### 一目でわかるproxy変数の処理方法の違い\n\nGitLabの[Nourdinel Bachaが調査したところ](https://gitlab.com/gitlab-com/support/support-team-meta/-/issues/2991)、これらのプロキシサーバー変数の処理方法は、使用言語やツールによって異なることがわかりました。\n\n#### http_proxyとhttps_proxyの場合\n\n各行はサポートされている動作を表し、各列にはそれが適用されるツール（例：curl）または言語（例：Ruby）を表しています。\n\n|                 | curl      | wget           | Ruby          | Python    | Go        |\n|-----------------|-----------|----------------|---------------|-----------|-----------|\n| `http_proxy`    | はい       | はい            | はい           | はい       | はい       |\n| `HTTP_PROXY`    | いいえ        | いいえ             | はい ([警告](https://github.com/ruby/ruby/blob/0ed71b37fa9af134fdd5a7fd1cebd171eba83541/lib/uri/generic.rb#L1519)) | はい (`REQUEST_METHOD` が環境にない場合)       | はい       |\n| `https_proxy`   | はい       | はい            | はい           | はい       | はい       |\n| `HTTPS_PROXY`   | はい       | いいえ             | はい           | はい       | はい       |\n| 大文字と小文字の優先順位 | 小文字 | 小文字のみ | 小文字     | 小文字 | 大文字 |\n| 参照       | [出所](https://github.com/curl/curl/blob/30e7641d7d2eb46c0b67c0c495a0ea7e52333ee2/lib/url.c#L2250-L2266) | [出所](https://github.com/jay/wget/blob/099d8ee3da3a6eea5635581ae517035165f400a5/src/retr.c#L1222-L1239) | [出所](https://github.com/ruby/ruby/blob/0ed71b37fa9af134fdd5a7fd1cebd171eba83541/lib/uri/generic.rb#L1474-L1543) | [出所](https://github.com/python/cpython/blob/030a713183084594659aefd77b76fe30178e23c8/Lib/urllib/request.py#L2488-L2517) | [出所](https://github.com/golang/go/blob/682a1d2176b02337460aeede0ff9e49429525195/src/vendor/golang.org/x/net/http/httpproxy/proxy.go#L82-L97) |\n\nこの表から以下のことがわかります。\n\n* http\\_proxyとhttps\\_proxyは常に全面的にサポートされているが、HTTP\\_PROXYは必ずしもサポートされているわけではない。  \n* Python（urllib経由）では状況がさらに複雑となる。HTTP\\_PROXYが使用できるのは、[REQUEST\\_METHODが環境で定義されていない場合に限られる](https://github.com/python/cpython/blob/030a713183084594659aefd77b76fe30178e23c8/Lib/urllib/request.py#L2504-L2508)。  \n* Goだけは他と異なり、小文字バージョンより大文字バージョンを優先する。\n\n環境変数はすべて大文字だと思われがちですが、実は最初に登場した`http_proxy`に倣い、小文字表記が事実上のスタンダードとなっています。よくわからない場合は、普遍的にサポートされている小文字形式の使用をおすすめします。\n\n#### no_proxyの場合\n\nさて、次は`no_pproxy`について説明します。次の表は、さまざまな実装の状態を示しています。こちらの表は`http_proxy`の場合に比べてもっと複雑です。例えば、`no_proxy`設定が次の様に定義されているとします。\n\n```sh\nexport no_proxy=example.com\n```\n\nこれはドメインが完全一致である必要があるのか、それともsubdomain.example.comのようなサブドメインも含まれるのでしょうか。次の表は様々な実装状況を示しています。「サフィックス（接尾辞）と一致？」の行を見ると分かるように、実際にはすべての実装がサフィックス（ドメイン末尾）を適切に一致させることができます。\n\n|                       | curl      | wget           | Ruby      | Python    | Go        |\n|-----------------------|-----------|----------------|-----------|-----------|-----------|\n| `no_proxy`            | はい       | はい            | はい       | はい       | はい       |\n| `NO_PROXY`            | はい       | いいえ             | はい       | いいえ       | はい       |\n| 大文字と小文字の優先順位       | 小文字 | 小文字のみ | 小文字 | 小文字のみ | 大文字 |\n| サフィックス（接尾辞）と一致？     | はい       | はい            | はい       | はい       | はい       |\n| `.`でリーディング停止？   | はい       | いいえ             | はい       | はい       | いいえ        |\n| `*` はすべてのホストに一致？| はい       | いいえ             | いいえ        | はい       | はい       |\n| 正規表現をサポート？     | いいえ        | いいえ             | いいえ        | いいえ        | いいえ        |\n| CIDRブロックをサポート？ | いいえ        | いいえ             | はい       | いいえ        | はい       |\n| ループバックIPを検出する？ | いいえ        | いいえ             | いいえ        | いいえ        | はい       |\n| 参考             | [出所](https://github.com/curl/curl/blob/30e7641d7d2eb46c0b67c0c495a0ea7e52333ee2/lib/url.c#L2152-L2206) | [出所](https://github.com/jay/wget/blob/099d8ee3da3a6eea5635581ae517035165f400a5/src/retr.c#L1266-L1274) | [出所](https://github.com/ruby/ruby/blob/0ed71b37fa9af134fdd5a7fd1cebd171eba83541/lib/uri/generic.rb#L1545-L1554) | [出所](https://github.com/python/cpython/blob/030a713183084594659aefd77b76fe30178e23c8/Lib/urllib/request.py#L2519-L2551)| [出所](https://github.com/golang/go/blob/682a1d2176b02337460aeede0ff9e49429525195/src/vendor/golang.org/x/net/http/httpproxy/proxy.go#L170-L206) |\n\nただし、`no_proxy`設定の先頭に「.」がある場合、動作が異なります。\n\nたとえば、`curl`と`wget`は動作が異なります。`curl`は常に先頭の「.」を削除し、ドメインサフィックスと照合します。次の呼び出しはプロキシをバイパスします。\n\n```sh\n$ env https_proxy=http://non.existent/ no_proxy=.gitlab.com curl https://gitlab.com\n\u003Chtml>\u003Cbody>You are being \u003Ca href=\"https://about.gitlab.com/\">redirected\u003C/a>.\u003C/body>\u003C/html>\n```\n\nただし、`wget`は先頭の「`.`」を削除せず、ホスト名に対して正確な文字列一致を実行します。その結果、`wget`はトップレベルドメインが使用されている場合にプロキシの使用を試みます。\n\n```sh\n$ env https_proxy=http://non.existent/ no_proxy=.gitlab.com wget https://gitlab.com\nResolving non.existent (non.existent)... failed: Name or service not known.\nwget: unable to resolve host address 'non.existent'\n```\n\nすべての実装において、正規表現はサポートされません。\n\n正規表現には独自の特徴（PCRE、POSIXなど）があるため、正規表現を使用すると問題がさらに複雑になると思われます。また、正規表現を使用すると、パフォーマンスとセキュリティの問題が発生する可能性があります。\n\n`no_proxy`を`*`に設定するとプロキシが完全に無効になる場合もあるが、これはすべてに共通するルールではない。  \n\nプロキシを使用するかどうかを決定する際に、ホスト名をIPアドレスに解決するためのDNSルックアップを実行する実装はない。\n\nクライアントによってIPアドレスが明示的に使用されることが予想される場合を除き、`no_proxy`変数にIPアドレスを指定しないようにしましょう。\n\n`18.240.0.1/24`などのCIDRブロックは、リクエストが直接IPアドレスに対して行われた場合にのみ機能します。CIDRブロックが許可されるのはGoとRubyのみです。他の実装とは異なり、GoではループバックIPアドレスが検出されると、プロキシの使用が自動的に無効になります。\n\n### GitLabのお客様が抱えたno proxy問題\n\n大文字小文字表記、言語とツールによるリアクションの違いに注意を払う必要があるのは、複数の言語で記述されたアプリケーションを、プロキシサーバーを備えた企業のファイアウォールの背後で動作させる場合です。GitLabもそのひとつであり、RubyとGoで記述された複数のサービスで構成されています。\n\nここでGitLabのあるお客様の例を挙げましょう。お客様はプロキシ構文を次のように設定しました。\n\n```yaml\nHTTP_PROXY: http://proxy.company.com\nHTTPS_PROXY: http://proxy.company.com\nNO_PROXY: .correct-company.com\n```\n\nこのお客様からGitLabに以下の問題の報告がありました。\n\n1. コマンドラインからの`git` pushが起動した\n2. ウェブUI経由で行われたGitの変更が失敗した\n\n連絡を受けたサポートエンジニアは、[Kubernetes](https://about.gitlab.com/ja-jp/blog/what-is-kubernetes/)の構文の問題により、古い値が残っていることを発見しました。ポッドの環境は実際には次のようになっていました。\n\n```yaml\nHTTP_PROXY: http://proxy.company.com\nHTTPS_PROXY: http://proxy.company.com\nNO_PROXY: .correct-company.com\nno_proxy: .wrong-company.com\n```\n\n`no_proxy`と`NO_PROXY`、両者の定義が一致していないため警告が出ました。定義を一致させるか／誤ったエントリを削除することで、この問題を解決できます。\n\nこの古いエントリの何が原因で問題が起きたのか、もう少し詳しく見てみることにします。先ほど「[no proxyの場合](#bookmark=id.3j5kjy3c5qh2)」で述べたことをここで思い出してみましょう。\n\n1. Rubyはまず小文字形式を試す\n2. Goはまず大文字形式を試す\n\nその結果、GitLab WorkhorseなどのGoで記述されたサービスには正しいプロキシ構文となりました。Goサービスが主にこのアクティビティを処理したため、コマンドラインからの`git push`は正常に機能しました。\n\n```mermaid\nsequenceDiagram\n    participant C as Client\n    participant W as Workhorse\n    participant G as Gitaly\n    C->>W: 1. git push\n    W->>G: 2. gRPC: PostReceivePack\n    G->>W: 3. OK\n    W->>C: 4. OK\n```\n\ngRPC呼び出しでは、`no_proxy`がGitalyに直接接続するように適切に構成されていたため、プロキシの使用が試行されませんでした。\n\nただし、ユーザーがUIを変更すると、GitalyはリクエストをRubyで記述された`gitaly-ruby`サービスに転送します。`gitaly-ruby`はリポジトリに変更を加え、[gRPCコールバックを介して親プロセスにレポートを返します](https://gitlab.com/gitlab-org/gitaly/-/issues/3189)(英語）。ただし、以下の手順4に示すように、レポート手順は実行されませんでした。\n\n```mermaid\nsequenceDiagram\n    participant C as Client\n    participant R as Rails\n    participant G as Gitaly\n    participant GR as gitaly-ruby\n    participant P as Proxy\n    C->>R: 1. Change file in UI\n    R->>G: 2. gRPC: UserCommitFiles\n    G->>GR: 3. gRPC: UserCommitFiles\n    GR->>P: 4. CONNECT\n    P->>GR: 5. FAIL\n```\n\ngRPCは基盤となるトランスポートとしてHTTP/2を使用するため、`gitaly-ruby`は間違った`no_proxy`設定で構成されたプロキシへのCONNECTを試行しました。プロキシはこのHTTP要求を即座に拒否し、ウェブUIプッシュケースで失敗を引き起こしました。\n\n環境から小文字の`no_proxy`を削除すると、UIからのプッシュが期待どおりに機能し、`gitaly-ruby`が親のGitalyプロセスに直接接続されました。以下の図のステップ4は適切に機能しました。\n\n```mermaid\nsequenceDiagram\n    participant C as Client\n    participant R as Rails\n    participant G as Gitaly\n    participant GR as gitaly-ruby\n    participant P as Proxy\n    C->>R: 1. Change file in UI\n    R->>G: 2. gRPC: UserCommitFiles\n    G->>GR: 3. gRPC: UserCommitFiles\n    GR->>G: 4. OK\n    G->>R: 5. OK\n    R->>C: 6. OK\n```\n\n#### もう一つの原因はgRPCにあった\n\n`https://`ではなく`http://`が使用されています。セキュリティの観点からは理想的ではありませんが、TLS証明書の検証の問題によりクライアントが失敗するという面倒を避けるために行う場合もあります。\n\nしかしこの場合、HTTPSプロキシが指定されていれば、この問題は発生しなかったでしょう。HTTPSプロキシが使用されている場合、gRPCは[HTTPSプロキシをサポートしていない](https://github.com/grpc/grpc/issues/20939)ため、この設定を無視するからです。\n\n### 解決策：最小限の共通項で設定する\n\n小文字と大文字のプロキシ設定で矛盾した値を定義すべきではないことは、誰もが同意すると思います。ただし、複数の言語で記述されたスタックを管理する必要がある場合は、HTTPプロキシ構文を最も共通する設定で行うよう検討することをおすすめします。\n\n#### `http_proxy` と `https_proxy`\n\n* 小文字形式を使用する。 `HTTP_PROXY` は常にサポートまたは推奨されるわけではない。\n    * どうしても大文字形式も使用する必要がある場合は、__必ず__ 同じ値を共有する。\n\n#### `no_proxy`\n\n1. 小文字形式を使用する。\n2. カンマ区切りの`hostname:port`値を使用する。\n3. IPアドレスは問題ないが、ホスト名は解決されない。\n4. サフィックスは常にマッチングされる(例:`example.com`は`test.example.com`と一致)。\n5. トップレベルドメインを一致させる必要がある場合は、先頭のドット(`.`)を使用しない。\n6. GoとRubyのみがCIDRマッチングをサポートしているため、CIDRマッチングの使用は避ける。\n\n### 解決策：`no_proxy`の標準化チェックリスト\n\n最小公分母を知っておくと、定義が異なるウェブクライアントにコピーされた場合に、問題を回避する上で役立ちます。しかし、`no_proxy`やその他のプロキシ設定には、間に合わせの標準よりも、文書化された標準が必要かもしれません。以下のリストを出発点としてお役立てください。\n\n1. 大文字の変数よりも小文字の変数を優先する (例 `http_proxy` は`HTTP_PROXY`の前に検索すべき)。\n2. カンマ区切りの `hostname:port` 値を使用する。\n    * 各値にはオプションの空白を含めることができる。\n3. DNSルックアップの実行や、正規表現の使用を行わない。\n4. **すべての** ホストに一致させるには`*`を使用する。\n5. 先頭のドット (`.`) を削除し、ドメインサフィックスに対してマッチングさせる。\n6. CIDRブロックマッチングをサポートする。\n7. 特別なIPアドレスを想定しない（たとえば`no_proxy`のループバックアドレス)。\n\n#### まとめ\n\n最初のウェブプロキシがリリースされてから25年以上経ちました。環境変数を介してウェブクライアントを構成する基本的な仕組みはあまり変わっていませんが、さまざまな実装で微妙な違いが生じています。\n\n今回、GitLabのあるお客様の具体的な事例をご紹介しました。このお客様の状況は以下のとおりでした。\n\n* 競合する`no_proxy`変数と`NO_PROXY`変数を誤って定義  \n* RubyとGoはこれらの設定を処理する方法が異なるため、トラブルシューティングに何時間も費やす\n\nこのブログではこの2つの違いに焦点を当て、解説しました。皆様の本番スタックで将来の問題発生回避にお役立ていただけると幸いです。また、設定標準チェックリストを参照して、ウェブクライアントの保守担当者様が動作を標準化し、このような問題を根本的に回避することを願っています。\n\nGitの利便性を生かしつつ、一元化されたプラットフォームでデベロッパー、セキュリティ担当者、運用チームをサポートするGitLabでは、[AIによるコード提案機能があるため、効率性を高められます](https://about.gitlab.com/ja-jp/platform/)。導入検討中の方は、ぜひ無料でのトライアルをお試しください。\n\n> [無料トライアルを開始してみる](https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/ja-jp/platform&glm_content=default-saas-trial)\n\n画像出展： [PixaBay](https://pixabay.com/illustrations/question-mark-pile-questions-symbol-2492009)\n{: .note}\n\n\u003Cbr>\u003Cbr>\u003Cbr>\n\n*監修：小松原 つかさ  [@tkomatsubara](https://gitlab.com/tkomatsubara)\u003Cbr>\n（GitLab合同会社 ソリューションアーキテクト本部 シニアパートナーソリューションアーキテクト）*","engineering",[268,9,731,732],"user stories","startups","2025-03-17",{"slug":735,"featured":6,"template":685},"we-need-to-talk-no-proxy","content:ja-jp:blog:we-need-to-talk-no-proxy.yml","We Need To Talk No Proxy","ja-jp/blog/we-need-to-talk-no-proxy.yml","ja-jp/blog/we-need-to-talk-no-proxy",1759956865830]