You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
274 lines
20 KiB
274 lines
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<T>” 等集合类型,实现数据更新自动刷新。 |
|
##### (3)通知机制 |
|
数据绑定属性值变化时,用合适机制通知视图更新。在支持数据绑定的框架(如 WPF)中,可利用相关接口(如 WPF 的 “INotifyPropertyChanged” 接口)。示例如下: |
|
```csharp |
|
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 |
|
|
|
```csharp |
|
<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 |
|
|
|
```csharp |
|
<TextBlock Text="{Binding DisplayName}" /> |
|
<TextBlock Text="{Binding UserInfo.Age}" /> |
|
``` |
|
命令绑定表达式:命令绑定用 “{Binding CommandName}” 格式,确保命令在 ViewModel 数据上下文中存在,如: |
|
.xaml |
|
|
|
```csharp |
|
<Button Content="Load Model" Command="{Binding LoadModelCommand}" /> |
|
``` |
|
##### (2)绑定源设置 |
|
在视图根元素(“Window”、“UserControl” 等)设置数据上下文,用 “DataContext” 属性绑定到 ViewModel 实例。可在 XAML 或代码 - behind 中设置。 |
|
|
|
XAML 中实例化 ViewModel:如: |
|
.xaml |
|
|
|
```csharp |
|
<Window.DataContext> |
|
<local:MyViewModel /> |
|
</Window.DataContext> |
|
``` |
|
代码 - behind 中设置数据上下文:如: |
|
```csharp |
|
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 文档注释格式,方便生成文档, |
|
- 如: |
|
```csharp |
|
/// <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 |
|
{ |
|
//... |
|
} |
|
``` |
|
|
|
|
|
方法注释详细描述功能、输入参数含义和要求、返回值类型和意义,如: |
|
```csharp |
|
/// <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 构造函数)注入依赖对象,提高可测试性和可维护性。 |
|
例如: |
|
```csharp |
|
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 块包裹关键代码区域。 |
|
例如: |
|
```csharp |
|
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)。 |
|
例如: |
|
```csharp |
|
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;";。随后,使用该连接字符串创建数据库连接并打开: |
|
```csharp |
|
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关键字定义错误码常量,常量名应具有清晰的语义,能够直观反映错误类型。 |
|
例如: |
|
```csharp |
|
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。 |
|
在处理错误的地方(如视图模型中的错误处理逻辑、全局异常捕获处),依据接收到的错误码常量执行特定的错误处理策略,如显示友好错误提示信息给用户、记录详细错误日志以供排查、进行错误恢复尝试或引导用户采取正确操作等,提升错误处理的规范性与可维护性,增强应用程序的健壮性与用户体验。 |