New需谨慎
New is Glue
When you’re working in a strongly typed language like C# or Visual Basic, instantiating an object is done with the new keyword.
It’s important that we recognize the significance of using this keyword in our code, because I would venture to say that well over 90% of developers don’t give it a second thought. Most developers have, at one time or another, heard the practice of building software applications likened to building something out of LEGO bricks (I’ve even written about LEGOs and software in the past). A few days ago, it occurred to me that I could sum up my thoughts on this topic in three words, which I might hope would be memorable enough to “stick” in developers’ minds:
New is Glue
Any time you use the new keyword, you are gluing your code to a particular implementation.
You are permanently (short of editing, recompiling, and redeploying) hard-coding your application to work with a particular class’s implementation.
That’s huge.
That’s like your code’s getting married, forever and ever, until redeployment do us part. Once you’ve said the words (new) and exchanged some vows (type metadata) and exited the church (deployed), changing your code’s chosen partner becomes a very expensive endeavor.
This is a big decision for you and your application.
You need to be very sure that this is the one and only implementation your code will ever need to work with, or else think about calling the whole thing off.
“Using new isn’t wrong, it’s a design decision. Like all design decisions, it should be an informed decision, not a de facto one.”
The marriage metaphor is taking things a bit far, I know, but let me add one more bit before we leave it behind us.
It’s quite likely that your code is going to need behavior from more than one other class.
In fact, it’s quite common that you’ll need to work with other classes in virtually every class within your application.
If you go the new route, that’s a lot of overlapping marriages that all need to stay happy for your application to work effectively.
But what if the behavior or service you need is only found in one place? Maybe it’s not the perfect, now-and-forever class, but it’s the one that has what you need right now? What am I suggesting,
that one forego leveraging any other classes, and just do everything yourself?Not at all, but let’s go with another metaphor now.
Favor Contractors over Employees
Your application requires certain services to do its job.
Think of your application like a small business. There are things your application does, and things it needs to do its job.
A small business might need office space, and beyond that, certain utilities like electricity, phone service, internet service, shipping, etc.
In a business, relationships you have with service providers and contractors are relatively easy to change, while relationships you have with employees can be somewhat more difficult (for purposes of this analogy, pretend we’re talking about a country where firing people is very difficult/expensive).
Now think about which approach to running a small business makes more sense.
In the first scenario, your business’s needs are each met by a full-time resource.You have Bob, the electrician, who ensures your electricity works, Joan, the telecom expert, who ensures your phone service is connected, and Joe, the truck driver, who takes care of delivering everything you ever need to send. You also need an office to hold all of these people, which of course you leased with a 5 year lease that’s every expensive to get out of.
In the second scenario, your business’s needs are all met by service providers or contractors. You use standard utility companies for electricity, phone, internet, and you can switch from one to another with only a small amount of pain if one fails you. You ship with FedEx, USPS, UPS, etc. based on whichever one fits your needs of the day. Your office space is just big enough for you, and since you’re still growing and aren’t sure how your needs will change, you’re paying month-to-month.
Which of these two scenarios provides the greater degree of freedom for the business to adapt to future needs?
Which one is locked into a particular implementation and will be hard-pressed to change when needed?
Decoupling Services from Providers
New is Glue. It binds your code to a particular collaborator.
If there is any chance you’ll need to be flexible about which implementation your code will need,
it’s worth introducing an interface to keep your code loosely coupled. It doesn’t matter what the service is you need – you can always replace it with an interface even if your class is the only one that uses it.
Let’s say your code needs send an email, with default code that looks like this:
using (var client = new SmtpClient())
using (var message = new MailMessage(fromEmail, toEmail))
{
message.Subject = subject;
message.Body = bodyHtml;
message.IsBodyHtml = true;
client.Send(message);
}
Note the two new keywords here, gluing whatever else this class is doing to this implementation of message sending.
This can be replaced with an interface like this one:
public interface IEmailClient
{
void SendHtmlEmail(string fromEmail, string toEmail,
string subject, string bodyHtml);
}
Now the code that used to contain the first block of code can be rewritten to simply use the interface:
public class SomeService
{
private readonly IEmailClient _emailClient;
public SomeService(IEmailClient emailClient)
{
_emailClient = emailClient;
}
public void DoStuff(User user)
{
string subject = "Test Subject";
string bodyHtml = GetBody(user);
_emailClient.SendHtmlEmail("noreply@whatever.com", user.EmailAddress, subject, bodyHtml);
}
}
Edited: Please assume that DoStuff() does some useful value-added work, and that sending an email to the user is just something it does as a side effect, perhaps only when it’s successful, and not that it’s the main intent of the method, if that helps you think about the value of removing the dependency on SmtpClient.
How many new keywords are in this class, now?
Run a search in Visual Studio to find how many instances of new you’re using in your solution, and where. Use ctrl-shift-F to find in files, search for “new”, Entire Solution, Match case, Match whole word, and Look at these file types: *.cs.
Have a look at the total numbers, the numbers of files, etc. In one very small ASP.NET MVC application I’m working on now, this search yields 280 matching lines in 25 files out of 47 files searched. Four of the files and 28 of the matches are in my tests project.
The lion’s share of the rest of the matches are from SqlHelper.cs. My controllers have a total of 7 matching lines, and all of these relate to creating model/viewmodel types, not services.
When is it Acceptable to use New?
Using new isn’t wrong, it’s a design decision.
Like all design decisions, it should be an informed decision, not a de facto one.
Use it when you don’t expect you’ll need flexibility in the future.
Use it when it won’t adversely affect the usability of your class by its clients.
Consider the transitivity of the dependencies you take on within your class – that is, how the things you depend on in turn become dependencies for classes that use your classes.
Consider too how your design decisions and considerations today may change in the future.
One common example of this is applications that hard-code dependencies on local system resources like the file system, local memory (session, cache, global collections), and local services like email.
When the application needs to scale out to multiple front-end machines, problems ensue.
Loosely coupling these resources together would make implementing a webfarm- or cloud-optimized version of these services trivial, but thousands of new statements gluing implementations together can take a long time to fix.
One last thing to think about, if you actually do search your code for new, is how many duplicate lines you find.
Remember the Don’t Repeat Yourself (DRY) and Single Responsibility (SRP) principles.
If you’re instantiating the same class in the same way in more than one place in your code (e.g. 10 places where you new up a new SqlConnection), that’s a DRY violation.
If you have ten different classes that all do some kind of work, AND need to know how to create a SqlConnection, that’s an SRP violation.
Knowledge of how to talk to the database should be in only one location, the responsibility of a single class.
The same is true for any other resource that you find you’re commonly instantiating in many different classes.
The exceptions here would be low-level intrinsics like strings, datetimes, and things like exceptions that generally are created only in exceptional cases.
Moving Forward
Learn to look for new in your code and in code reviews. Question whether it’s appropriate when you see it.
Work on pushing the responsibility of choosing which classes your code will work with into as few classes as possible, and see if your code doesn’t start to become more loosely coupled and easier to maintain as a result.
评论:
A unit test of this code would inject some sort of "fake" [mock/stub/whatever] implementation of IEmailClient. The DoStuff() method would fire, and the test could assert that the proper values were passed along to SendHtmlEmail. THAT'S ALL YOU NEED TO TEST; as long as the correct values are passed, this service is correctly interacting with the email component.
I agree that eventually we need to test actual email delivery. There should be an integration test of each "real" implementation of IEmailClient that handles that.
Using interfaces allows the rest of the tests to assume that the email component is working and focus only on their interactions with it, not the side effects of doing so.
I'd like to disagree that this style of coding results in code that is "more loosely coupled and easier to maintain".
In the last two or three years, I've seen a tendency to overdose on buzzword-laden techniques like design pattern, loose coupling, inversion of control, dependency injection, abstraction layer, etc.
The result: layers upon layers of convoluted abstraction, configured with layers upon layers of convoluted XML. The resulting application is so abstracted that anyone other than the original development team needs a 6-9 month learning curve before they have any hope of maintaining anything, and no field tech. has a snowball's chance of getting anything configured in the field.
Of course, if your goal is to create a permanent gig for yourself, have at it!
Those class factories aren't going to write themselves ...
Hi Ron,
Where's the XML in this? And why do you think this needs
to become a mess to understand? It's quite straightforward, in my
experience, to keep things organized while still avoiding tight coupling
everywhere (though of course it can be the case that things get out of
hand and are not organized, no question).
Are you coming from a
background that uses XML to configure containers (e.g. Spring) perhaps?
Most .NET containers use conventions for most of their heavy lifting
and require just a few lines of code for the rest (where "a few" varies
with the size of the application).
Just curious to know where you're
coming from, since I'm sure others share your experience, though I do
not.
The XML comes into play during the next step in loose coupling - making the application more "flexible" by being able to defer what get coupled until run-time.
This means either relying on reflection to decide what gets put together, or configuration by means of a config file, or rows in a database table (metadata).
My background included just such a .NET product. Dependencies were determined at run-time by nine XML .config files, two of which used a proprietary (barely-documented) schema, and were over 100k in size. (This doesn't include web.config, which was also huge). It generally took about a year to get a new client deployed.
My primary opposition is to the idea that "violations" of "principles" like DRY or SRP need to be eradicated for software to be considered "good". Sometimes you instantiate the same class the same way 10 times because it's a well-written, basic class that can be cleanly reused in ways easy to understand.
Requiring that all these uses be moved to a new, common location means a developer has to look in one more place to learn how a particular piece of code works.
As the application grows, this new location gets used more and more, and then needs to move to it's own single-use location. These new locations often get moved to new assemblies, so now a developer has to learn the build process to know where to look.
Eventually, it can take days to find where the actual call to something like SqlConnection is made (especially when the XML comes into play).
Clearly, like anything else, one can go too far. Again, I'm not saying that new is evil - obviously you need it. But I want people to recognize the coupling it introduces and be OK with that, or else consider using an interface instead *if that would be better*.
All things in moderation, and too much is, by definition, too much. :)
Great advice. You may take this so far as to say that all static references are glue, just like DateTime.Now, DependencyResolver.Current, etc. Look at the IL that gets generated: new is just another static method on the class.
Keeping with the metaphor, some glue is 10,000 year epoxy and some glue is rubber cement.
You can write test setup code that makes DependencyResolver.Current.GetService<t>() return a mock and write expectations against that mock. But that's a bit of a pain, and often results in a high test : production code ratio.
If you use HttpContext.Current, you had better plan on running an in-memory web instance. It can be done, but it's another pain point. And if you use "new MyDataProvider(connectionString)", you can forget about mocking away anything at all.
Why not just use an interface where it matters (e.g. where the date is used as a condition of behavior)? I frequently employ ICalendar or IDateTime interfaces for this purpose, or simply write the code in question so that a DateTime is provided as a parameter, if that's an option.
I think this is the post you're referring to, too: Insidious Dependencies - http://ardalis.com/insidiou...
You shouldn't blindly use interfaces everywhere to avoid new - agreed.
You should *think* about the coupling you're introducing when you use new, and make a decision about whether it's acceptable.
Also, not writing automated tests of your code should probably get you fired as well,
and once you need to call your method both from a test and from the UI,
in one case doing real work and in another not, you have your two use cases that necessitate adding an interface.
You said to avoid new keyword to avoid tight coupling. What if instead of using new keyword, I call UI->BL->DAL as static class (where BL, DAL are static class). Does this also tightly couple it, please make me understand this scenario.
Yes, certainly. This also has a catchy name (I didn't coin) called "static cling" because the use of statics makes unrelated parts of your codebase stick together. Statics with no side effects are fine to call, but those with dependencies on infrastructure (like DAL/Database) can make your code difficult to test and change.
Hi.
I landed here again after reading the excellent .Net C# tutorial.
I saw this code for the Orders property (right) and I didn't really liked it. I modified it like this (left): I moved the Orders property initialization in the constructor. Any reason I shouldn't?
Thanks.
This isn't really related to the article, but either version would *usually* be fine.
There is a difference (with possible performance implications) in how the two versions work.
Your code on the left creates the _orders collection immediately when the Customer instance is created, regardless of whether the Orders property will ever be accessed.
The code on the right is an example of lazy instantiation, where the _ordersView collection isn't created until the first time the Orders property is accessed, and if Orders is never accessed then the collection is never created.
Yes, it's not related to the article, I'm sorry if it's out of place.
I can't help but notice that the lazy instantiation is burdened by a mandatory check on each access.
It also adds a new member. I'd say it's premature optimization, the lists are not populated at this point, so there's not really a memory penalty.
I'd also say the code smells. Is there something I'm getting wrong?
I don't have time to code it right now, but you could probably also use a Lazy<t> for this purpose, too.
My only point here is to recognize that every time you use 'new' within a method (or class), you are coupling your code to a specific implementation type. That may be, and often is, perfectly acceptable.
It's no different than if you were calling a static method, in terms of the coupling effect.
Coupling isn't *bad*, per se, but it should be managed.
Unnecessary tight coupling causes problems with testability and ultimately maintainability in applications. Thus, it's worth putting at least a little bit of thought into how much coupling you want between your classes, and where you want decisions to be made about which classes couple to which implementations.
If you follow the Dependency Inversion Principle, you're likely to have a loosely coupled application where decisions about which implementations to use are made centrally, not scattered throughout the codebase.
See my training classes on SOLID and N-Tier applications for more on this topic: http://ardalis.com/training...
I'm mainly referring to types that are instantiated within methods in order to perform logic relevant to that method.
The 'new' keyword is less a concern (to me, at least) for how you've designed your model and the entities that have property relationships to one another.In my designs, such entities are POCOs that can be instantiated anywhere without side effects, so the effect of using 'new' on them isn't a problem.
It's when you're in the middle of some operation that has no side effects and you decide you need some data so you use new to create a dbConnection, which in its constructor tries to open a connection to a database, that you've caused a major coupling problem.
How then will you test that method, without having a database? That's more of the issue being considered here.
Watch the DDD Fundamentals course I did with Julie Lerman. Look at the sample application there.
In this architecture, OOP and testing are not sacrificed.
Implementation classes are placed in a specific project that can be tested using integration tests.
Business and domain logic goes into another project that can be extensively tested with unit tests, and which has no dependencies on infrastructure concerns or implementation details.
The general guideline to follow, once you have this architecture in place, is to push as much of your business logic into the Core project, and only put things into the Infrastructure project that have dependencies on infrastructure concerns (databases, files, networking, etc).
I'm not sure I follow your concern at this point. You can see an example of the kind of architecture I'm talking about here: https://github.com/ardalis/...
If you feel this architecture is "messing with" the OOP design or is otherwise making sacrifices that you think aren't worthwhile, let me know (here or via an issue in that repo). Thanks!
Hey Steve,
Saw your tweet about revisiting that article. I'll have to agree with you. `new` is glue.
But just like everything, glue isn't necessarily bad. It's all about picking what you glue together.
I do `new` lists, exceptions, MVC action results, etc. Why? Because it's the right thing to do and decoupling at that level would not bring anything to the table. Not even testability.
Repositories? DbContext? Email Sender? Yeah no. I'm not gluing those. What if I swap my SmtpClient for SendGrid or <include email="" provider="">? Well... all my tests will break and are not guaranteed to be reimplemented properly.
Still a great article 4 years later.
Cheers,
Thanks! Yes, I agree completely that 'new' isn't something to be avoided, just something to be mindful about.
By all means instantiate value objects and non-infrastructure types directly.
Don't add abstraction for abstraction's sake.
But do remember the coupling you're introducing when you do new up an infrastructure type...
Hi Matt,
That, too, sounds like a great, albeit more involved,
example.
I went with the simpler one to keep the post and the example
short and simple, and also because I do believe it illustrates the
issue.
Even for something as specific as sending an email (not an
arbitrary message, but an email), eliminating the use of new (or a
static method call) in the client code (the code that needs the email
sending functionality) is a big win in terms of keeping the code loosely
coupled.
And, perhaps like you, most developers in my experience don't
even think about this in simple cases like this, even if they do for
less simple scenarios.
I'm trying, with this post and the mnemonic "new
is glue" to get a larger number of developers to give this more thought
before they instantiate objects throughout their codebase without
considering the consequences.
If you're not familiar with it, I recommend reading about the Open/Closed Principle. A good start is here: http://deviq.com/open-close...
It pretty well describes the value of having code that you can change without having to change the source.
But also note that, as you said yourself, this isn't necessarily something you code from day one.
This is something you add when you feel it is necessary (because you want reuse, because you need it for testing, etc.).
The idea behind 'new is glue' is just that you're aware of the coupling you're creating, so that you're making an informed decision, rather than a default, possibly careless, one.
New需谨慎的更多相关文章
- 从Go、Swift出发:语言的选择需谨慎
本文转自 : http://www.csdn.net/article/2014-12-09/2823025 摘要:无论是开源的Go,还是闭源的Swift,新的语言总是利弊一体.不过可以确定的是,新的语 ...
- IOS开发中重写init方法使用需谨慎
IOS开发中重写init方法使用需谨慎 今天在写一个小软件的时候出现一点问题,这个软件的功能是搜索全国学校,首页就是搜索输入框,在框中输入完要查询的学校所在省份,点击buttom后就会跳转到对应的视图 ...
- PHP就业前景好不好一看便知,转行选择需谨慎!
随着互联网行业迎来新一波的热潮,更多的年轻人选择软件行业发展.由于互联网本身快速发展.不断创新的特点,决定了只有以快开发速度和低成本,才能赢得胜利,才能始终保持网站的领先性和吸引更多的网民. 互联网的 ...
- 借root之名,行流氓之实,劝告,root需谨慎
20160425++++++ 今日再回头看这篇文章,貌似有点偏激了一点,不过xda论坛上有个疑似kingroot开发团队的用户说明了kingroot确实对supersu做了限制,说是supersu在替 ...
- Xcode8-beat升级需谨慎
Xcode8-beat版本在打开xib文件的时候,出现了如下的弹窗 在这里要选择Cancel,选择Choose后xib文件的verson会改变,那么Xcode7就没法打开了(坑队友啦), 更没法运行 ...
- 检验appium环境是否正常:使用appium自动给手机安装app(注意:如果已存在该app,再执行会将原来的卸载再重装,需谨慎)
(注意:如果已存在该app,再执行会将原来的卸载再重装.泪的教训,我的微信被卸载重装了o(╥﹏╥)o,自动安装app这个步骤需谨慎操作) hi,前面几篇已经讲了appium环境的搭建.设备的连接, 那 ...
- Git存储用户名和密码(明文需谨慎)
当你配置好git后,在C:\Documents and Settings\Administrator\ 目录下有一个 .gitconfig 的文件,里面会有你先前配好的name 和email,只需在下 ...
- 设计数据库字段或者java中使用boolean型时需谨慎
boolean型变量只有两个值 false和true,我们在设计数据库字段时或者定义java变量时会使用boolean,通常情况下开关类的变量使用无可非议,但请一定要考虑到扩展性. 使用前请仔细考虑一 ...
- 下载安全程序需谨慎 黑客盯上XP用户
中关村在线消息:微软在上周正式结束了对Windows XP的技术支持,而很多仍在使用Windows XP的用户会选择在网上自行下载第三方的安全工具.不过国外的安全机构Malwarebytes近日提醒, ...
- BT币(金融有风险,投资需谨慎)哥的失败投资
谁都知道bt币是一个旁氏骗局, 而进去的人,就必须保证自己不赔钱,所以只能随着大潮往前走,谁也不能让它跌 压垮骆驼的最后一根稻草, 还是幕后有个 推手, 在炒作 BT币, 事实上,作为新的投资项目,B ...
随机推荐
- 三、CSS语言
CSS语言 1.概述:CSS (Cascading Style Sheets)是层叠样式表用来定义网页的显示效果.可以解决html代码对样式定义的重复,提高了后期样式代码的可维护性,并增强了网页的显示 ...
- 网络爬虫基础知识(Python实现)
浏览器的请求 url=请求协议(http/https)+网站域名+资源路径+参数 http:超文本传输协议(以明文的形式进行传输),传输效率高,但不安全. https:由http+ssl(安全套接子层 ...
- nginx入门与实战 安装 启动 配置nginx Nginx状态信息(status)配置 正向代理 反向代理 nginx语法之location详解
nginx入门与实战 网站服务 想必我们大多数人都是通过访问网站而开始接触互联网的吧.我们平时访问的网站服务 就是 Web 网络服务,一般是指允许用户通过浏览器访问到互联网中各种资源的服务. Web ...
- InnoDB中锁的算法(2)
Ⅰ.上节回顾 session1: (root@localhost) [test]> select * from l; +---+------+------+------+ | a | b | c ...
- java异常处理解决方案
一.异常概念 Throwable类是Java中所有错误或异常的超类. 1.只有当对象是此类(或其子类)的实例时,才能通过Java虚拟机或着Java throw语句抛出. 2.只有此类或其子类才 ...
- git 远程删除文件
git rm -r --cached a/2.txt //删除a目录下的2.txt文件 删除a目录 git rm -r --cached a git commit -m "删除a目录下的2. ...
- 018-并发编程-java.util.concurrent.locks之-ReentrantReadWriteLock可重入读写锁
一.概述 ReentrantLock是一个排他锁,同一时间只允许一个线程访问,而ReentrantReadWriteLock允许多个读线程同时访问,但不允许写线程和读线程.写线程和写线程同时访问.相对 ...
- CSS中list-style详解
取消默认的圆点和序号可以这样写list-style:none;, list的属性如下: list-style-type:square;//正方形 list-style-position:inside; ...
- 69A
#include <stdio.h> int main() { int n; int sum1=0, sum2=0, sum3=0; int x, y, z; scanf("%d ...
- 【Assembly】NO.70.EBook.7.Assembly.1.001-【汇编语言 第3版 张爽】- 基础知识
1.0.0 Summary Tittle:[Assembly]NO.70.EBook.7.Assembly.1.001-[汇编语言 第3版 张爽]- 基础知识 Style:Assembly Serie ...