原項目介紹與分析
項目介紹
這是一個學生成績管理系統,該系統在其開發者手中時原計劃是要實現包括增加、刪除、修改學生和研究生的成績并進行一個統一管理,但因為時間原因匆匆了事,開發者僅僅實現了其中的增和刪減功能,以及完成了一個保存數據的半成品,據原開發者所說是在優化代碼的時候可能把關鍵代碼優化掉了,但沒有精力再次復查,便只殘存了一個辦成品,該項目現被交接到了我的手上進行逆向軟件設計和開發的練手。
項目來源
在大一上學期我室友練手用的一個小項目,但其中功能較少,且創新性不足,對于使用者來說非常不友好,不僅如此,程序在使用過后因為某些原因無法保存到電腦文件,導致每次打開程序都需要重新輸入成績,非常不便,但該程序又承載者這位朋友的一些青春回憶,所以希望我能夠將其進一步完善。
原先項目分析
根據我與原開發者的溝通,我畫出了他心目中該項目應該有的樣子(圖1-1)

圖1-1
但可惜的是,他所完成的不過是其中的一半都不到,僅僅有包含GStudent、Student、removeGStudent、removeStudent的四個類,以及半成品的loadFromFile和saveToFile這五個類,當時他所偷的懶可見一斑,當時他的系統運行起來是這樣的(圖1-2)

圖1-2
沒有加載和保存數據選項是因為這串代碼的存在會讓系統出現編譯錯誤,甚至無法運行,所以將其暫時擱置,等待后期進行重新添加。
在正常情況下,該項目運行時是這樣的(圖1-3)

非常的簡單,簡單到不會出現任何錯誤,讓人放心,但也讓使用者非常難受,這更堅定了我進行修改的決心。
項目改進
1.對于該項目功能的增加設計
首先,原先系統的內容非常空洞和單薄,功能單一,完全無法滿足作為學生成績管理系統的需求,所以在項目的逆向開發中,我重新設計了該項目的構架和功能設定,并為此增加了修改學生成績的功能,將學生總分進行排序,并增加了顯示所有學生信息的操作,完美實現了開發者原先想這個系統成為的樣子(圖1-1)在系統運行后,菜單頁面變成了如圖(圖2-1)

接下來我將分別介紹其中的改進
1.1 修改學生
// 修改學生信息
void Class::modifyStudent(int studentNumber, const Student& newStudent) {
for (auto& student : students) {
if (student.studentNumber == studentNumber) {
student = newStudent;
break;
}
}
}

1.2 修改研究生
// 修改研究生信息
void Class::modifyGStudent(int studentNumber, const GStudent& newGStudent) {
for (auto& gstudent : gstudents) {
if (gstudent.studentNumber == studentNumber) {
gstudent = newGStudent;
break;
}
}
}

1.3 按總分排序學生并顯示
// 學生按總成績排序,從高到低
void Class::sortStudents() {
sort(students.begin(), students.end(),
[](const Student& a, const Student& b) { return a.getTotalScore() > b.getTotalScore(); });
sort(gstudents.begin(), gstudents.end(),
[](const GStudent& a, const GStudent& b) { return a.getTotalScore() > b.getTotalScore(); });
}

1.4顯示所有學生
// 顯示學生列表
void Class::display() {
for (const auto& student : students) {
cout << student << endl;
}
for (const auto& gstudent : gstudents) {
cout << gstudent << endl;
}
}

2.對于該項目功能的修正設計:
在這個項目拿到手的時候,該系統無法正常將數據保存到文件,以及從文件加載數據,在仔細閱讀了相對應的代碼后,我找到了代碼其中出現的邏輯錯誤,并進行改進,具體過程如下:
原功能出錯分析:
原代碼:
// 從文件加載數據
void Class::loadFromFile( string filename ) {
ifstream infile(filename);
if (infile.is_open()) {
students.clear();
gstudents.clear();
string type;
while (infile >> type) {
if (type == "S") {
string name;
int studentNumber;
infile >> name >> studentNumber;
Student student(name, studentNumber);
int subjectCount;
infile >> subjectCount;
for (int i = 0; i < subjectCount; ++i) {
string subject;
float score;
infile >> subject >> score;
student.addScore(Score(subject, score));
}
students.push_back(student);
}
else if (type == "G") {
string name;
int studentNumber;
string projectName;
infile >> name >> studentNumber >> projectName;
GStudent gstudent(name, studentNumber, projectName);
int subjectCount;
infile >> subjectCount;
for (int i = 0; i < subjectCount; ++i) {
string subject;
float score;
infile >> subject >> score;
gstudent.addScore(Score(subject, score));
}
gstudents.push_back(gstudent);
}
}
infile.close();
}
else {
cout << "打開文件錯誤。" << endl;
}
}
在我看來,他的代碼邏輯上基本正確,并無大礙,但其中如果文件的類型不符合預期,程序可能會崩潰,并且在調用 students.clear() 和 gstudents.clear() 之前,如果容器中已經存在數據,可能會導致內存泄漏或不必要的資源占用,這些都是需要改進的點,且他的代碼有一些冗余之處,所以我對代碼進行了修改如下:
// 從文件加載數據
void Class::loadFromFile(const string& filename) {
ifstream infile(filename);
if (!infile.is_open()) {
cout << "打開文件錯誤: " << filename << endl;
return;
}
// 清空現有數據
students.clear();
gstudents.clear();
string type;
while (infile >> type) {
if (type == "S") {
string name;
int studentNumber;
if (!(infile >> name >> studentNumber)) {
cerr << "讀取學生信息失敗。" << endl;
break;
}
Student student(name, studentNumber);
int subjectCount;
if (!(infile >> subjectCount)) {
cerr << "讀取學生科目數量失敗。" << endl;
break;
}
for (int i = 0; i < subjectCount; ++i) {
string subject;
float score;
if (!(infile >> subject >> score)) {
cerr << "讀取學生科目成績失敗。" << endl;
break;
}
student.addScore(Score(subject, score));
}
students.push_back(student);
}
else if (type == "G") {
string name;
int studentNumber;
string projectName;
if (!(infile >> name >> studentNumber >> projectName)) {
cerr << "讀取研究生信息失敗。" << endl;
break;
}
GStudent gstudent(name, studentNumber, projectName);
int subjectCount;
if (!(infile >> subjectCount)) {
cerr << "讀取研究生科目數量失敗。" << endl;
break;
}
for (int i = 0; i < subjectCount; ++i) {
string subject;
float score;
if (!(infile >> subject >> score)) {
cerr << "讀取研究生科目成績失敗。" << endl;
break;
}
gstudent.addScore(Score(subject, score));
}
gstudents.push_back(gstudent);
}
else {
cerr << "未知類型: " << type << endl;
break;
}
}
infile.close();
}
其次,對于保存數據文件,他的代碼有些難以閱讀,因為使用了一些奇怪的類,詢問本人表示他也不記得之前是怎么思考和構思的了,在此基礎上,我在保證總體框架不進行較大變動的同時,進行代碼重構和改進,修正后的代碼如下:
// 保存數據到文件
void Class::saveToFile(string filename) {
ofstream outfile(filename);
if (outfile.is_open()) {
for (const auto& student : students) {
outfile << "S " << student.name << " " << student.studentNumber << " " << student.scores.size();
for (const auto& score : student.scores) {
outfile << " " << score.subject << " " << score.score;
}
outfile << endl;
}
for (const auto& gstudent : gstudents) {
outfile << "G " << gstudent.name << " " << gstudent.studentNumber << " " << gstudent.projectName << " " << gstudent.scores.size();
for (const auto& score : gstudent.scores) {
outfile << " " << score.subject << " " << score.score;
}
outfile << endl;
}
outfile.close();
}
else {
cout << "打開文件錯誤。" << endl;
}
}
在代碼中,我使用 ofstream 打開指定文件 filename,準備寫入數據,如果文件打開失敗,輸出錯誤信息 "打開文件錯誤。"對于分別保存普通學生數據和研究生學生的數據,需要遍歷 students和gstudents容器中的每個學生。將學生的類型標識 "S"、姓名 name、學號 studentNumber 和成績數量 scores.size() 寫入文件,遍歷學生的成績列表 scores,將每門課程的名稱 subject 和分數 score 寫入文件;將研究生的類型標識 "G"、姓名 name、學號 studentNumber、研究項目名稱 projectName 和成績數量 scores.size() 寫入文件,遍歷研究生的成績列表 scores,將每門課程的名稱 subject 和分數 score 寫入文件,在最后使用 outfile.close() 關閉文件。
功能示例:


實驗總結:
本次逆向開發的目標是對已有的學生成績管理系統進行深入分析,理解其功能實現、數據結構及代碼邏輯,并在此基礎上進行優化、擴展或修復問題。通過逆向工程,我們能夠更好地掌握系統的設計思路和技術細節,為后續的維護和升級提供支持。通過本次逆向開發,我們不僅修復了原有系統的問題,還為其未來的擴展和優化奠定了基礎。逆向工程是一項復雜但極具價值的工作,它幫助我們更好地理解現有系統的設計思路,并為后續開發提供了寶貴經驗。未來,我們將繼續完善系統功能,提升其性能和用戶體驗,使其更好地服務于學生成績管理的需求。
浙公網安備 33010602011771號