zoukankan      html  css  js  c++  java
  • 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 Dependencieshttp://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.

  • 相关阅读:
    Adventure C CF-665E(字典树、二进制)
    实验7投资问题
    Beautiful Array CF-1155D(DP)
    Salary Changing CF-1251D(二分)
    Beautiful Sets of Points CF-268C(乱搞)
    Vasya And Array CF1187C(构造)
    Tree Painting CF-1187E(换根DP)
    Vus the Cossack and Numbers CF-1186D(思维)
    Tree POJ-1741(点分治+树形DP)
    Magical Girl Haze 计蒜客-A1958(分层最短路)
  • 原文地址:https://www.cnblogs.com/panpanwelcome/p/10189973.html
Copyright © 2011-2022 走看看