题目:实现一个图书管理系统。需求如下:
- 当用户启动程序时,可看到欢迎信息;
- 程序启动时,用户可以看到一列菜单选项;
- 用户可以选择菜单
- 当用户选择无效时提示“请输入有效选项”
- 用户可以看到图书馆中所有书的列表
- 用户可以收藏一本书
- 如果书籍收藏成功,提示“谢谢!祝您阅读愉快!
- 如果无法提供用户想收藏的书籍,提示“抱歉,我们没有您所需要的书。“
- 用户可以查看帐户信息。目前,用户选择该项功能时显示”请咨询管理员“
1、分包不当。
源代码的目录结构可以反映出程序的设计,分包不当也就意味着程序在设计时逻辑不清晰。
我把系统分成两个部分:domain和control。 domain里是“名词性”的类,control里是与业务操作相关的类(by the way, control拼错了)。
domain中的BookGallary负责从txt文件中读取书名,然后生成一个Book的List,它是对外部资源的访问。把BookGallary和其它 "名词"放在domain里,是不合适的。
Console负责在控制台上显示并获得用户的指令,是程序和用户之间的交互,相当于mvc里的view,放在control里也确实不太合群。
Description类里全是constant的字符串,用以保存系统的提示信息,跟control没什么关系。
总之,在第一版中,各司其职的类都聚在control里,显得混乱。
于是,删掉重来。第二版程序的结构看起来更清楚了。
2、TDD
测试驱动开发有很多好处。TDD让我在写代码之前能明白自己想做什么
(view这个包里的类就是TDD的产物,因为控制台的输出真心不好测啊。。。)。TDD过程中,mock是必不可少的。如果把写代码比作修建房屋的话,那么mock可以让我从顶楼修起。这样,事情就变得简单了——只专注于一件事。
这里有一个细节。需要访问外部资源的类应该怎么测?比如BookLoader(BookGallary的翻版)这个类,它可以从txt文件中获取书目,并生成Book的List。我之前是在自己电脑上创建了一个txt文件,然后在测试里通过文件路径(我这里是:"D:\books.txt")去访问。这样有诸多弊端,比如在别人电脑上跑测试时,还要重新手工建一个文件。这样不方便,而且还让测试依赖于外部资源。最终的解决方案是在测试的里用代码创建测试用的txt文件。然后,我可以不用操这份心了~
package com.twu.biblioteca.loader;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
public class BookLoaderTest {
private File file;
@Before
public void setUp() {
file = new File("books.txt");
createTextFile(file);
}
private void createTextFile(File file) {
if (file.exists()) {
file.delete();
}
try {
file.createNewFile();
writeBooksInTextFile(file);
} catch (IOException e) {
e.printStackTrace();
}
}
private void writeBooksInTextFile(File file) throws IOException {
BufferedWriter output = new BufferedWriter(new FileWriter(file));
output.write("Head First Java\n");
output.write("Refactoring");
output.close();
}
@Test
public void should_load_books_from_text_file() throws Exception {
BookLoader bookLoader = new BookLoader();
bookLoader.loadBooksByTextFile("books.txt");
assertThat(bookLoader.getBookList().size(), is(2));
}
@After
public void tearDown() throws Exception {
file.delete();
}
}