20 KiB
开发规范
一、总体架构与设计原则
(一)MVVM 架构概述
在软件开发中,为实现良好代码结构、可维护性与可扩展性,应分离视图逻辑与业务逻辑,MVVM 设计模式是有效的解决方案。 View(视图层):包含用户界面控件与 XAML 元素,是用户与应用交互界面,负责展示数据和接收用户操作。应保持简洁,避免复杂业务逻辑,依据 ViewModel 数据展示,并将用户操作传递给 ViewModel 处理。 ViewModel(视图模型层):是视图层和模型层的桥梁,处理控件数据绑定,将视图层控件属性与自身属性绑定实现数据双向流动,还处理视图交互逻辑,调用模型层方法完成业务功能并反馈结果给视图层。 Model(模型层):处理数据和业务逻辑,包括数据获取、存储、处理和验证。对于复杂数据关系,可借助 ORM 技术或自定义数据结构;简单数据存储用普通类和属性。模型层独立于视图层和视图模型层,保证可复用性。 通过将控件属性绑定到 ViewModel,再由 ViewModel 与 Model 交互,实现数据和逻辑在各层有效流转,提高应用可维护性和扩展性。
二、具体实现细节
(一)ViewModel
1. 命令定义与实现
(1)命名规范
ViewModel 中的命令应采用 “动词 + 名词 + Command” 格式命名,清晰表明功能。对应的执行方法命名与命令中的动词一致,便于理解命令与执行逻辑的关联。
(2)代码位置
与视图交互的命令在对应的 ViewModel 类中定义和实现,ViewModel 类放在 “ViewModels” 文件夹下。大型项目中,按功能模块细分该文件夹,如 3D 模型相关的 ViewModel 放在 “ViewModels/3DModelViewModels” 文件夹。
(3)实现方式
使用合适的命令类实现:建议用实现 ICommand 接口的命令类,如自定义的 “RelayCommand”。它要处理执行逻辑和可执行状态判断,依据条件(如数据准备情况、用户权限)决定命令是否可执行。 命令初始化与方法绑定:在 ViewModel 构造函数中初始化命令并与执行方法绑定,如 “LoadModelCommand = new RelayCommand (LoadModel);”,确保命令触发时调用正确执行方法。 参数处理:命令执行需传递参数时,命令类要能正确处理参数传递和接收。修改命令类构造函数或执行方法以接受参数,在视图层绑定命令时设置参数值。
2. 数据绑定属性
(1)命名规范
数据绑定属性名称用驼峰命名法,清晰反映绑定的数据内容,如 “DisplayName” 绑定文本框显示文本,“DataList” 绑定列表数据。
(2)数据类型选择
根据绑定控件需求和数据性质选择合适数据类型。简单文本显示控件用 “string” 类型;列表控件用 “ObservableCollection” 等集合类型,实现数据更新自动刷新。
(3)通知机制
数据绑定属性值变化时,用合适机制通知视图更新。在支持数据绑定的框架(如 WPF)中,可利用相关接口(如 WPF 的 “INotifyPropertyChanged” 接口)。示例如下:
private string _displayName;
public string DisplayName
{
get { return _displayName; }
set
{
_displayName = value;
OnPropertyChanged(nameof(DisplayName));
}
}
属性 “set” 方法调用时,通过 “OnPropertyChanged” 方法通知视图层属性值变化,促使控件更新显示内容。
3. 事件处理与通知
(1)事件命名
ViewModel 中通知视图的事件命名采用 “名词 + 过去分词” 形式,表明动作完成或状态改变,如 “ModelLoaded” 表示模型加载完成,便于理解事件含义和业务逻辑状态。
(2)事件参数类型
事件参数类型准确反映事件携带的信息。如 “ModelLoaded” 事件传递加载后的模型对象,参数类型为 “Model3D”。若事件需传递多个信息,创建自定义事件参数类封装信息。
(3)事件触发
业务逻辑完成后(如模型加载成功、数据保存完成),在合适位置触发事件,用标准事件触发机制,如 “ModelLoaded?.Invoke (model);”。触发前确保事件初始化和订阅正确,避免空引用等问题。
(二)View
1. XAML 结构与布局
(1)命名空间引用
XAML 文件开头只引入实际使用的命名空间,按字母顺序排序,减少复杂性、提高编译速度,如: .xaml
<Window x:Class="YourNamespace.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:HelixToolkit="clr - namespace:HelixToolkit.Wpf;assembly=HelixToolkit.Wpf"
Title="3D Model Viewer" Height="450" Width="800">
(2)布局设计原则
选择合适的布局容器:优先用合适布局容器实现灵活可维护布局。简单水平或垂直排列控件用 “StackPanel”,通过 “Orientation” 属性控制排列方向。需精确行列布局用 “Grid”,通过定义行和列定位控件。 避免绝对坐标定位:尽量不用绝对坐标定位控件,以免界面在不同分辨率和窗口大小下适应性差。使用布局容器可实现自适应,保证良好显示效果。
(3)控件命名
为重要控件命名,采用驼峰命名法且以控件类型开头,如 “btnLoadModel”(加载模型按钮)、“txtInputFileName”(输入文件名文本框),提高代码可读性。复杂界面可分组控件并命名,方便在代码 - behind 中访问和操作,如 “LoginControls” 包含登录相关控件。
2. 数据绑定与命令绑定
(1)绑定表达式规范
数据绑定表达式:数据绑定用 “{Binding Path=PropertyName}” 格式,“Path” 可省略(绑定当前数据上下文直接属性时)。绑定深层属性需指定 “Path” 值,如: .xaml
<TextBlock Text="{Binding DisplayName}" />
<TextBlock Text="{Binding UserInfo.Age}" />
命令绑定表达式:命令绑定用 “{Binding CommandName}” 格式,确保命令在 ViewModel 数据上下文中存在,如: .xaml
<Button Content="Load Model" Command="{Binding LoadModelCommand}" />
(2)绑定源设置
在视图根元素(“Window”、“UserControl” 等)设置数据上下文,用 “DataContext” 属性绑定到 ViewModel 实例。可在 XAML 或代码 - behind 中设置。
XAML 中实例化 ViewModel:如: .xaml
<Window.DataContext>
<local:MyViewModel />
</Window.DataContext>
代码 - behind 中设置数据上下文:如:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MyViewModel();
}
}
3. 代码 - behind 使用规范
(1)减少逻辑复杂度
代码 - behind 文件(如 “MainWindow.xaml.cs”)主要处理视图特定初始化和事件订阅等与视图相关操作,避免放复杂业务逻辑或数据处理代码。将业务逻辑和数据处理放 ViewModel 层和模型层,保持视图层简洁,降低耦合度,便于维护和扩展。
(2)事件处理方法命名
代码 - behind 中处理视图事件的方法命名遵循 “控件名称 + 事件名称” 格式,如 “btnLoadModel_Click”,清晰反映事件来源和处理逻辑,提高代码可读性。
(3)资源释放与清理
视图关闭或不再使用时,在代码 - behind 中释放相关资源,如关闭文件流、取消定时器。妥善处理可能导致内存泄漏的对象(如事件订阅),可在合适事件处理方法(如 “Window_Closing”)中添加资源释放代码。
三、代码组织与项目结构
(一)项目文件夹结构
1. 根目录
项目文件:包含解决方案文件(.sln)管理项目文件和关系,项目配置文件(.csproj 等)描述项目设置(如引用、编译选项、目标框架)。 README 文件:简要介绍项目,包括功能、构建和运行步骤、主要依赖项(第三方库、框架版本及来源),方便其他开发人员了解项目。 LICENSE 文件:若项目开源或有特定授权许可,放相应许可证文件,明确使用权限和限制。
2. ViewModels 文件夹
存放所有 ViewModel 类,按功能模块细分子文件夹。每个子文件夹的 ViewModel 类功能围绕所属模块,提高内聚性和可维护性。
3. Views 文件夹
存放所有 XAML 视图文件,按功能模块或界面部分划分子文件夹,如 “MainViews” 放主窗口视图文件,“SettingsViews” 放设置界面视图文件,便于修改相关界面时定位视图文件。
4. Models 文件夹
存放所有模型类,按模型类型细分文件夹。如 “DatabaseModels” 放与数据库相关模型类(用数据库技术处理数据存储和检索),“InputModels” 放处理用户输入数据的模型类(封装、验证和处理用户输入),提高代码可读性和可维护性。
5. Services 文件夹
存放与外部服务交互的类,为模型层或视图模型层提供服务。如网络服务调用类处理网络通信,文件系统操作服务类处理文件操作,使外部交互逻辑清晰,便于维护和扩展。
6. Helpers 文件夹
放复用的辅助类和工具函数,如数据转换类、字符串处理工具类、通用数学计算类。避免代码重复,提高复用性和可维护性。
7. Resources 文件夹
存放项目资源文件,按类型细分文件夹。如 “Images” 放图片资源(用于界面装饰、图标显示),“Styles” 放样式文件(定义界面风格),若支持多语言,“Localization” 放本地化资源文件(实现国际化和本地化),方便管理和使用资源。
8. Language 文件夹
存放语言国际化资源文件,可分为en_US.resx、zh_CN.resx 等,用于实现国际化和本地化。
(二)代码文件结构
1. 每个类的结构
- 命名空间声明:代码文件开头声明类所属命名空间,反映类在项目中的位置和功能,如 “YourNamespace.ViewModels”、“YourNamespace.Models.DatabaseModels”,避免类名冲突,使代码结构清晰。
- 类的组织:类成员按功能和访问权限分组。先公共属性(按功能相关性排列),再公共方法(按功能逻辑排列),接着私有属性和私有方法(也按功能分组)。事件可放在方法后或根据与特定方法关联程度安排,便于理解成员关系和逻辑。
- 注释与文档:为类、方法、属性和重要代码块添加注释,解释功能、目的、参数和返回值(若有)。公共成员用 XML 文档注释格式,方便生成文档,
- 如:
/// <summary>
/// This class represents the ViewModel for the main window.
/// It handles the data binding and command logic related to the main window view.
/// </summary>
public class MyViewModel
{
//...
}
方法注释详细描述功能、输入参数含义和要求、返回值类型和意义,如:
/// <summary>
/// Loads the user data from the database.
/// </summary>
/// <param name="userID">The unique identifier of the user.</param>
/// <returns>An instance of UserData if the user exists, otherwise null.</returns>
public UserData LoadUserData(int userID)
{
//...
}
2. 代码依赖管理
尽量减少循环依赖:避免类之间的循环依赖(类 A 依赖类 B,类 B 又依赖类 A),这种情况会增加逻辑复杂性,导致编译和运行问题。出现循环依赖时,重新设计类结构或提取公共接口打破循环,如创建新抽象层或接口提取共同依赖部分。 依赖注入(如果适用):使用依赖注入框架时,遵循最佳实践管理对象依赖关系。在合适位置(如 ViewModel 构造函数)注入依赖对象,提高可测试性和可维护性。 例如:
private IDataService _dataService;
public MyViewModel(IDataService dataService)
{
_dataService = dataService;
//...
}
在测试 ViewModel 时可模拟数据服务类进行单元测试,修改数据服务类实现时不影响 ViewModel 代码(保持接口一致)。使用依赖注入框架时,正确配置和管理依赖关系(注册依赖项、解析依赖对象等)。
四、其他开发要点
(一)命名规范
1. 参数命名
采用驼峰命名法,应具有明确表意性,反映参数含义与用途。例如,代表用户 ID 的参数可命名为 “userID”,表示文件路径的参数命名为 “filePath”。
2. 变量命名
遵循驼峰命名法,依据变量存储的数据特性命名。如存储用户名的变量为 “userName”,用于计数的变量可叫 “count”,布尔类型变量依其代表的逻辑状态取名,如 “isVisible” 表示是否可见。
3. 方法命名
采用动词开头的驼峰命名法,清晰传达方法功能。如 “CalculateSum” 用于计算总和,“LoadDataFromFile” 表示从文件加载数据,“ValidateUserInput” 用于验证用户输入。
(二)全局异常捕获
在应用程序启动点(如 App.xaml.cs 中的启动方法)设置全局异常捕获机制,使用 try-catch 块包裹关键代码区域。 例如:
try
{
// 启动应用程序的核心代码,如初始化主窗口、加载配置等
this.StartupUri = new Uri("MainWindow.xaml", UriKind.Relative);
}
catch (Exception ex)
{
// 记录异常信息至日志文件,可使用日志处理模块(参考 MainWindow 中的用法)
Logger.LogError($"全局异常捕获:{ex.Message}", ex);
// 可根据异常类型进行友好提示或采取恢复措施,如显示错误对话框告知用户
MessageBox.Show($"应用程序出现错误:{ex.Message}");
}
此机制能捕获未在局部处理的异常,防止程序崩溃,提升稳定性与用户体验,同时利于通过日志排查问题根源。
(三)国际化调用传参变量方案
在资源文件中,与键dynamicTextKey对应的文本模板可设计为占位符形式,如"{var1} 完成了 {var2} 操作"。MultilingualHelper.getString方法内部实现逻辑将解析键值对参数,替换文本模板中的占位符,从而精准生成符合当前语言环境及动态数据的国际化文本。 通过这种方式,无论在视图层绑定文本属性还是业务逻辑中生成提示信息等场景,只需传入相应键值对参数,即可轻松实现多语言文本动态化,提升国际化功能的适应性与易用性,确保不同语言用户获取准确、个性化信息,优化整体用户体验。
(四)日志处理
在项目中统一日志处理方式,如参考 MainWindow 中的用法,创建日志记录工具类 “Logger”。在关键业务逻辑点(如数据读写、外部服务调用、重要操作执行前后)记录日志,依操作类型与重要性分级别(DEBUG、INFO、WARN、ERROR)。 例如:
public static void LogInfo(string message)
{
// 使用日志框架(如 NLog、log4net)记录 INFO 级别日志,含时间戳、日志级别、消息内容
// 配置日志输出目标(文件、控制台等)与格式,如文件按日期命名滚动存储
}
public static void LogError(string message, Exception ex)
{
// 记录 ERROR 级别日志,除消息外附加异常堆栈信息,助于故障排查与问题追踪
}
(五)代码分析警告清除
定期用代码分析工具(如 Visual Studio 内置分析器)检查项目,依警告类型处理。对于未使用变量或导入命名空间,精准判断必要性后清理;可空性警告依实际业务逻辑处理,通过空值检查、设置默认值或调整类型解决; 代码风格类警告依既定规范统一格式(如大括号换行、缩进空格数),确保代码一致性与可读性,规避潜在问题,提升代码质量与可维护性。
(六)初始化 DB(SQLite)
在项目启动流程中(例如在 App.xaml.cs 的启动阶段)嵌入 SQLite 数据库初始化逻辑。首先,引入 SQLite 相关命名空间:using System.Data.SQLite;。接着,在启动方法内构建数据库连接字符串, 假设数据库文件存于项目根目录下名为 “myDatabase.db”,则连接字符串示例为string connectionString = "Data Source=myDatabase.db;Version=3;";。随后,使用该连接字符串创建数据库连接并打开:
using (SQLiteConnection connection = new SQLiteConnection(connectionString))
{
connection.Open();
// 在此处执行数据库初始化操作,如创建表结构
string createTableQuery = "CREATE TABLE IF NOT EXISTS MyTable (Id INTEGER PRIMARY KEY AUTOINCREMENT, Name TEXT)";
using (SQLiteCommand command = new SQLiteCommand(createTableQuery, connection))
{
command.ExecuteNonQuery();
}
// 若有初始化种子数据需求,可在此处插入数据,如下示例插入一条记录
string insertSeedDataQuery = "INSERT INTO MyTable (Name) VALUES ('Initial Data')";
using (SQLiteCommand command = new SQLiteCommand(insertSeedDataQuery, connection))
{
command.ExecuteNonQuery();
}
}
通过上述步骤,确保 SQLite 数据库在应用启动时完成初始化,创建必要表结构并填充初始数据,为数据访问层提供坚实基础,保障数据操作流畅性与应用功能完整性。
(七)页面自适应
为实现页面在不同设备与分辨率下的自适应,布局设计遵循响应式原则。在布局容器运用上,除前文提及的 “StackPanel” 与 “Grid”,对于 “Grid” 布局,除设置列宽为 “”(按比例分配空间)、“Auto”(自动适应内容)组合外,还可搭配 “MinWidth” 和 “MaxWidth” 属性,精准限定列宽范围,确保内容合理展示且布局稳定。如重要信息列可设 “MinWidth” 保障可读性,弹性列用 “” 分配剩余空间,防止极端尺寸下布局错乱。 针对文本类控件,“TextBlock” 的 “TextWrapping” 设为 “Wrap” 实现自动换行,“MaxWidth” 依父容器或屏幕宽度动态调整,确保长文本不溢出、排版美观。图像类控件 “Image” 的 “Stretch” 属性依场景设为 “Uniform”(等比缩放填满)、“Fill”(拉伸填满)或 “None”(保持原始尺寸),配合 “MaxWidth”“MaxHeight” 于不同分辨率下维持图像质量与布局平衡。 运用尺寸自适应触发器,依屏幕宽度阈值切换布局样式或隐藏 / 显示控件。如窗口宽度小于 600px 时,将两列 “Grid” 布局切换为单列 “StackPanel”,重新排列控件顺序、调整间距与字体大小,提升移动设备或小屏幕浏览体验,实现全场景优质交互与视觉呈现。
(八)错误码常量类
创建统一的错误码常量类ErrorCodes,用于集中管理应用程序中的错误码。该类应位于项目的合适位置,例如Helpers文件夹下,以提高可维护性和复用性。 在ErrorCodes类中,使用public const关键字定义错误码常量,常量名应具有清晰的语义,能够直观反映错误类型。 例如:
public static class ErrorCodes
{
// 数据库连接错误
public const int DB_CONNECTION_ERROR = 1001;
// 数据验证失败错误
public const int DATA_VALIDATION_FAILED = 1002;
// 文件读取错误
public const int FILE_READ_ERROR = 1003;
// 用户认证失败错误
public const int USER_AUTHENTICATION_FAILED = 1004;
// 网络请求超时错误
public const int NETWORK_REQUEST_TIMEOUT = 1005;
}
在整个项目中,当发生相应错误时,统一使用这些错误码常量进行标识和传递。例如,在数据库访问层捕获到连接错误时,返回ErrorCodes.DB_CONNECTION_ERROR;在数据验证模块验证失败时,返回ErrorCodes.DATA_VALIDATION_FAILED。 在处理错误的地方(如视图模型中的错误处理逻辑、全局异常捕获处),依据接收到的错误码常量执行特定的错误处理策略,如显示友好错误提示信息给用户、记录详细错误日志以供排查、进行错误恢复尝试或引导用户采取正确操作等,提升错误处理的规范性与可维护性,增强应用程序的健壮性与用户体验。