你已经是面向模型开发人员






4.95/5 (11投票s)
识别你已经使用的一些面向模型的技术,并将这些技术置于你可以利用的整体面向模型技术背景下。
引言
所有有用的软件,无论用什么语言、技术或流程来创建,都是为了满足特定领域的需求。我将简单地称这种需求为领域愿景。我们需要从当前情况,即当前领域,转向满足这一需求。新的需求可能涉及从头开始创建一个新系统、替换现有系统,或者仅仅是添加一个功能,这在本次讨论中无关紧要。
理想情况下,当我们开发新软件时,我们会有效地、高效地沿着一条短路径前进,朝着清晰的愿景。更常见的是,我们在实现一个一开始就不清晰,或者在前进过程中显著演变的愿景的长路径中会遇到一些挫折。最糟糕的情况是,我们沿着一条死胡同路径开发软件,最终却偏离了愿景。
作为软件开发人员,我们努力尽可能地接近短而有效的路径,有许多技术、流程和工具可以帮助我们做到这一点。本文的重点是属于面向模型开发
的技术集合。你已经是面向模型开发人员了!你无疑已经利用了这里概述的一些技术。本文的目的是将你已经使用的技术置于你可能觉得有用的整体技术背景下。
面向模型开发
面向模型开发(MOD)是一种技术,它允许你在开发过程中的任何阶段使用模型进行开发。你将模型信息转换为源代码和/或在开发过程中直接利用模型。这并不意味着任何特定的流程或技术。你如何表示和利用你的模型完全取决于你。
考虑以下图表。任何形式的MOD都可以帮助你更清晰地定义愿景,进一步用想法、需求、设计和最终代码来表示。
本文将涵盖MOD的四种基本形式:非正式、内联、耦合和解耦。这些不是行业标准术语,因为我没有见过这些形式的标准术语。我列出了一些属于这些形式的技术和工具,它们可能你都很熟悉。
在继续之前,我想强调两点。第一,我并不是说应该遵循瀑布或Scrum等特定流程,我也不打算在本文中涵盖任何形式的流程。想法会发展为需求、设计和代码,这当然是事实,无论你是在迭代地解决领域中的小问题,还是试图分阶段解决整个领域。第二,你选择使用的MOD形式不是非此即彼的选择。在从想法到代码的任何地方,利用来自多种形式的技术通常都非常有益。
我将在下面的章节中描述每种MOD形式,并稍微侧重于模型如何表示和利用。我将根据我在初创公司环境中工作的主要经验和观察,概述一些优缺点,并注入我个人的偏好,即“一点或很多”。我认为每种形式都可以非常有益,并且每种形式在特定情况下如果过度使用都可能变得负担沉重。
非正式面向模型开发
当你脑海中有一个关于你需要编写的软件的想法时,那个想法就是你需要编写的代码的一种抽象表示,以实现你需要的愿景。那个想法就是一个模型!
当你在一个团队中为共同的愿景工作时,你需要沟通和分享你的想法,直到达成共识,每个人都说同一种语言。你可能会进行白板讨论,并以团队理解和同意的术语表示或记录这些想法。你可能会进一步将这些想法阐述为需求甚至设计理念。如果你做了这些,你就是一名面向模型开发人员,利用非正式技术来阐述你的模型。
非正式MOD可以大致定义为
- 在源代码之外拥有一个临时的模型定义。模型可能只是脑海中的想法、书面描述、图画或你自己设计或标准的图表。
- 没有任何软件工具可以解释你的模型定义。
- 没有任何软件工具可以转换模型信息为源代码。你手动将信息转换为代码。
举例来说,我们的愿景是一个小型酒店管理网站,在那里我们可以跟踪入住我们酒店的客人。下面是一个小的白板图,它模拟了基本想法和需求。
优点
非正式MOD的主要优点包括
- 想法——这项技术可能是最初讨论想法的最佳方法,而不会受到必须将想法表示为正式模型的束缚。
- 需求——如果愿景是针对相对简单的领域和/或易于通过可运行软件观察的领域(即没有复杂的底层规则和行为),那么定义和使用模型的非正式方法应该就可以了。
- 设计和代码——设计和编写代码很容易,而不会受到模型定义的束缚。自定义代码同样容易。
- 团队——非正式方法非常适合小型团队,在那里达成共识的工作量相对较小。
缺点
非正式MOD的主要缺点包括
- 需求——对于更复杂的领域,缺乏正式模型可能会使验证需求和确保需求一致性更加困难。这会增加实现愿景的长路径的风险,甚至更糟的是,如果团队意识到软件与愿景不符,则会踏上死胡同路径。
- 设计和代码——根据领域的复杂性,在没有更正式模型背景的情况下有机地设计和编码可能会导致代码重复增加和/或需要重构代码,从而延长实现愿景的路径。
- 团队——随着团队规模的扩大和地理上的分散,非正式方法开始失效。
测试和维护
非正式MOD通常不对测试和维护提供任何特殊的好处。如果非正式模型有图示和书面记录,它们通常只用于初步理解。与代码一样,编写测试很容易。对于下游的更改,你通常必须依赖遗留代码和测试用例的质量来理解原始意图以及更改将如何影响它们。
一点还是很多?
我总是至少使用一点这种MOD形式,尤其是在讨论初步想法和编码过程中。对于小型团队和小型简单项目,我大量使用非正式MOD。
内联面向模型开发
随着许多框架的出现和使用,MOD的内联形式如今非常流行,这些框架极大地帮助你有效地设计和开发应用程序。内联MOD可以大致定义为
- 在源代码中定义了一个正式的模型。该模型通常与应用程序的特定层相关。
- 有一个框架可以解释和利用你的正式模型定义。该框架有支持软件,允许你利用模型,和/或提供一个机制来为你转换(生成)部分软件。
内联MOD的一个很好的例子是ORM,如Entity Framework、NHibernate或Dapper。使用ORM,你在代码中定义你的模型,和/或从数据库定义加载它,然后框架通常会管理你的数据库模式(有时会生成存储过程)。ORM通常会使你能够用最少的额外代码轻松地进行事务性数据管理。 VITA 是一个特别强大的ORM,用于管理你的数据并提供额外的构建块,例如基于你的模型的身份验证规则。下面是我们小型酒店管理系统的VITA模型定义示例
[Entity(Name="Hotel", TableName="Hotel")]
[Paged, OrderBy("Name")]
public partial interface IHotel
{
[Column("Id"), PrimaryKey, ClusteredIndex(IndexName="PK_Hotel"), Auto]
Guid Id { get; set; }
[Column("Name", Size = 50), Index(IndexName="IX_Name")]
string Name { get; set; }
IList<IRoom> HotelRooms { get; }
}
[Entity(Name="Room", TableName="Room")]
[Paged]
public partial interface IRoom
{
[Column("Id"), PrimaryKey, ClusteredIndex(IndexName="PK_Room"), Auto]
Guid Id { get; set; }
[Column("Number"), Index(IndexName="IX_Number")]
int Number { get; set; }
[Column("MaxOccupancy")]
int MaxOccupancy { get; set; }
[EntityRef(KeyColumns = "BuildingId")]
IBuilding Building { get; set; }
}
[Entity(Name="Guest", TableName="Guest")]
[Paged, OrderBy("Name")]
public partial interface IGuest
{
[Column("Id"), PrimaryKey, ClusteredIndex(IndexName="PK_Guest"), Auto]
Guid Id { get; set; }
[Column("Name", Size = 50), Index(IndexName="IX_Name")]
string Name { get; set; }
[Column("Email", Size = 150), Index(IndexName="IX_Email")]
string Email { get; set; }
IList<IReservation> GuestReservations { get; }
}
[Entity(Name="Reservation", TableName="Reservation")]
[Paged]
public partial interface IReservation
{
[Column("Id"), PrimaryKey, ClusteredIndex(IndexName="PK_Reservation"), Auto]
Guid Id { get; set; }
[Column("ArrivalDate"), Index(IndexName="IX_ArrivalDate")]
DateTime ArrivalDate { get; set; }
[Column("DepartureDate"), Index(IndexName="IX_DepartureDate")]
DateTime DepartureDate { get; set; }
[Column("NumberOfOccupants"), Index(IndexName="IX_NumberOfOccupants")]
int NumberOfOccupants { get; set; }
[Column("Rate"), Index(IndexName="IX_Rate")]
decimal Rate { get; set; }
[EntityRef(KeyColumns = "GuestId")]
IGuest Guest { get; set; }
[EntityRef(KeyColumns = "RoomId")]
IRoom Room { get; set; }
}
许多面向UI的框架建立在MVC(或MVwhatever)设计模式之上,当然模型是其中的核心。框架完成了大部分工作,使你能够轻松地使用你的模型与视图绑定、验证、更新服务器上的数据等。Backbone.js就是一个模型相当突出的例子,下面是一个模型定义的示例(只添加了一点验证)
var Room = Backbone.Model.extend({
defaults: {
Id: "",
Number: 0,
MaxOccupancy: 0
},
validate: function (attr) {
if (!attr.Number <= 0) {
return "Invalid Room Number supplied."
}
}
});
var Rooms = Backbone.Collection.extend({
model: Room
});
var Hotel = Backbone.Model.extend({
defaults: {
Id: "",
Name: "",
HotelRooms: new Rooms()
},
validate: function (attr) {
if (!attr.Name) {
return "Invalid Hotel Name supplied."
}
}
});
var Reservation = Backbone.Model.extend({
defaults: {
Id: "",
GuestId: "",
RoomId: "",
ArrivalDate: undefined,
DepartureDate: undefined,
NumberOfOccupants: 0,
Rate: 0
},
validate: function (attr) {
if (!attr.NumberOfOccupants <= 0) {
return "Invalid Number Of Occupants supplied."
}
}
});
var Reservations = Backbone.Collection.extend({
model: Reservation
});
var Guest = Backbone.Model.extend({
defaults: {
Id: "",
Name: "",
Email: "",
GuestReservations: new Reservations()
},
validate: function (attr) {
if (!attr.Name) { return "Invalid Guest Name supplied."
}
}
});
优点
内联MOD的主要优点包括
- 设计和代码——框架提供了许多好处
- 模型在代码中易于定义和理解。
- 让开发人员免于编写支持性代码来利用模型。
- 有助于强制执行围绕模型使用的标准实践。
- 团队——利用框架来实现通用标准,无论团队大小都能很好地协同工作。
缺点
内联MOD的主要缺点包括
- 想法和需求——这种技术不太适合阐述或传达想法和需求(尤其是对非开发人员)。最好为想法和需求使用不同的MOD技术。
- 设计和代码——如果使用过多的框架,且各自有重复的模型定义,那么保持这些模型同步可能会变得很麻烦。
测试和维护
内联MOD通常对测试和维护提供至少一些好处,具体取决于所使用的框架。至少,框架的模型为理解先前意图和更改影响提供了一些背景。需要编写的代码更少和/或由你(重新)生成一些样板代码可以降低维护成本。一些框架具有内置功能(例如Entity Framework迁移)来帮助维护和部署更改。
一点还是很多?
如果系统的任何层的技术要求和团队最佳实践都倾向于使用框架,我赞成在设计和编码中使用大量这种MOD形式。
耦合面向模型开发
MOD的耦合形式可能在20世纪90年代和21世纪初更为流行,当时有多种建模方法最终形成了UML标准,并围绕该标准演化出了许多流程和工具。耦合MOD可以大致定义为
- 在源代码之外定义了一个正式的模型。
- 模型元素与你的源代码之间存在很强的相关性(通常是一对一)。因此,模型包含了软件设计的元素。
- 有一些软件工具可以解释你的模型定义。
- 有一些软件工具可以转换模型信息为源代码。
许多UML图旨在阐明系统的设计,技术上讲,模拟用户和一般抽象和行为的UML图应该属于后面将要介绍的解耦MOD形式。下面是一个类模型,它概述了我们小型酒店管理愿景的类和接口设计。
将这种方法应用于代码,有许多工具可以从UML模型生成代码。一些著名的例子包括Rational Software Architect、Rational Rhapsody和Altova。请注意,一些UML代码生成工具,如Visual Studio类设计器或Umple,属于内联MOD形式。
我选择了Altova来创建一些模型并从中生成代码。Altova似乎和许多耦合MOD工具一样好。这是Altova中的类图。
这是从上述模型生成的一些代码。
public interface IHotel
{
System.Guid Id
{
set;
get;
}
System.String Name
{
set;
get;
}
}
public class Hotel : IHotel
{
public Room[] HotelRooms;
public System.Guid Id
{
set{}
get{}
}
public System.String Name
{
set{}
get{}
}
}
public interface IRoom
{
Guid Id
{
set;
get;
}
int Number
{
set;
get;
}
int MaxOccupancy
{
set;
get;
}
}
public class Room : IRoom
{
private Reservation[] RoomReservations;
public Guid Id
{
set{}
get{}
}
public int Number
{
set{}
get{}
}
public int MaxOccupancy
{
set{}
get{}
}
}
public interface IGuest
{
Guid Id
{
set;
get;
}
String Name
{
set;
get;
}
String Email
{
set;
get;
}
}
public class Guest : IGuest
{
public Reservation[] GuestReservations;
public Guid Id
{
set{}
get{}
}
public String Name
{
set{}
get{}
}
public String Email
{
set{}
get{}
}
}
public interface IReservation
{
Guid Id
{
set;
get;
}
DateTime ArrivalDate
{
set;
get;
}
DateTime DepartureDate
{
set;
get;
}
int NumberOfOccupants
{
set;
get;
}
decimal Rate
{
set;
get;
}
}
public class Reservation : IReservation
{
public Guid Id
{
set{}
get{}
}
public DateTime ArrivalDate
{
set{}
get{}
}
public DateTime DepartureDate
{
set{}
get{}
}
public int NumberOfOccupants
{
set{}
get{}
}
public decimal Rate
{
set{}
get{}
}
}
与大多数耦合MOD工具一样,你需要对模型和相应的设置进行大量的调整才能获得想要的代码,而且你经常会遇到一些限制。例如,我在模型中没有找到让C#属性自动化的设置(而是生成了一个空的{}实现)。尽管Altova似乎在后续合并你的自定义项方面做得很好,比如使属性自动化或添加你的实现细节。截至本文撰写时,我还没有找到一种方法可以使组合和关联对应于C#属性(使用IList等),而不是生成为数组。
优点
耦合MOD的主要优点包括
- 设计——建模设计可以增加设计的清晰度以及整体架构,并为团队(包括非开发人员)提供验证这些设计的手段。
- 代码——直接使用你的模型生成代码的工具可以帮助减轻一些繁重的工作。
- 团队——正式模型对于大型团队很有用,可以用一种普遍理解且健全的格式传达信息。
缺点
耦合MOD的主要缺点包括
- 需求——详细的设计模型通常会掩盖基本的底层需求,使得验证这些需求更加困难。
- 设计——多年来,我的许多同事都回避模型和建模,我认为主要是因为有过(亲身经历或以其他方式)进行过多的设计建模的经历。在某个时刻,创建和维护设计模型所需的工作量将与在代码中进行相同工作量一样多(甚至更多),而且我认为大多数开发人员(包括我自己)更愿意用代码来表达设计。
- 代码——使用工具从模型生成过多的代码(以及支持代码所需的建模量)可能会变得繁重,难以根据你的需求进行自定义和/或演进到你需要遵循的最佳实践。这当然取决于你选择的代码生成工具定制代码生成方式和时机的能力。
测试和维护
如果耦合MOD模型在为团队提供清晰设计背景方面做得很好,并且与相应的代码相比,它们将有助于概述设计更改并理解潜在影响。如果采用有效的代码生成解决方案来处理代码和测试,那么维护和升级遗留代码及相关测试的成本就可以降低。
一点还是很多?
我个人在几乎任何情况下都倾向于使用其他形式的MOD,而不是这种形式,所以耦合MOD的使用不会超过一点点。有时,我可能会和团队一起做一些UML设计模型来细化整体架构内的设计细节,而不从这些模型生成代码。
解耦面向模型开发
随着UML在设计规范中使用的增加,我认为解耦MOD形式在整个行业中受到的关注较少。解耦MOD可以大致定义为
- 在源代码之外定义了一个正式的模型。
- 模型元素与你的源代码之间存在较弱的相关性。你的源代码与模型是解耦的。抽象模型倾向于包含很少甚至不包含设计信息。详细设计也与模型解耦。
- 有一些软件工具可以解释你的模型定义。
- 有一些软件工具可以转换模型信息为源代码。
识别这种形式的关键在于模型的目的是阐明想法和需求。UML用例/序列/类/状态/活动图可用于细化想法和验证不包含设计的需求。称我为老派,但我仍然偏爱使用Shlaer-Mellor图,这些图有助于将焦点放在需求和愿景的通用数据/工作流上。下面是我们酒店管理系统的信息模型,其中描述和形式化了关系。
将这种方法应用于代码,我认为很少有工具能做得很好。一些基于模板的代码生成器,如CodeSmith,可能是最知名的。其中许多工具可以利用现有的数据库作为模型信息的来源,这减少了建模工作。一个特别有效的解耦MOD工具将使你能够完全控制管理你的模型(你需要的结构和数据),一个强大的语言让你能够完全控制编程以生成代码的设计模式,以及完全集成你自定义代码的能力。Mo+试图涵盖所有这些方面。
解耦MOD工具相对于耦合工具的一个显著优势是能够与内联MOD框架良好集成。这只是提供一种手段,将解耦模型映射到每个框架的特定模型。下面是一个Mo+属性/模板(没有正确的语法高亮显示),用于从Mo+模型(一种ER模型,其元素与上述图表良好对应)管理VITA ORM模型接口(如上所示)。
<%%:
<%%-/*<copyright>
%%>
<%%=Solution.Copyright%%><%%-
</copyright>*/
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using Vita.Entities;
namespace %%><%%=Project.Namespace%%><%%-.Models%%>
<%%=Solution.OPEN_BRACE%%><%%-
//------------------------------------------------------------------------------
/// <summary>This interface defines the key elements for managing
/// an associated table for %%><%%=VITAClassName%%><%%- items, utilizing
/// the VITA ORM.</summary>
///
/// This file is code generated and should not be modified by hand.
/// If you need to customize outside of protected areas, add those changes
/// in another partial interface file. As a last resort (if generated code needs
/// to be different), change the Status value below to something other than
/// Generated to prevent changes from being overwritten.
///
/// <CreatedByUserName>%%><%%=USER%%><%%-</CreatedByUserName>
/// <CreatedDate>%%><%%=NOW%%><%%-</CreatedDate>
/// <Status>Generated</Status>
//------------------------------------------------------------------------------%%>
<%%=VITAInterfaceAttributesCode%%>
<%%=Solution.NEWLINE%%>
<%%-public partial interface %%><%%=VITAInterfaceName%%>
var hasBaseEntity = false
if (BaseEntity != null && BaseEntity.VITAIsDataModelEntity == true)
{
hasBaseEntity = true
}
<%%=Solution.OPEN_BRACE%%>
if (hasBaseEntity == true)
{
<%%-
[PrimaryKey, EntityRef(KeyColumns = "%%>
foreach (Property where IsPrimaryKeyMember == true)
{
if (ItemIndex > 0)
{
<%%-,%%>
}
<%%=VITAPropertyName%%>
}
<%%-")]
%%><%%=BaseEntity.VITAInterfaceName%%><%%- %%><%%=BaseEntity.VITAClassName%%><%%- { get; set; }%%>
}
foreach (Property where VITAIsInterfaceModelProperty == true)
{
if (ItemIndex > 0 || hasBaseEntity == true)
{
<%%=Solution.NEWLINE%%>
}
<%%=VITAPropertyCode%%>
}
if (VITAIsPrimaryUserEntity == true)
{
<%%=Solution.NEWLINE%%>
<%%=Solution.NEWLINE%%>
<%%-UserType Type { get; set; } //might be combination of several type flags%%>
}
foreach (Collection where VITAIsInterfaceModelProperty == true)
{
<%%=Solution.NEWLINE%%>
<%%=VITAPropertyCode%%>
}
foreach (EntityReference where VITAIsInterfaceModelProperty == true)
{
<%%=Solution.NEWLINE%%>
<%%=VITAPropertyCode%%>
}
if (VITAHasFullNameProperty == true)
{
<%%=Solution.NEWLINE%%>
<%%=VITAFullNameProperty%%>
}
<%%=Solution.CLOSE_BRACE%%>
<%%=Solution.CLOSE_BRACE%%>
%%>
下面是从上述模板生成的一些代码。使用解耦方法创建和维护高质量代码通常要容易得多。
/*<copyright>
This software falls under the Code Project Open License (CPOL), please feel free to use it!
</copyright>*/
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using Vita.Entities;
namespace Forums.VITA.Models
{
//------------------------------------------------------------------------------
/// <summary>This interface defines the key elements for managing
/// an associated table for Discussion items, utilizing
/// the VITA ORM.</summary>
///
/// This file is code generated and should not be modified by hand.
/// If you need to customize outside of protected areas, add those changes
/// in another partial interface file. As a last resort (if generated code needs
/// to be different), change the Status value below to something other than
/// Generated to prevent changes from being overwritten.
///
/// <CreatedByUserName>INCODE-1\Dave</CreatedByUserName>
/// <CreatedDate>1/27/2017</CreatedDate>
/// <Status>Generated</Status>
//------------------------------------------------------------------------------
[Entity(Name="Discussion", TableName="tblForums_Discussion")]
[Paged, OrderBy("Title")]
public partial interface IDiscussion
{
[PrimaryKey, EntityRef(KeyColumns = "PostID")]
IPost Post { get; set; }
[Column("Title", Size = 255), Index(IndexName="IX_Discussion_Title")]
string Title { get; set; }
[Column("DiscussionText"), Unlimited]
string DiscussionText { get; set; }
IList<IDiscussionReply> DiscussionReplyList { get; }
}
}
优点
解耦MOD的主要优点包括
- 需求——做一些建模有助于为团队明确大局。针对需求定制的正式模型在细化整体愿景方面大有裨益。这些模型可用于限定和验证需求,并有助于及早发现需求中的不一致之处。
- 设计——由于详细设计与模型解耦,开发人员可以灵活地在代码或任何他们认为合适的方式中定义设计。
- 代码——直接使用你的模型生成代码的工具可以帮助减轻一些繁重的工作。需要进行的建模工作量减少,并且能够用代码定义设计模式,这可能使这些工具比耦合MOD工具更具吸引力。
- 团队——正式模型对于大型团队很有用,可以用一种普遍理解且健全的格式传达信息。
缺点
解耦MOD的主要缺点包括
- 设计——这种形式在阐明非开发人员团队的设计方面做得很少,甚至不做。
- 代码——与耦合MOD一样,使用工具从抽象模型生成过多的代码,如果过度使用可能会变得繁重。同样,这取决于所用工具的有效性。
测试和维护
由于解耦MOD模型提供了理解整体需求的背景,因此对这些模型的更改有助于阐明需求更改的影响,并有助于规划设计更改。如果采用有效的代码生成解决方案来处理代码和测试,那么维护和升级遗留代码及相关测试的成本就可以降低。
一点还是很多?
为了细化想法作为需求,我发现我大量使用解耦MOD。我总是倾向于这种形式,即使我只做一些建模以供自己理解。我使用Shlaer-Mellor和/或UML图来实现这一点。
对于编写设计模式和使用工具生成代码,这取决于。我经常这样做是为了快速原型化想法,特别是那些利用内联MOD框架的想法。为了选择使用工具来维护生产代码,我问这些问题:
- 生成的代码(质量而非数量)是否非常接近我按照团队实践手动创建的代码?生成大量不必要、重复或有损代码的代码对生产毫无用处。
- 工具使用的设计模式的编码是否可以节省时间并有助于确保一致性?
- 编码的设计模式(和生成的代码)是否可以根据需要进行升级,以应对新兴技术和实践?
- 编码的设计模式(和生成的代码)能否与自定义代码无缝集成?
- 在任何时候停止使用该工具是否会对维护生产代码产生不利影响?
如果对于给定情况和工具,这些问题的任何一个答案是否定的,我将不会使用该工具来维护生产代码。
结论
以任何形式进行一点面向模型开发都有助于团队在更短的路径上朝着清晰的愿景开发软件。找到适合您团队在任何特定情况下的MOD使用平衡点,并识别何时过度使用某种形式的MOD会成为一种负担而不是益处。希望本文能为您提供一些关于MOD技术及其使用时机的想法。