コードベースやフレームワークのモダナイゼーションを行うために新しいプログラミング言語へ移行する必要があったり、同じ言語内の新しい機能の知識を深めたい場合は、AI搭載のGitLab Duoが有効です。ここでは、筆者が過去20年にわたるコーディングキャリアで培ったベストプラクティスをもとに、コードリファクタリングの際に直面する課題への効果的なアプローチ方法をご紹介します。
この記事では、VS CodeとJetBrains IDE(IntelliJ IDEA、PyCharm、CLion)を使用したプロンプトと例を紹介します。いずれのIDEにもGitLab Duo拡張機能やプラグインがインストールされています。開発環境としてはGitLab.comが使用されています。ここでは大規模言語モデル(LLM)がAnthropic Claude 3.5にアップデートされており、GitLab Duoのコード提案とDuo Chatに適用されています。これらの機能は卓越したパフォーマンスと効率性を発揮します。
この記事では必要なセクションだけを確認しても良いですし、上から順を追って読んでも構いません。また、自己学習に役立つソースコードや演習問題も用意しています。
- モダンなプログラミング言語標準に合わせたコードのリファクタリング
- Java 7のコードを生成し、Java 8にリファクタリング
- C++標準に基づいたリファクタリング
- 移行:C++03をC++14にリファクタリング
- ダウングレード:C++23をC++11にリファクタリング
- COBOLの説明とリファクタリング
- 異なる言語間でのリファクタリング
- CをRustにリファクタリング
- PerlをPythonにリファクタリング
- その他のリファクタリング実践例
- JavaScriptのリファクタリング
- BashをZSHやSHにリファクタリング
- その他のユースケースやチュートリアル
- 重要なポイント
モダンなプログラミング言語標準に合わせたコードのリファクタリング
次のセクションでは、特定のプログラミング言語におけるリファクタリングの方法、言語固有の違いの説明、および理解の仕方について解説します。
Java 7のコードを生成し、Java 8にリファクタリング
あるお客様から、GitLab DuoがJava 7をサポートしているかどうかのご質問が寄せられました。Java 7固有のコードを生成するためのコード提案とGitLab Chatのプロンプトをテストしている際、移行支援についても考えました。Anthropic Claude 3.5と同様に、GitLab Duoも言語固有の違いを理解できます。
次の例では、コード提案を使用してJava 7のコードを生成するプロンプトを示しています。
// Generate a new class for a File Manager. Use Java 7 features only.
import java.io.*;
import java.nio.file.*;
import java.util.*;
public class FileManager {
private Path rootDirectory;
public FileManager(String rootPath) throws IOException {
this.rootDirectory = Paths.get(rootPath);
if (!Files.exists(rootDirectory)) {
Files.createDirectories(rootDirectory);
}
}
public void createFile(String fileName, String content) throws IOException {
Path filePath = rootDirectory.resolve(fileName);
Files.write(filePath, content.getBytes(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
}
public String readFile(String fileName) throws IOException {
Path filePath = rootDirectory.resolve(fileName);
return new String(Files.readAllBytes(filePath));
}
public void deleteFile(String fileName) throws IOException {
Path filePath = rootDirectory.resolve(fileName);
Files.delete(filePath);
}
public List<String> listFiles() throws IOException {
List<String> fileList = new ArrayList<>();
try (DirectoryStream<Path> stream = Files.newDirectoryStream(rootDirectory)) {
for (Path file : stream) {
fileList.add(file.getFileName().toString());
}
}
return fileList;
}
public void copyFile(String sourceFileName, String destinationFileName) throws IOException {
Path sourcePath = rootDirectory.resolve(sourceFileName);
Path destinationPath = rootDirectory.resolve(destinationFileName);
Files.copy(sourcePath, destinationPath, StandardCopyOption.REPLACE_EXISTING);
}
public void moveFile(String sourceFileName, String destinationFileName) throws IOException {
Path sourcePath = rootDirectory.resolve(sourceFileName);
Path destinationPath = rootDirectory.resolve(destinationFileName);
Files.move(sourcePath, destinationPath, StandardCopyOption.REPLACE_EXISTING);
}
public long getFileSize(String fileName) throws IOException {
Path filePath = rootDirectory.resolve(fileName);
return Files.size(filePath);
}
public boolean fileExists(String fileName) {
Path filePath = rootDirectory.resolve(fileName);
return Files.exists(filePath);
}
}
ご自身のIDEで次の手順を進めてください。
- 生成されたコードを理解する:エディタでコード部分を選択(スクリーンショットの青色部分)し、GitLab Duo Chatに切り替えて、
/explain
スラッシュ(/) コマンドをプロンプトとして送信します。
- Java 7のコードをJava 8にリファクタリングする:エディタでコードを選択し、Chatに切り替えて、プロンプトを
/refactor using Java 8 features
に変更して送信します。 - リファクタリングの代替方法を練習する:新しいファイル
java8.java
を作成し、コード提案を使って// Generate a new class for a File Manager. Use Java 8 features only.
というプロンプトでJava 8固有のコードを生成します。 - AI搭載のコード補完機能を使って、同じコンテキストでJava 8のコードの続きを記述します。
次の録画ですべての手順をご覧になれます。
ソースコードはGitLab Duo Challenge - Generate and refactor Java 7 to 8 project で取得できます。
C++標準に基づいたリファクタリング
C++の標準には長い歴史があり、安定性と成熟度には信頼があります。ターゲットとなるアーキテクチャやプラットフォームに応じて、サポートされる標準はホストのオペレーティングシステム上のコンパイラに依存します。
筆者は自身のキャリアを通して、25種類の異なるLinuxおよびWindowsディストリビューションをサポートするオープンソースプロジェクトに関わり、新しい言語機能やC++標準に対するコンパイラのサポートが多様であることを実感してきました。2015年にはC++11の機能を採用し、それ以降C++14やC++17に触れることはありませんでした。
関数やアルゴリズムを新しいC++標準にリファクタリングするのには多大な労力がかかり、その違いを説明する支援が必要でした。当時は、リファクタリングの調べものを、書籍やオンライン検索、Stack Overflow、そして時にはツールの助けを借りて行っていました。
しかし、AI搭載のアシストやGitLab Duoの登場で状況は変わりました。コード提案やDuo Chatに、特定のC++標準に準拠したソースコードを提示するよう指示したり、異なる出力を比較したりできるようになったのです。 しかし、AI搭載のアシストやGitLab Duoの登場で状況は変わりました。コード提案やDuo Chatに、特定のC++標準に準拠したソースコードを提示するよう指示したり、異なる出力を比較したりできるようになったのです。
次の例では、C++03標準に特化したプロンプトを使用してソースコードを生成しています。これは、.ini
構成フォーマット用のシンプルなコンフィグパーサー(configparser)を実装しています。
/* iniフォーマットを解析できるconfigparserを作成
C++03標準のみを使用
*/
#include <iostream>
#include <fstream>
#include <string>
#include <map>
#include <sstream>
class ConfigParser {
private:
std::map<std::string, std::map<std::string, std::string> > config;
public:
ConfigParser() {}
bool parse(const std::string& filename) {
std::ifstream file(filename.c_str());
if (!file.is_open()) {
return false;
}
std::string line;
std::string currentSection;
while (std::getline(file, line)) {
// Trim whitespace
size_t first = line.find_first_not_of(" \t");
size_t last = line.find_last_not_of(" \t");
if (first == std::string::npos) continue; // Skip empty lines
line = line.substr(first, (last - first + 1));
if (line[0] == '[' && line[line.length() - 1] == ']') {
// This is a section
currentSection = line.substr(1, line.length() - 2);
} else {
// This is a key-value pair
size_t delimiterPos = line.find('=');
if (delimiterPos != std::string::npos) {
std::string key = line.substr(0, delimiterPos);
std::string value = line.substr(delimiterPos + 1);
config[currentSection][key] = value;
}
}
}
file.close();
return true;
}
std::string getValue(const std::string& section, const std::string& key) const {
std::map<std::string, std::map<std::string, std::string> >::const_iterator sectionIt = config.find(section);
if (sectionIt != config.end()) {
std::map<std::string, std::string>::const_iterator keyIt = sectionIt->second.find(key);
if (keyIt != sectionIt->second.end()) {
return keyIt->second;
}
}
return "";
}
void printConfig() const {
std::map<std::string, std::map<std::string, std::string> >::const_iterator sectionIt;
for (sectionIt = config.begin(); sectionIt != config.end(); ++sectionIt) {
std::cout << "[" << sectionIt->first << "]" << std::endl;
std::map<std::string, std::string>::const_iterator keyIt;
for (keyIt = sectionIt->second.begin(); keyIt != sectionIt->second.end(); ++keyIt) {
std::cout << keyIt->first << " = " << keyIt->second << std::endl;
}
std::cout << std::endl;
}
}
};
int main() {
ConfigParser parser;
if (parser.parse("config.ini")) {
std::cout << "Config file parsed successfully." << std::endl;
parser.printConfig();
std::cout << "Value of 'key1' in 'section1': " << parser.getValue("section1", "key1") << std::endl;
} else {
std::cout << "Failed to parse config file." << std::endl;
}
return 0;
}
同じプロンプトを、コードの説明、テスト生成、さらにはコードのリファクタリングにも適用できます。たとえば、/explain focus on C++03 features
、/tests for C++03 features
、/refactor into C++03
などです。
次のC++標準は、GitLab Duoのコード提案を使用して動作を確認済みです。
- C++03
- C++11
- C++14
- C++17
- C++20
- C++23
ソースコードは、Challenge - Refactor Cpp Standards Config Parser projectで取得できます。
C++標準間でソースコードをリファクタリングする組み合わせは数多く存在します。次の例では、C++コードを最新バージョンにアップグレードするケースに焦点を当てています。一方、熱心なデベロッパーがC++20を使用したいものの、エンタープライズな顧客環境をサポートするためにC++11にダウングレードしなければならなかったというシナリオもテストしています。
移行:C++03をC++14にリファクタリング
ここでは、コードのモダナイゼーションを行い、C++03からC++14への変更点を理解することを目標とします。IDEで03.cppのソースコードを開き、選択した後、次のChatプロンプトを使用します。
/refactor into C++14 and explain the changes
(日本語訳:/C++14にリファクタリングし、変更を説明してください)
Chatの回答例:
…
変更点の説明:
1. デフォルトコンストラクタ: `ConfigParser() {}` を `ConfigParser() = default;` に変更し、デフォルトコンストラクタを使用しました。
2. ファイルオープン: ファイルを開く際、`.c_str()` を削除しました。C++11以降では、`std::string` が直接サポートされています。
3. ラムダ関数: 文字列のトリミング処理を行うラムダ関数trimを導入し、コードの可読性と再利用性を向上させました。
4. 自動キーワード: 型推論のために様々な箇所で `auto` を使用し、コードの可読性を改善しました。
5. 範囲ベースのforループ: `printConfig` メソッド内で従来のforループを範囲ベースのforループに置き換え、より簡潔でエラーが少ない構造にしました。
6. 構造化束縛: 範囲ベースのforループで構造化束縛を使用し、キーと値のペアに直接アクセスできるようにしました。
7. 初期化付きif文: `getValue` メソッド内で、検索操作と条件チェックを1行で行える初期化付きif文を使用しました。
8. 文字列メンバー関数: 文字列の最初と最後の文字を取得する際に、配列風のアクセスではなく `front()` と `back()` を使用しました。
9. 初期化の統一: オブジェクトの生成時に `()` ではなく `{}` を使用する統一された初期化を採用しました。
10. `std::endl` の削除: `std::endl` を `'\n'` に置き換え、パフォーマンスを向上させました。`std::endl` は出力バッファーを強制的にフラッシュするためです。
これらの変更により、コードはよりモダンで簡潔になり、同じ機能を維持しつつ効率性が向上しています。
ダウングレード:C++23をC++11にリファクタリング
時に、デベロッパーはC++20のようなモダンなC++標準で新しい機能の概念実証を開始しますが、エンタープライズな顧客環境で実際にサポートされているバージョンでは、C++11をサポートする古いコンパイラが必要になる場合があります。バージョンをダウングレードするにはコストがかかるほか、どの言語機能がまだサポートされているかを確認し、潜在的なデメリットを検討する必要があります。
IDEで23.cppのソースコードを開き、選択した後、次のChatプロンプトを使用します。
/refactor into C++11, explain the necessary changes and potential problems
(日本語訳:/C++14にリファクタリングし、必要な変更点と潜在的な問題点を説明してください)
Duo Chatの回答例:
このコードをC++11に対応させるために必要な主な変更点は次の通りです:
1. C++23標準のコメントを削除しました。これはもう適用されないためです。
2. trim関数内で `std::string::find_first_not_of` と `std::string::find_last_not_of` をC++11対応の代替方法に置き換えました。
3. ループ変数には `auto` の代わりに `const auto&` を使用し、不要なコピーを避けました。
オリジナルのコードにおける潜在的な問題点は次の通りです:
1. オリジナルのコードはC++23の機能を使用しており、まだ広くサポートされていない可能性があります。
2. `trim` 関数では `std::string::find_first_not_of` と `std::string::find_last_not_of` を使用しており、これらはC++11には存在しません。
3. 一部のループで `const auto&` の代わりに `auto` を使用していないと、複雑なオブジェクトが不必要にコピーされる可能性があります。
これらの問題はリファクタリング版で解決されています。`trim` 関数は、C++11対応の場合と同じ結果を得るために、ラムダ関数を使った `std::find_if_not` を利用するように変更されました。また、 `const auto&` を使用することで、設定マップを反復処理する際に不要なコピーが行われないようにしました。
非同期練習:他のバージョンのリファクタリングシナリオをテストしてみましょう。
COBOLの説明とリファクタリング
GitLab Duoを使用して、COBOLプログラムのソースコードを説明させたり、解析、修正、リファクタリングを実行してもらえます。筆者はCOBOLを書いたことも学んだこともありませんでしたが、このCOBOL Programming Courseでは豊富な例が紹介されていて有用だと感じました。
その後、次のように、Chatに「COBOLの始め方」「COBOLプログラムの作成方法」「macOSでのCOBOLプログラムのコンパイル方法」について質問しました。
Please explain what COBOL is and its syntax
(日本語訳:COBOLとは何か、その構文について説明してください。)
Please create a COBOL program that shows the first steps
(日本語訳:COBOLプログラムを作成し、最初のステップを示してください。)
Tell me more about the COBOL compiler. Which system do I need? Can I do it on my macOS?
(日本語訳:COBOLコンパイラについて教えてください。どのようなシステムが必要ですか? macOSで実行できますか?)
COBOLプログラムを開き、ソースコードを選択したら、Duo Chatに切り替えて、/explain
プロンプトを送信し、目的や機能を説明してもらいましょう。
また、プロンプトをさらに洗練して、全体像をより反映したサマリーを取得することもできます。
/explain like I am five
(日本語訳:/explain 5歳児にもわかるように説明してください。)
ヒント:プログラミング言語は、類似したアルゴリズムや機能を共有しています。COBOLについては、ChatによるPythonを介した説明が提供されたため、以降のプロンプトではPythonでの説明を求めるように調整しました。
/explain in a different programming language
(日本語訳:/explain 異なるプログラミング言語で説明してください。)
Chatでは、/refactor
スラッシュ(/)コマンドを使用して、コード品質の改善、潜在的な問題の修正、およびCOBOLのPythonへのリファクタリングを実行できます。
/refactor fix the environment error
(日本語訳:/refactor 環境上のエラーを修正してください。)
/refactor fix potential problems
(日本語訳:/refactor 潜在的な問題を修正してください。)
/refactor into Python
(日本語訳:/refactor Pythonにリファクタリングしてください。)
次のGitLab Duo Coffee Chat - Challenge: Explain and Refactor COBOL programsの録画では、欠落しているピリオドの見つけ方など、すべての手順が実際のユースケースをもとに説明されています。
異なる言語間でのリファクタリング
モダナイゼーションとコード品質の改善には、プログラミング言語の変更が必要になる場合があります。GitLab Duoのリファクタリングプロンプトを使用して、移行プロセスを迅速に進められますCOBOLとPythonの例は、エンタープライズ環境において一般的な要件のひとつに過ぎません。その他のユースケースを詳しく見ていきましょう。
CをRustにリファクタリング
2024年初頭に、Cを始めとするいくつかのプログラミング言語は、メモリの安全性に問題があると指摘されました。今後のプロジェクトでは、Rustのようなメモリ安全な言語(英語)が推奨されています。ここでは、移行へのアプローチや、想定すべき課題について理解しましょう。
簡単なCの例で試してみましょう。このコードはコード提案を使用して生成されており、オペレーティングシステムの基本情報(名前、バージョン、プラットフォームなど)を表示します。このCコードは、Windows、Linux、macOSでクロスプラットフォームにコンパイルできます。
// OSファイルを読み込み、プラットフォーム、名前、バージョンを特定する
// ターミナルに出力する
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef _WIN32
#include <windows.h>
#elif __APPLE__
#include <sys/utsname.h>
#else
#include <sys/utsname.h>
#endif
void get_os_info() {
#ifdef _WIN32
OSVERSIONINFOEX info;
ZeroMemory(&info, sizeof(OSVERSIONINFOEX));
info.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
GetVersionEx((OSVERSIONINFO*)&info);
printf("Platform: Windows\n");
printf("Version: %d.%d\n", info.dwMajorVersion, info.dwMinorVersion);
printf("Build: %d\n", info.dwBuildNumber);
#elif __APPLE__
struct utsname sys_info;
uname(&sys_info);
printf("Platform: macOS\n");
printf("Name: %s\n", sys_info.sysname);
printf("Version: %s\n", sys_info.release);
#else
struct utsname sys_info;
uname(&sys_info);
printf("Platform: %s\n", sys_info.sysname);
printf("Name: %s\n", sys_info.nodename);
printf("Version: %s\n", sys_info.release);
#endif
}
int main() {
get_os_info();
return 0;
}
ここでは、JetBrains CLionなどを使用して os.c
のソースコードを開きます。 ソースコードを選択し、Chatプロンプトで /explain
を使用して目的や機能を説明します。次に、Chatプロンプトで /refactor
を使用してCコードをリファクタリングし、さらに /refactor into Rust
を使用してRustにリファクタリングします。
新しいRustプロジェクトを初期化(ヒント:Duo Chatに質問してみましょう)し、生成されたソースコードを src/main.rs
ファイルにコピーします。cargo build
を実行してコードをコンパイルします。
GitLab Duo Coffee Chat: Challenge - Refactor C into Rust の録画では、の録画では、すべての手順を確認できるほか、コンパイルエラーが発生し、それがChatと /refactor
スラッシュ(/) コマンドによって修正される様子もご覧になれます。また、このセッションでは、新しいRustコードのメンテナンス性を向上させるために、エラーハンドリングを追加する方法も紹介されています。
PerlをPythonにリファクタリング
本番環境サーバーでジョブを実行するスクリプトがありますが、作成者は10年前に退社しており、誰もそのスクリプトに手を付けたがりません。この問題は、複数のスクリプトや、さらにはアプリケーション全体にも影響している可能性があります。そこで、すべてをモダンなPython 3に移行する決定が下されました。ここでは、コードのモダナイゼーションを行い、PerlからPythonへの変更点を理解することを目標とします。
最近、GitLab Duoのワークショップに参加したお客様から、GitLab Duoを使って直接移行が可能かどうかという質問がありました。一言でお答えすれば、「可能」です。これを詳しく説明するならば、この記事にある他の例と同様に、洗練されたChatプロンプトを使用することで、PerlコードをPythonにリファクタリングすることができます。
script.pl
ソースコードをIDEで開き、選択して、Chatを開きます。
#!/usr/bin/perl
use strict;
use warnings;
open my $md_fh, '<', 'file.md' or die "Could not open file.md: $!";
my $l = 0;
my $e = 0;
my $h = 0;
while (my $line = <$md_fh>) {
$l++;
if ($line =~ /^\s*$/) {
$e++;
next;
}
if ($line =~ /^#+\s*(.+)/) {
print "$1\n";
$h++;
}
}
print "\nS:\n";
print "L: $l\n";
print "E: $e\n";
print "H: $h\n";
次のプロンプトを使用できます:
/explain
でその目的を説明させ、/refactor
でコードを改善させます。/refactor into Python
:実行可能なPythonスクリプトを取得します。
ヒント:Perlコードは、より多くのターゲット言語にリファクタリングできます。GitLab Duo Coffee Chat:チャレンジ - PerlからPythonへのリファクタリングの録画では、PHP、Ruby、Rust、Go、Java、VB.NET、C#などの言語も取り上げています。
Perlスクリプトの使用を継続するには、Duoのコード提案でPerlを追加言語として設定できます。ChatはすでにPerlを理解しており、質問やスラッシュ(/) コマンドのプロンプトにも対応できます。次の録画でこれについて確認できます。
その他のリファクタリング実践例
JavaScriptのリファクタリング
JavaScriptをリファクタリングしてコード品質を向上させたり、機能を追加したりする方法について、Eddie Jaoudeが実用的な例を交えて紹介しています。
BashをZSHやSHにリファクタリング
筆者はBashをShellとして20年間使用していましたが、最近になってmacOSのZSHに移行しました。この移行に伴い、スクリプトが機能しなくなったり、端末に不明なエラーが発生したりしました。リファクタリングの別のユースケースとして、Shellの制約があります。たとえば、一部のオペレーティングシステムやLinux/Unixディストリビューション(Alpineなど)では、Bashが提供されておらず、SHしか使用できません。
GitLab Duo Coffee Chat: Challenge - Refactor Shell Scripts では、syslogファイルを追跡するCプログラムと、Bashで記述されたビルドスクリプトの例が紹介されています。このチャレンジの中では、コードの改善を目的として、Chatに対して /explain
や /refactor
のプロンプトが使用されます。また、BashをPOSIX準拠のSHやZSHにリファクタリングすることもできます。セッションの最後では、Chatに5つの異なるShellスクリプトの実装を提供させ、そのサマリーについて説明するようリクエストしています。
その他のユースケースとチュートリアル
- ドキュメント:GitLab Duoのユースケース
- チュートリアル:AI搭載のGitLab Duoチャットを使用するためのベストプラクティス【10選】
- チュートリアル:AI搭載のGitLab Duoチャットを使用するためのベストプラクティス【10選】
重要なポイント
- GitLab Duoは、コードの説明やリファクタリングにおいて効率的なサポートを提供します。
- 言語標準間でコードをリファクタリングしたり、Chatで追加の質問をしたりできます。
- コード提案のプロンプトは、特定の言語標準に基づいたコードを生成できます。また、コード補完は現行のコードのコンテキストを尊重します。
- 新しいプログラミング言語へのリファクタリングは、長期的な移行プロセスやモダナイゼーションの計画に有効です。
- コードは、古いシステムがサポートする言語標準に「ダウングレード」することもできます。
- GitLab Duoは、複雑なコードやプログラミング言語について、異なるプログラミング言語の例を用いて説明できます。
- GitLab.comにおけるAnthropic Claude 3.5へのアップデートにより、コード提案およびChatの質と速度がさらに向上しました(Self-Managed版では17.3へのアップグレードを推奨しています)。
- 本番環境における問題がある場合を除き、ユースケースはアイデア次第で無限に広がります。
コード提案とChatを活用した効率的なワークフローについて理解を深めましょう。GitLab DuoのAI搭載コードリファクタリングを、今すぐお試しいただけます。
監修:知念 梨果 @rikachinen
(GitLab合同会社 カスタマーサクセス本部 カスタマーサクセスエンジニア)