tag:blogger.com,1999:blog-27158746407438832672024-03-19T14:02:08.122-07:00#Blog de NachokhanBlog about Software Architecture, Patterns, Practices, Principles and a lot of software issues that I find interesting.
Blog sobre Arquitectura de Software, Patrones de Diseño, Principios y otros asuntos de software que me interesan.nachokhanhttp://www.blogger.com/profile/00777464846297951418noreply@blogger.comBlogger57125tag:blogger.com,1999:blog-2715874640743883267.post-74827279319236381682017-05-22T01:54:00.000-07:002017-05-22T01:54:19.931-07:00Some online programming languages..For the Code-Catas I'm organizing in the company, I found this site, which allows the users not only to choose a language among <b>a lot </b>of languages, but also to work with projects and files.<br />
<br />
This was a "must have" because in the catas we wanted also to try design patterns, and good programming techniques, what sometimes means to use more than just one file.<br />
The idea of using an online tool came because of licensing issues by installing some languages in the laptops. There were not supposed to be used to develop productive code (code that will be sold) so buying a license just for that was a lot of money and time installing all the languages.<br />
<br />
The page I found is:<br />
<br />
<a href="https://www.tutorialspoint.com/codingground.htm">https://www.tutorialspoint.com/codingground.htm</a><br />
<br />
Let's see how it work for our purposes!nachokhanhttp://www.blogger.com/profile/00777464846297951418noreply@blogger.com0tag:blogger.com,1999:blog-2715874640743883267.post-58102409618137179912017-03-30T01:42:00.000-07:002017-03-30T02:36:23.913-07:00A tool with a fool, it's still a tool ;)<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhP66aaRB7W2eMeYP97FA7JlPoAnXj4JauWAhxct4-4Pfd2zQ6kSAmv6038DNR5uCgwTZ8Xhq6Sdpi28HtMAgHjwe40BoFUSbRsSh3lTokqxQovJB1eOiIiLpMN_EDD9pSz2_fKSNoW91w/s1600/fool_tool.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img alt="a tool with a fool" border="0" height="136" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhP66aaRB7W2eMeYP97FA7JlPoAnXj4JauWAhxct4-4Pfd2zQ6kSAmv6038DNR5uCgwTZ8Xhq6Sdpi28HtMAgHjwe40BoFUSbRsSh3lTokqxQovJB1eOiIiLpMN_EDD9pSz2_fKSNoW91w/s200/fool_tool.jpg" title="" width="200" /></a>Two weeks ago a colleague came to me extremely happy, jumping like a happy Bunny to ask me if I could use the only license of <a href="http://www.ndepend.com/" target="_blank">NDepend </a>that we have in the firm to get the metrics and the graphics of his new code he was proud of. Eight months back in time, we bought this tool, because I downloaded a trial version that I tried for... well, now I don't remember if it was 14 days or a month. But I was quite satisfied with it, so I asked to get a license. And I was listened. ;)<br />
<br />
But the funny part of this story is that at that time, when I first tried it, this colleague came to me (this time not like happy Bunny) a told me "<i>Good programmers don't need tools. I don't mean to offend you, but a fool with a tool, it's still a fool</i>."... Now he's asking if he can get his own license too.<br />
<br />
The interesting part of this, it's not in fact to say that "people who lives in glass houses should never throw stones", but to point out the motivational change that I noticed in this person. When he decided to throw his code in a metric tool, he also started thinking about how to write his code better. how to architect his solution, and taking care about dependencies, death code, etc.<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjbjn6k6CKPxAv6MUcFFl5eJkMM61y35oIwBr4y1a72EzotU9FNhF9Ox1for3oWbroGfQYa2BFzVmxry8V0LCEDuYq3rtg030wEiUfPN0ln-qzgl3_YsqAMUlfF35MC8q5OTISiXzpp5zg/s1600/ndpend_dash.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="NDepend Dashboard" border="0" height="161" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjbjn6k6CKPxAv6MUcFFl5eJkMM61y35oIwBr4y1a72EzotU9FNhF9Ox1for3oWbroGfQYa2BFzVmxry8V0LCEDuYq3rtg030wEiUfPN0ln-qzgl3_YsqAMUlfF35MC8q5OTISiXzpp5zg/s200/ndpend_dash.png" title="NDepend Dashboard" width="200" /></a>So, my conclusion is that when people see how good or bad his code actually is (in this case based on numeric metrics and graphics), <b>they feel more motivated to do it better</b>. Your quality doesn't live in the ether, you can actually see it! By the way, I must say that <b><i>NDepend is a great tool</i></b>, that I can fully recommend. nachokhanhttp://www.blogger.com/profile/00777464846297951418noreply@blogger.com0tag:blogger.com,1999:blog-2715874640743883267.post-86543159582646609192017-03-16T12:55:00.002-07:002017-03-30T02:52:24.876-07:00"How to tell people their code is bad"While reading the book <i><a href="https://blogs.msdn.microsoft.com/microsoft_press/2014/09/10/new-book-microsoft-net-architecting-applications-for-the-enterprise-second-edition/" target="_blank">Microsoft .NET: Architecting Applications for the Enterprise</a> (Esposito & Saltarello, 2nd ed)</i>, I find quite interesting when they speak about this topic..<br />
<br />
Because let's face it: architecting in an enterprise is much more than just doing the architecture, fighting with inconsistent requisites and meeting deathlines ;). It's -among other things- also about dealing with developers. As architects are some kind of technical leaders, I've noticed they must be seen as some inspirational developer. Someone who not only knows and leads the coding aspects, but also someone who can guide other developers to enhace themselves, to write better and less dirty code. But we all know how difficult can it be to tell someone that their code isn't good, or even not good enough. In my personal experience, junior developers are open minded to enhancements, but semi-seniors and senior are most of the time very difficult to get through.<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhxdMXyXW0Ji3sMHdF-ZMOt0QbjpwZHmmenVtp49QdLt_JPLvUXwmBQENi6D65qsOyfaWCwd5QNylSCJk55m__c7fPoqz8JFX6gfU5QkWp4AJv74edGz5-7UPL6ohkLj1kwKPI61tkGePg/s1600/bas_code.jpg" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="bad code programming" border="0" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhxdMXyXW0Ji3sMHdF-ZMOt0QbjpwZHmmenVtp49QdLt_JPLvUXwmBQENi6D65qsOyfaWCwd5QNylSCJk55m__c7fPoqz8JFX6gfU5QkWp4AJv74edGz5-7UPL6ohkLj1kwKPI61tkGePg/s200/bas_code.jpg" title="" width="200" /></a>Well, what this book says about it is no key of the universe, but very usefull and easy to keep in mind: instead of telling someone that the code he/she wrote it's not good -and here I mean whatever nice way to tell this- it could be much, much better to ask him/her subtly <i><b>why was the code written in this way</b></i>. Maybe in a humble way of meaning "<i>I don't really get what you did, but I'm really interested in it.</i>"<br />
<br />
You can be suprised of knowing what was it, that toke that person to solve a thing that way. It can be lack of time, lack of knowledge or just a new point of view that you dind't consider. If you think it could be better done in other way, you can tell the developer.... but <i><b>not as a correction</b></i>, rather <i><b>as an opportunity to enhance</b></i> his or her knowledge.nachokhanhttp://www.blogger.com/profile/00777464846297951418noreply@blogger.com0tag:blogger.com,1999:blog-2715874640743883267.post-51738064458892072162017-03-06T05:07:00.000-08:002017-03-07T01:16:41.308-08:00Little Awful Practices (II)This time I'm going to continue with the<i> try-catch</i> examples ;) This thing I found in the code, was also a little stinky. Take a look by yourself:<br />
<br />
<pre class="brush:csharp">public static void UpdatePersistentData()
{
try
{
PreferencesManager.UpdatePersistentData();
TestConfigManager.UpdatePersistentData();
}
catch (Exception)
{
// ignore
}
}
</pre>
<br />
Why would someone here want to catch en exception and do nothing with it? Well... this is more common as it should. The reason behind this, I want to believe, is that we don't care if some exception occurs, maybe because it's not crucial for the program's workflow.<br />
<br />
Nevertheless I wouldn't agree most of the times with this solution because for me exceptions MUST BE CAUGHT and MUST BE HANDLED.<br />
<br />
It happened to me a few months ago to be working on the development of a project which had a dependency for a small library, which was also developed by some guy in the company a few years ago. When the user was under certain environmental conditions the software threw always a crash error, that I had never had. I couldn't debug it, so I start looking deep into my code to try to find where the bug was, but I couldn't really find anything rare. The only suspicious part, was a call to this library, because it was the only part I didn't knew how was working inside. So I reopened that library's project and started looking at the code... and what did I found? Yes, a try-catch section where the catch was actually catching an exception but doing nothing with it!! And this was the reason why my piece of code couldn't detect the true error with a correct message to be shown!<br />
<br />
So that's for me the most important reason to catch exceptions: if anything happens (and it'll probably do) <b>your software should notice it to come up with the correct message</b> and you, no matter if you're user or developer, can handle the situation in some way. But at least you can have an idea where does the exception comes from. :)nachokhanhttp://www.blogger.com/profile/00777464846297951418noreply@blogger.com0tag:blogger.com,1999:blog-2715874640743883267.post-43459472990305235572017-03-02T01:39:00.001-08:002017-03-06T05:09:17.315-08:00Little Awful Practices (I)The first post of this series is about repeating code. We all know that code repetition doesn't smell good, but sometimes we just don't have the time to fix it (which I know, should however be no excuse). But I've recently seen this piece of code, which really annoyed me. Why? Not only because it's a lot of repeated code, but also it was like 70 lines of code instead about the 17 it could be. One must scroll the code over and over to see what is was doing instead of having all in the same screen! I know, that can't hurt anybody, but I still disagree with this smalls things ;)<br />
<br />
So, this was the original code:<br />
<br />
<pre class="brush:csharp">public static void LoadPersistentData()
{
try
{
// load plug-in configurations
Manager01.LoadPersistentData();
}
catch( Exception ex )
{
Utilities.Logger.Instance.LogError( ex );
}
try
{
// load user preferences
Manager02.LoadPersistentData();
}
catch( Exception ex )
{
Utilities.Logger.Instance.LogError( ex );
}
// lots of try-catchs...
try
{
// load model configurations
Manager07.LoadPersistentData();
}
catch( Exception ex )
{
Utilities.Logger.Instance.LogError( ex );
}
}
</pre>
<br />
<br />
I've change the real name of the objects because of privacy, but they were all "managers". If we just apply some basic refactoring, just look what do we have in the end. Is it worth the "effort"?<br />
<br />
<pre class="brush:csharp">public static void LoadPersistentData()
{
try
{
Manager01.LoadPersistentData();
Manager02.LoadPersistentData();
/// lots of calls...
Manager07.LoadPersistentData();
}
catch (Exception ex)
{
Utilities.Logger.Instance.LogError(ex);
}
}
</pre>
<br />
I think it's not a big deal to perform such refactoring... And it also doesn't take any time to do it, whether you have the time or not.nachokhanhttp://www.blogger.com/profile/00777464846297951418noreply@blogger.com0tag:blogger.com,1999:blog-2715874640743883267.post-85720846484787992412017-03-01T00:56:00.000-08:002017-03-01T00:58:39.723-08:00Little Awful Practices (Serie)<br />
Although I'm not a perfect developer, it's quite common when I start looking code to find some practices that I don't like at all. And I think that's actually the reason (that I am a normal developer) why I find them so quickly and complain about them. Sometimes is my own code and of course sometimes not.<br />
<br />
In most cases, these are pieces of code which actually do what they are supposed to, and they do it fine, but are very complicated to manage. Or.. maybe not THAT complicated but still large, in a way that they use a lot of "code space" disturbing the developer's sight, or maybe they hide some bugs that are very difficult to find later. So the thing is that some of these practices are innocent and the effort doesn't worth it to to don't let them live. But others must be killed, though.<br />
<br />
Some of these spawns, I guess, are born as a result of having a lot of thing in mind and just not paying enough attention to a particular section of code. That would be the best case, I guess. But unfortunately some of them are just the product of a lack of knowledge, basic knowledge the most time. As I said before: I'm not the great programmer who never make mistakes, I do it, like everybody, but it worth it to pay attention and fix this small bugs once you've seen them. And tell the other developers if you find their bugs (but carefully, because programmers are very susceptible about own bugs ;))<br />
<br />
But I must recognize that there is also the case, where we think we found a bad practice... and it's not a bad practice at all! It's actually a solution that we would have done in a different way. "The grass is always greener on the other side" doesn't apply that commonly in programming ;) <br />
<br />
Summarizing, the idea of this series is to post and "keep in mind" such practices, as some kind of "don't do it" remainders. nachokhanhttp://www.blogger.com/profile/00777464846297951418noreply@blogger.com0tag:blogger.com,1999:blog-2715874640743883267.post-58631052430695641652016-07-31T10:38:00.001-07:002016-07-31T10:41:55.776-07:00The Difference (and relationship) between MVC and MVP patternsI wrote this post like one year ago, but I did it in spanish. Yet I'm trying to write also a little more in english. So here it goes.<br />
<br />
I think this topic is normally a bit confusing due to the big amount of information that we can easily find in the internet and the short time we can spend to analyze all this information.<br />
<br />
The MVC Pattern (Model-View-Controller) is a well-known design pattern which is being used all around the world. In fact, there's no one in the software development who hasn't hear about it. So I'm not gonna explain it. Nevertheless, not so well known is this other pattern called MVP (Model-View-Presenter), although it has gained more terrain in the last years. <br />
<br />
At first view one can be tempted to think "<i>Oh, they only changed the Controller of the MVC for the Presenter of the MVP, so it's almost the same thing, but with another name!</i>".<br />
<br />
Well.. nothing more far away from the truth! Absolutely not! So, what is it? What is the real difference between them? While I'm not gonna explain the MVP pattern in detail, because there is A LOT of information in internet, I would like to explain what is the relationship between these two patterns. How I see it.<br />
<br />
First of all, let's quickly remember what the MVC is about: to separate an application into three conceptually-different parts, so he have:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi8pZzwaNReTjmlymXEyAP7vOl59zPldEsbmraTN8qzWH9p4CIZ3TaJgbluHGENe0vm0KcfEDqK0e3VUYtJ1iLkAajzqFazDFjlpbFTyuw6Zg0MjdzV42d0uJG8tIFsMgQhApRl9Lr89M4/s1600/01_MVC.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="56" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi8pZzwaNReTjmlymXEyAP7vOl59zPldEsbmraTN8qzWH9p4CIZ3TaJgbluHGENe0vm0KcfEDqK0e3VUYtJ1iLkAajzqFazDFjlpbFTyuw6Zg0MjdzV42d0uJG8tIFsMgQhApRl9Lr89M4/s400/01_MVC.png" width="400" /></a></div>
<br />
<ul>
<li><u><b>Model</b></u>: makes reference to <b>data </b>or, better said, to <b>representation of the information</b>. Depending on how you interpret the pattern, the model can be directly bound to the persistence of this information. But in a wider interpretation, it can also mean the whole state of the system in a given moment. So, in its wider interpretation, we can say it embraces both <i>the state of the system</i> and <i>its persistence</i>. </li>
<li><u><b>Controller</b></u>: represents the business logic. The rules on which the systems run..</li>
<li><u><b>View</b></u>: represents the user interface.</li>
</ul>
<blockquote class="tr_bq">
<br />
Here it is worth to mention that I don't particularly agree with the separation between "<i>Model</i>" and "<i>Controller</i>", because I think often is kind of difficult -and expensive in terms of time- to totally exclude the model (<i>the data</i>) from the controller (<i>the business logic i.e. the behaviour</i>). However, I'm not saying it can't be done, but rather consider that there is actually a conceptual separaration between them, althought it might be unnecesary (this will be inherent to each situation and system).</blockquote>
<br />
Now let's briefly review the what the MVP pattern is about:<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjQZKn32KDfJ4y6WpmGZRUdfFpfe9Wi_IUxod8W5FUAOW9G4Ck0Eiw7R6L5Ip34ZGIMdteh8vJNh0o7dYjDQDKamBrqMycMS7cGZJJoE3_MUkEkCloARH-9MUnnd55yArNIBfZ8ziegPHw/s1600/02_MVP.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="65" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjQZKn32KDfJ4y6WpmGZRUdfFpfe9Wi_IUxod8W5FUAOW9G4Ck0Eiw7R6L5Ip34ZGIMdteh8vJNh0o7dYjDQDKamBrqMycMS7cGZJJoE3_MUkEkCloARH-9MUnnd55yArNIBfZ8ziegPHw/s400/02_MVP.png" width="400" /></a></div>
<br />
<ul>
<li><b><u>Model</u></b>: this model represents the <i>business model</i>. <i>Violá</i>! It's not the same as the model of the MVC, but rather this model includes both the <b>representation of the data</b> and the <b>business logic</b>. That's why we talk about a "<b><i>Business Model</i></b>". That is, this model invovles the <b>Model </b>and the <b>Controller </b>of the MVC together.</li>
<li><u><b>Presenter</b></u>: this is the <i>director</i>: on one hand it directs the events <i>of the view to the model</i> and on the other it updates<i> the view with the information coming from the model</i>. This concept does not exists directly in the MVC pattern. I mean <i>directly</i>, because is perfectly valid to affirm that this "<i>Presenter</i>" is a part of the "<i>View</i>" in the MVC pattern. That is, this is <b>the logic</b> that we are used to write into the MVC's View, no matter how basic it is! </li>
<li><u><b>View</b></u>: The View, according to the MVP, is the silliest and easy-to-write code that we have. It's just about a "View" that knows his "Presenter", to whom it <b>delegates absolutely everything</b> that happens in it. This View <i>makes nothing but to delegate</i>. The user clicks, the "View" tells the "Presenter" where did the user click. Or the other way: the "Presenter" tell the "View" to fill a ComboBox, the "View" fills the Combo with the information provided. It just does not have business logic at all! <i><b></b></i></li>
</ul>
<br />
So having this information, if we analyze it a little, we come to the conclusion that both patterns are not mutually excludent. It is not about predicting "We're using the MVC pattern or the MVP pattern", but it could be interesting thinking in a combination of them. That might lead us to something called VPCM (View-Presenter-Controller-Model).<br />
<br />
What?! Does it actually exists?! What are we talking about?<br />
<br />
Nothing new, to be honest. Maybe the name it's new, it doesn't even exist. But that's not the point. The point is that we are breaking down the <b>View of the MVC</b> in two parts: a "<i>View</i>" who does nothing (but to delegate) and its "<i>Presenter</i>" who does everything for the view. Those are, the <b>View and the Presenter of the MVP</b>. Something like this:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgl2Yx2toTP_x1kPagVoSc2wXcTeamhVUs3SrL1KLVguJdv3Y8ep0Y4R5l2_LGbSTirwvhp_3jB4sBnfo68zBiJC3Mq4F8jwDiidNqyUTmBrjgiW1P4y-jEdq_jnXRsAF1J__CDN7xpNqI/s1600/03_VIEW.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="156" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgl2Yx2toTP_x1kPagVoSc2wXcTeamhVUs3SrL1KLVguJdv3Y8ep0Y4R5l2_LGbSTirwvhp_3jB4sBnfo68zBiJC3Mq4F8jwDiidNqyUTmBrjgiW1P4y-jEdq_jnXRsAF1J__CDN7xpNqI/s400/03_VIEW.png" width="400" /></a></div>
<br />
Another way to see this, is the other way: we take the <b>Model of the MVP</b> and we break it down in two new pieces: a <i>Controller </i>(which has the <i>business logic</i>) and a <i>Data Model</i> (which <i>represents the information</i>). That is, we've just get the <b>Controller and the Model of the MVC</b> pattern. It looks something like this: <br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEif6lfIKzwooKqRxv4ks9vY8nhTutgxmC3olIuevRWYosGKG1jXRLyilL4DG0_RgaEmuYkyFnybzaTTp6fYErXAIVS3rUtlnNJGVL0HMBSjC0nux9DSlufS1KU9iboXIeddVNL3763CYHI/s1600/04_Model.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="156" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEif6lfIKzwooKqRxv4ks9vY8nhTutgxmC3olIuevRWYosGKG1jXRLyilL4DG0_RgaEmuYkyFnybzaTTp6fYErXAIVS3rUtlnNJGVL0HMBSjC0nux9DSlufS1KU9iboXIeddVNL3763CYHI/s400/04_Model.png" width="400" /></a></div>
<br />
As we can see after this analysis, <b>they are NOT mutually excludent patterns</b>: they complement each other! We could say that one emphasize <br />
the Backend, and the other the Frontend. MVC and MVP respectively.<br />
<br />
I would like to have some opinions about this analysis and its conclusion.<br />
Cheers!nachokhanhttp://www.blogger.com/profile/00777464846297951418noreply@blogger.com0tag:blogger.com,1999:blog-2715874640743883267.post-31446806325224239332016-07-24T09:10:00.000-07:002016-07-24T12:33:30.183-07:00Writing your first Aspect with Postsharp in C#Hi!<br />
<br />
As some of you might remember, some years ago I started to dabble in the world of Aspects Oriented Programming. I also wrote three posts explaining what is aspect oriented programming, what are its advantages and I gave an example on how to implement aspects in Java using AspectJ. That was a few years ago. Nevertheless, those posts are more relevant than ever!! So if you don't feel that sure with AOP, I´d recommend you to read a few on what aspects are to understand it before you continuing reading. <br />
<br />
But life and likes started taking me to and through the .NET and C# world, so I began to look for new tools. Today there are really a lot of tools and frameworks to support aspects, however I chose the one called <a href="https://www.postsharp.net/" target="_blank">Postsharp</a>.<br />
<br />
Until now, <span class="null">I must admit that the simplicity of this framework seems wonderful for me when compared</span> with other like <a href="http://www.codeproject.com/Articles/26726/AOP-Using-Spring-NET-Part" target="_blank">Spring.NET AOP</a>, which requires a little more of skills when setting it up. Postsharp doesn't need to be configured!! So this is the first advantage.<br />
<br />
That said, let's go to the point and see an example on how this framework works.<br />
<br />
Maybe the correct thing would be to start defining some concepts that I didn't clarify in those posts, and which are usefull to understand what we are doing. But no: today we start directly with an example. After that, in other posts, I will clarify these points. <br />
<br />
<br />
So, lets imagine a very simple scenery. It's about a well-known and complex application called "Hello, World" ;), on which we are going to add an aspect. We are going to create only one aspect, that will be responsible to intercept the program's execution just before calling the method who writes "Hello World" in to the screen.<br />
<br />
<h3>
Step 1: Add Postsharp to the project.</h3>
Once we've created an empty project (e.g. Console Application), we add the Postsharp packet to it. The simplest way to do it is, of course, using <a href="https://www.nuget.org/" target="_blank">NuGet</a>. So easy as opening NuGet and searching the framework by its name. Then adding it to the project.<br />
<br />
<blockquote class="tr_bq">
Here it is worth it to mention, that once added to the project, Postsharp asks the user to download an executable to be installed (version VS > 2010). This is meant to install a component that will added to Visual Studio. Postsharp has a free version which is enough to write aspects with total freedom, but it also has two more versions which are not only not free, but very expensive for the single user, but provide already-written and already-working aspects and also the possibility to write complex aspects. I haven't had yet the chance to try them, but I'm gonna do it as soon as possible. But do not be afraid of it: get the Express version (the free one) to continue. This installation needs to be made only once. So its not a problem.</blockquote>
<h3>
Step 2: Write the "Hello World".</h3>
Actually, steps 1 and 2 are interchangeables. It is really the same. We can write first the "base" project (without aspects) and then install Postsharp. <br />
<br />
I know it can be difficult, so I'm gonna help you. To create the "Hello World" project, you should call the method <i>WriteLine()</i> to write in the console, in the main class.<br />
<br />
<pre class="brush:csharp">using System;
namespace FirstTryWithPostSharp
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello, World!");
}
}
}
</pre>
<br />
I guess you didn't have much more complications in this step! ;) Let's move on!<br />
<br />
<h3>
Step 3: Write the Aspect.</h3>
Now the interesting part: an aspect, is nothing else but a class! Yes, that simple! Well.. but actually this class must accomplish two conditions: <br />
<br />
<ol>
<li>It must be ALWAYS <a href="https://msdn.microsoft.com/es-es/library/ms233843%28v=vs.120%29.aspx" target="_blank">serializable</a>. This is achieved applying the "<a href="https://msdn.microsoft.com/es-es/library/system.serializableattribute%28v=vs.110%29.aspx" target="_blank">Serializable</a>" attribute as we'll see soon..</li>
<li>It ALWAYS inherits from one Postsharp's class, which give our class
the "super powers", to put it in some way. They aren't actually
superpowers, but these Postsharp's classes have methods which can be <a href="https://msdn.microsoft.com/es-es/library/ebca9ah3%28v=vs.120%29.aspx" target="_blank">overriden</a>to redefine them. Postsharp's Core will insert them in our
code during compile time. In our example, it will be inserted just
before the call to <i>Main()</i> method.</li>
</ol>
Ok, lets create the class, which we can call <i>AspectThatInterceptsAMethod</i> and says this:<br />
<br />
<br />
<br />
<pre class="brush:csharp">using PostSharp.Aspects;
using System;
namespace FirstTryWithPostSharp
{
[Serializable]
class AspectThatInterceptsAMethod : OnMethodBoundaryAspect
{
public override void OnEntry(MethodExecutionArgs args)
{
Console.WriteLine("Entering the method: " + args.Method.Name);
}
}
}
</pre>
<br />
<br />
Briefly I want to say that I assume that we already know where this class is being indicated as Serializable, and that the aspect (the class) inherits from the class <a href="http://doc.postsharp.net/t_postsharp_aspects_onmethodboundaryaspect" target="_blank">OnMethodBoundaryAspect</a>, which provides the possibility to rewrite some methods. In this case, we chose the method <i>OnEntry()</i> which indicates that whatever we do inside it will be executed just before executing the methods to which this aspect will be applied. But like <i>OnEntry()</i>, there are other methods that you can also redifne: <i>OnExit()</i>, <i>OnSuccess()</i> y <i>OnException()</i>. Lets see how to apply this aspect to the <i>Main()</i> Method.<br />
<br />
Another little detail, is that args.Method.Name give us the name of the method that was intercepted. This, and much, much more (like accesing to the parameters, and its values) are part the powerful tools with which we count.<br />
<br />
<h3>
Step 4: Indicate to which methods our aspect will be applied.</h3>
<h3>
</h3>
So far we've made an aspect as god demands. But it is isolated, i.e. it is not being used! To use it, we just have to apply it as an attribute in the methods we want to be affected by it. This is made incredible easy in the following way:<br />
<br />
<pre class="brush:csharp"> [AspectThatInterceptsAMethod]
static void Main(string[] args)
{
</pre>
<br />
<br />
If we now run the project, we'll see that the aspect's text is showed at first, and then Main method's text. This occurs this way, because the aspect will be executed always before the method. Why? Just because we decided to overwrite the <i>OnEntry()</i> method. If we'd wanted to show the text <b>before AND after</b> the main's method, then we should have overwritten also the <i>OnExit()</i> method inside the aspect. Yes, only that!.<br />
<br />
Another interesting tip is, that if we want the aspect to be applied to ALL methods of a class, it's just needed to put the attribute over the class name. Like this:<br />
<br />
<pre class="brush:csharp"> [AspectThatInterceptsAMethod]
class Program
{
static void Main(string[] args)
{</pre>
<br />
<br />
And in this way, any method will be automatically a victim of our aspect ;) Easy, don't?<br />
<br />
I invite you to try it by yourself!<br />
<br />
This was an introduction to a very simple aspect. The aspects's world is very much extensive and interesting! Many wonderful things can be done but, however, like everything in software, I recommend you: always analyze which are the real advantages and disadvantages of introducing a new practice, framework or whatever. Aspects are not always needed. Remember: <i>"Having a good hammer doesn't means that every problem is a nail"</i>.<br />
<br />
In my experience, the use of aspects is a growing trend, but because many people is getting used to it and not being afraid of it. I'll try to come back with new things about Postsharp's aspects.nachokhanhttp://www.blogger.com/profile/00777464846297951418noreply@blogger.com0tag:blogger.com,1999:blog-2715874640743883267.post-21303509104731063632016-05-10T12:44:00.000-07:002016-05-11T04:46:47.752-07:00Code Reviews - Parte 1Hace pocas semanas que se me ha encargado la implementación de Code Reviews en la empresa para la cual trabajo. Si bien yo ya tenía la idea de hacer esto hace un tiempo, ahora que está "la orden" dispongo de "más y mejor" tiempo para llevarlo a cabo. Y de forma seria, es decir, como un procedimiento formal.<br />
<br />
<br />
Como suele pasar en muchas de las empresas que desarrollan software para uso interno, la calidad del mismo es más o menos cuestionable. Si bien el software "está funcionando", cuando hay que meterle mano para actualizar, reparar bugs, o agregar nuevas características, comienza la odisea de introducirse en esos códigos de cinco o diez años de antiguedad, sucios, emparchados, manoseados por diferentes personas de las que uno solo recuerda el nombre y con suerte alguna que otra anécdota.<br />
<br />
El problema con esto es que cuando urge algun tipo de mantenimiento siempre es un peligro modificar ese código, porque como dije "ya está funcionando" y uno nunca sabe los efectos colarterales que puede producir al modificarlo.<br />
<br />
Existen algunas formas de minimizar el impacto de modificar estos códigos, por ejemplo, la cobertura con tests. Si tenemos el código cubierto con tests, cualquier modificación que rompa el comportamiento se verá refejada en los test que fallen. Pero seamos sinceros: casi nunca existen estos tests, o menos aún están actualizados.<br />
<br />
Entonces podemos aplicar una segunda técnica, más humana si se quiere, pero que si está bien aplicada puede, a futuro, allanar y simplificar gran parte del camino: los Code Reviews. <br />
<br />
Los Code Reviews, o revisiones de código, consisten en sentar a una persona para que revise una porción de código que no escribió ella misma. De esta forma, puede detectar potenciales bugs, y algunas cosas que para los compiladores es más difícil o imposible: legibilidad y calidad del código. Por ejemplo, si un método es demasiado grande, o si recibe muchos parámetros, o si los nombres de los métodos o las variables no son descriptivos, etc. Si hay codigo redundante, o código duplicado, son todos aspectos que una persona puede identificar mejor que una herramienta, y aunque hay herramientas muy buenas, el "ojo humano" tiene ventajas indiscutibles.<br />
<br />
En fin, retomando, voy a ir comentando un poco el proceso de code reviews que implementemos, que está basado ni más ni menos que la documentación disponible en internet, ya que convengamos que no hay ningún misterio ni secreto en este tema. En fin, más adelante iré comentando como me va con esta experiencia, y que en otra empresa en la cual trabajé se hacían code reviews, pero eran muy informales, sin ningún tipo de planificación, y sin una cultura organizacional que los acompañara, lo cual no ayuda en absoluto y los hace ver como "una molestia que tengo que sacarme de encima rápido para seguir adelante".<br />
<br />
Saludos.nachokhanhttp://www.blogger.com/profile/00777464846297951418noreply@blogger.com0tag:blogger.com,1999:blog-2715874640743883267.post-86490349781830481752016-03-14T16:31:00.001-07:002016-05-10T12:45:46.921-07:00Mi experiencia en el Agile Open Camp 2016Hace una semana comenzaba a terminar el AOC 2016, la segunda edición del <a href="http://www.agileopencamp.com.ar/" target="_blank">Agile Open Camp</a> que se celebra en Argentina desde el año pasado. Fui afortunado al poder participar. Quiero contar un poco lo que es o, más bien, lo que fue para mi.<br />
<br />
Pero es difícil comenzar. Es dificil describir con palabras algo que te emociona, que toca tus sentidos mucho más allá del simple aprendizaje de la filosofía Agile. Porque no se ven exactamente Metodologías Ágiles; para eso ya está plagado internet de libros y artículos. Porque no te explican cómo implementar tal o cual cosa; para eso hay manuales. Vas a compartir.<br />
<br />
<table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: left; margin-right: 1em; text-align: left;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjKbLFO-nAKSGsO50zGuHh3gqbmUkbt8OA8TR-XPiuy3uKOM4QpHdm02Svt2znBScqShOMFBOsFDffTs3SbElsYG2Op8PCNdLoUMT5YZvppBB6jT5kfqI0jx2wA6a_HL8SabUnddVkSwmw/s1600/charla+afuera.jpg" imageanchor="1" style="clear: left; margin-bottom: 1em; margin-left: auto; margin-right: auto;"><img border="0" height="212" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjKbLFO-nAKSGsO50zGuHh3gqbmUkbt8OA8TR-XPiuy3uKOM4QpHdm02Svt2znBScqShOMFBOsFDffTs3SbElsYG2Op8PCNdLoUMT5YZvppBB6jT5kfqI0jx2wA6a_HL8SabUnddVkSwmw/s320/charla+afuera.jpg" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Keynote de Juan Daza, a orillas del río.</td></tr>
</tbody></table>
<br />
En estos Open Camps, vivís tres días y medio aislado del mundo, solo en contacto con otras decenas de personas que buscan lo mismo que vos: compartir momentos y experiencias. El tronco temático es Agile, sí, pero vos podés compartir o escuchar lo que vos quieras. Vos podés aprender de Arduino, de microservicios, de scrum, de bitcoins, nociones sobre el movimiento Slow, e incluso podés pedir que si alguien sabe, te enseñe a tocar guitarra. Hay también quienes se ofrecen para darte un taller de astronomía o eseñarte fotografía nocturna. Pero vos podés ofrecer o pedir lo que quieras.<br />
<br />
Pero por supuesto hay mucho de Agile, experiencias, charlas, workshops, y una modalidad que me encantó que se llama "World Cafe", en la que se discuten temas en mini sesiones de 20 minutos. Y se vive en carne propia la magia de la auto-organización del evento. Vivir la experiencia de esta auto-organización no te lo da ningún libro, ningún curso, ninguna conferencia, ningún congreso. Acá cada uno es libre de participar en lo que quiera (y si es que quiere).<br />
<br />
En medio del evento, de pronto podés conocer a alguien con quién te das cuenta que podés combinarte y dar un taller, gestar una idea que, quien te dice, germine con el tiempo para salir a la luz en forma de un emprendimiento, un proyecto, o simplemente conocer un buen amigo.<br />
<br />
<table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: right; margin-left: 1em; text-align: right;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgU6Vmho7G2bPWnZdRwYmT78mjctgePTnuNBJutcTKpzr3ckN3t2WSZrW7_1gMY2Oel_nZUL_sjw6ZsYPp33cdp2JlssxFQ4kygTmTmDTAF68f-qOGncFZNy3h0ai7HhM5NEYKY0X_1AbI/s1600/sentados.jpg" imageanchor="1" style="clear: right; margin-bottom: 1em; margin-left: auto; margin-right: auto;"><img border="0" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgU6Vmho7G2bPWnZdRwYmT78mjctgePTnuNBJutcTKpzr3ckN3t2WSZrW7_1gMY2Oel_nZUL_sjw6ZsYPp33cdp2JlssxFQ4kygTmTmDTAF68f-qOGncFZNy3h0ai7HhM5NEYKY0X_1AbI/s200/sentados.jpg" width="200" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Auto-organizando talleres.</td></tr>
</tbody></table>
<br />
No menor es el hecho de que el evento se realiza (al menos hasta ahora) en diferentes lugares cercanos -pero no dentro- de la ciudad de San Carlos de Bariloche (Argentina), cuyos paisajes y tranquilidad, condimentan con el sabor mágico que hace falta para crear ese ambiente relajado e inspirador que lo caracteriza y lo hace tan especial, porque convengamos que no sería lo mismo hacer el este evento en el medio de la ciudad, con los ruidos y distracciones que abudan allí. Acá estás "obligado" a compartir con la misma gente todo el tiempo, y eso tiene un efecto impresionante.<br />
<br />
En esta edición en particular, tuvimos la oportunidad de asistir a muchísimos talleres, jugar al fútbol, al paddle, hacer caminatas por la zona en medio de árboles y bordeando ríos, meditar, y hacer un fogón en medio de lo que fue la noche más silenciosa y estrellada de mi vida; con guitarras, chistes, charlas y un corderito que rebasaba absolutamente cualquier tipo de pretención que pudierámos tener.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: left; margin-right: 1em; text-align: left;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEir7wI4FK06oK-k9vzmbkSW9DTqksbYJBWFTjI1c9Y0WexQjtZmcw1zL8sBMIOpnH_tce1p64fOpvamfiQMNZ-Xv8Pco80oN4PPbuOzcA9eqfdGeOn8L2rx0S8hWwjYWDGKKQ5HPwOPZHk/s1600/charlas.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="177" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEir7wI4FK06oK-k9vzmbkSW9DTqksbYJBWFTjI1c9Y0WexQjtZmcw1zL8sBMIOpnH_tce1p64fOpvamfiQMNZ-Xv8Pco80oN4PPbuOzcA9eqfdGeOn8L2rx0S8hWwjYWDGKKQ5HPwOPZHk/s320/charlas.jpg" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Cronograma tentativo.</td></tr>
</tbody></table>
Lo importante, dicen algunos, no es sólo pasarla bien durante los días que dura el evento. Lo importante es que, además, el evento se detenga mágicamente en el tiempo dentro de cada uno de nosotros, adoptando la curiosa forma de la motivación y, de alguna manera, pase a formar parte los días subsiguientes, y los meses, y si es posible de la vida. Es decir, que produzca un cambio en nosotros, que nos llene de energía para poder comenzar una transformación como personas, mejorar las relaciones con los demás, generar confianza, y crecer.<br />
<br />
Sigo sintiendo que no puedo aún expresar la magia que me dejó esta experiencia. Creo que si tengo que compararlo con algo para explicárselo a quien nunca fue, es como un retiro espiritual de agilidad. No soy creyente, pero es una comparación muy acertada, porque te quedás con esa hermosa sensación de "yo puedo -y voy- a cambiar el mundo".<br />
<br />
Para terminar, quiero citar a Mauro Strione, uno de los organizadores principales del AOC, que expresa en lindas palabras, lo que muchos sentimos una vez que terminó el evento:<br />
<br />
"<i>Fueron tres días llenos de energía, motivación, compromiso, ganas... y
sin obligar a nadie a nada, es más, todos vienen porque quieren, no
porque los mandan de su empresa. La mayoría se lo pagan de su bolsillo..."</i><br />
<br />
"<i>No se si logro transmitir de qué se trata esto, quizás suene a secta o a
que estamos locos. Solamente quiero advertir que cuando los geeks
descubrimos el potencial de los ceros y unos de las computadoras,
transformamos el mundo en pocos años, si ahora empezamos a entender a
las personas y sus relaciones, sus sentimientos y motivaciones, y
logramos combinarlos con esos ceros y unos... cuidado con lo que podemos
lograr!</i>"<br />
<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjUQ0qI0YRL-viC-tA7fTuzgjLIGxpOz6hHMHW-ibXty-7QZoSJWS9K1LJwflQom2M5LaEd71nzKO4crl0UwblbxtNWlaJjq0XFl6OC-nVU6kdnEd5UEXRf64xgxevhwj5Ghe5XAIf9f1Y/s1600/nocturna.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="263" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjUQ0qI0YRL-viC-tA7fTuzgjLIGxpOz6hHMHW-ibXty-7QZoSJWS9K1LJwflQom2M5LaEd71nzKO4crl0UwblbxtNWlaJjq0XFl6OC-nVU6kdnEd5UEXRf64xgxevhwj5Ghe5XAIf9f1Y/s400/nocturna.jpg" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Fotografía de las noches de Bariloche.</td></tr>
</tbody></table>
nachokhanhttp://www.blogger.com/profile/00777464846297951418noreply@blogger.com1tag:blogger.com,1999:blog-2715874640743883267.post-18090812280239863492016-03-09T19:32:00.001-08:002016-03-09T19:32:43.525-08:00C# - Obtener canales RGB de un BitmapVamos a trabajar con el siguiente bitmap tomado de internet para trabajar:<br />
<br />
<div style="text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjK_Bo5THcmbVMJ4tpCrMscSMon9D6oXY0X9qbgjJI9nAhqQbWCwuMUNOspyRGjnHB9J8eYBvAb-5jCnYPwW0hFEgVqEd7dN59_LWsSuai4wY2c3-ja67NMJlstaAS5ANn-x_R55GhbJ0I/s1600/rgb.jpg" imageanchor="1"><img border="0" height="212" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjK_Bo5THcmbVMJ4tpCrMscSMon9D6oXY0X9qbgjJI9nAhqQbWCwuMUNOspyRGjnHB9J8eYBvAb-5jCnYPwW0hFEgVqEd7dN59_LWsSuai4wY2c3-ja67NMJlstaAS5ANn-x_R55GhbJ0I/s320/rgb.jpg" width="320" /></a> </div>
<br />
Para descomponer un Bitmap de tres canales (RGB) en tres Bitmaps que representen cada uno, un canal del Bitmap original, podemos hacer lo siguiente: <br />
<br />
<br />
<pre class="brush:csharp">rgb = new Bitmap("imagen.bmp");
int width = rgb.Width;
int height = rgb.Height;
Bitmap channelRed = new Bitmap(width, height);
Bitmap channelGreen = new Bitmap(width, height);
Bitmap channelBlue = new Bitmap(width, height);
for(int x = 0; x < width; x++)
{
for(int y = 0; y < height; y++)
{
Color color = rgb.GetPixel(x, y);
Color colorRed = Color.FromArgb(color.R, color.R, color.R);
Color colorGreen = Color.FromArgb(color.G, color.G, color.G);
Color colorBlue = Color.FromArgb(color.B, color.B, color.B);
channelRed.SetPixel(x, y, colorRed);
channelGreen.SetPixel(x, y, colorGreen);
channelBlue.SetPixel(x, y, colorBlue);
}
}</pre>
<br />
De esta forma, obtenemos 3 bitmaps, cada uno en blanco y negro y representando la tonalidad de cada canal<span style="color: #0000ee;"><span style="color: #0000ee;">:</span></span><br />
<br />
<div style="text-align: center;">
<span style="color: #0000ee;"><span style="color: #0000ee;"> <a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgWfrwzEddPv510AFQtOsN190whiXF3Jooq_Xr8eWb0RSgHnf_ddrhQ3qRYsvwv9ME62U3jt5oif3Tiig0KrlSnwdKDY9vImn4nBbwyWXMfbdQPE76ovYUdMFlF96dKB3YEvXO6Wzl5zZc/s1600/red.bmp" imageanchor="1"><img border="0" height="132" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgWfrwzEddPv510AFQtOsN190whiXF3Jooq_Xr8eWb0RSgHnf_ddrhQ3qRYsvwv9ME62U3jt5oif3Tiig0KrlSnwdKDY9vImn4nBbwyWXMfbdQPE76ovYUdMFlF96dKB3YEvXO6Wzl5zZc/s200/red.bmp" width="200" /> </a><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJEh36HHuvtGFaS5WsGotwid7HDI-OJxfKjbPfHL-otfooPLLb-uy0j-E5rbFPAFqtDQIqCJgzgjmAaGRsydUMs47qXO45NcdLKzK-hB_b7E1Hnkjm7H7HrPMN9BO4Q7rxIWJqMyFA7yg/s1600/green.bmp" imageanchor="1"><img border="0" height="132" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJEh36HHuvtGFaS5WsGotwid7HDI-OJxfKjbPfHL-otfooPLLb-uy0j-E5rbFPAFqtDQIqCJgzgjmAaGRsydUMs47qXO45NcdLKzK-hB_b7E1Hnkjm7H7HrPMN9BO4Q7rxIWJqMyFA7yg/s200/green.bmp" width="200" /> </a><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhr0PlRroYzovxUzDOy22yN9QECaWPwxBtRr5GrBdKnDwDrJzBw-sMnq4qLnYQ9_OpPax0e8Gqy4MCZSCtXM-PT8r1j0xxEIVNvYo0Zinfv0-rqMMdOFWc9zAPGxMBjoncQE4LDZUE0k9w/s1600/blue.bmp" imageanchor="1"><img border="0" height="132" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhr0PlRroYzovxUzDOy22yN9QECaWPwxBtRr5GrBdKnDwDrJzBw-sMnq4qLnYQ9_OpPax0e8Gqy4MCZSCtXM-PT8r1j0xxEIVNvYo0Zinfv0-rqMMdOFWc9zAPGxMBjoncQE4LDZUE0k9w/s200/blue.bmp" width="200" /></a></span></span></div>
<br />
Esto se ve así, porque ocupamos cada canal, de cada imagen, con el canal que obtuvimos previamente. Es decir, extrajimos el canal rojo de la imagen original, y grabamos todos los canales de channelRed con ese componente. Al tener cada canal el mismo valor, se ve gris. Si quisieramos que cada imagen contenga únicamente el valor del canal que extraemos, tendríamos que dejar los otros dos canales en cero. Cambiemos entonces la parte del código que asigna los colores:<br />
<br />
<pre class="brush:csharp">Color color = rgb.GetPixel(x, y);
Color colorRed = Color.FromArgb(color.R, 0, 0);
Color colorGreen = Color.FromArgb(0, color.G, 0);
Color colorBlue = Color.FromArgb(0, 0, color.B);
</pre>
<br />
Y lo que obtenemos es esto:
<br />
<br />
<div style="text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgGE5oFMg9wfsrbw3I89Emp4bUMyY2HhHNO-ZB-3knD-H_smX3RY5YmTaboUe7uw4e6uQSr4j6BXwDFiZT-XQ7qSiapUyCkx4u6vu7spg4nbEmkRCSOuyRfcoR9sHz-bGG7oeIAuih0nvA/s1600/red.bmp" imageanchor="1"><img border="0" height="132" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgGE5oFMg9wfsrbw3I89Emp4bUMyY2HhHNO-ZB-3knD-H_smX3RY5YmTaboUe7uw4e6uQSr4j6BXwDFiZT-XQ7qSiapUyCkx4u6vu7spg4nbEmkRCSOuyRfcoR9sHz-bGG7oeIAuih0nvA/s200/red.bmp" width="200" /> </a><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhvbEhYkN1S_WWuGwytR9ItwsgeSIUC8BkZ_Rl46d_Uy3M0BXrAX6cwKSDwjnRlDqCH3R3GVTMIhf3wJupLM7Zz_oUBgMRPpO4Erhj49BKEdpfHk1l7eFc6AgqKt0T8sDPjccvKNGACfWY/s1600/green.bmp" imageanchor="1"><img border="0" height="132" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhvbEhYkN1S_WWuGwytR9ItwsgeSIUC8BkZ_Rl46d_Uy3M0BXrAX6cwKSDwjnRlDqCH3R3GVTMIhf3wJupLM7Zz_oUBgMRPpO4Erhj49BKEdpfHk1l7eFc6AgqKt0T8sDPjccvKNGACfWY/s200/green.bmp" width="200" /> </a><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgjW2ANFVv09KT3wDCCHc_iTF02O_5eAvlByju4YXXk3F7Ov7D0IaQhFGRNan72ZoDsMHOhz8_KIQtkaZ-SXBXjl4Re1MlkvXmJQSlMKemV_cFgx-VLKpmJdHK63gDaw7eCDyDEurTgqGo/s1600/blue.bmp" imageanchor="1"><img border="0" height="132" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgjW2ANFVv09KT3wDCCHc_iTF02O_5eAvlByju4YXXk3F7Ov7D0IaQhFGRNan72ZoDsMHOhz8_KIQtkaZ-SXBXjl4Re1MlkvXmJQSlMKemV_cFgx-VLKpmJdHK63gDaw7eCDyDEurTgqGo/s200/blue.bmp" width="200" /></a></div>
<br />
Si bien el resultado es el esperado, esta forma no es precisamente rápida. En caso de imágenes más grandes, el proceso tarda más, y más. Y ni hablar si queremos operar con estos valores. Hay, claro, formas de trabajar con esto mucho más rápido, en otro momento las veremos.<br />
<br />nachokhanhttp://www.blogger.com/profile/00777464846297951418noreply@blogger.com0tag:blogger.com,1999:blog-2715874640743883267.post-88655841035100575982016-02-15T17:48:00.003-08:002016-02-26T16:44:43.868-08:00¿Qué es el NDVI, o Indice de Diferencia Normalizada de Vegetación?Esta vez no voy a hablar de ningún concepto del software, y me permito divagar por otros ámbitos que no se le parecen demasiado, como es el tema del agro. No se parecen, pero están relacionados. ¿Por qué? Porque TODO, tarde o temprano, termina esclavizando al desarrollo de software para sobrevivir. Los informáticos, somos una espcie necesaria para la industria, nos guste o no.<br />
<br />
Hace unos años, allá a madiados de de los setenta -si mal no recuerdo-, un científico se hallaba en su trabajo, ni más ni menos que la NASA, analizando imagenes satelitales del LandSAT (un satelite que tomaba fotografías areas de la Tierra). Su estudio se orientaba al análisis de la vegetación sobre la tierra.<br />
<br />
Este señor, cuyo nombre era Compton Tucker, después de mucho analizar, y obtener resultados, escribió un paper llamado "Red and Photograghic Infrared Linear Combinations for Monitoring Vegetation", es decir: Combinaciones Lineales del Rojo e Infrarojo Fotográfico para Monitoreo de la Vegetación". De los resultados que obtuvo, nos vamos a ocupar en este post.<br />
<br />
Aunque a simple vista puede sonar aburrido a quienes no son del palo de la agricultura -y similares-, lo que este señor encontró es sorprendente: Un árbol en buen estado de salud refleja muchísima luz infraroja, y muy poca luz roja. Pero si este árbol se enferma, entonces comienza a reflejar muchísima menos luz infraroja, casi en la misma cantidad que la luz roja. Y si se muere dicho árbol, entonces deja de irradiar luz infraroja, comparado con la luz roja. Todo esto tiene una explicación lógica, que está relacionada con la clorofila, pero por ahora no nos interesa.<br />
<br />
Me permito tomar prestada una imagen de internet para ilustrar este concepto con imágenes, que siempre resulta más comprensible y entretenido:<br />
<br />
<div style="text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhm2UXXJDriWcgVfIb9kWVyLE5j4wXWyirWmSrNvYhw40O5uRQYAqUNqow94Lxg6kAyocjxBFWR3A6YiOMSf4Gi7iUfB8wiDciqNS7yx4lUE4ZQrYr62PhE8KGGxMoIQVUoG0099k1cDQs/s1600/hojasss.jpg" imageanchor="1"><img border="0" height="341" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhm2UXXJDriWcgVfIb9kWVyLE5j4wXWyirWmSrNvYhw40O5uRQYAqUNqow94Lxg6kAyocjxBFWR3A6YiOMSf4Gi7iUfB8wiDciqNS7yx4lUE4ZQrYr62PhE8KGGxMoIQVUoG0099k1cDQs/s400/hojasss.jpg" width="400" /></a></div>
<br />
Aunque está en inglés, es bastante simple:<br />
<ul>
<li><b>La hoja saludable</b> (<i>Healthy Leaf</i>) refleja muchísismo la luz infraroja (NIR), y bastante luz verde y es por eso que un arbol sano se ve de color verde.</li>
<li><b>La hoja estresada</b> (<i>Stressed Leaf</i>) refleja muchísimo menos infrarojo, y un poco más de rojo y verde.</li>
<li><b>La hoja muerta</b> (<i>Dead Leaf</i>) refleja aún menos infrarojo que la estresada, y menos verde, por eso se ve más opaca y apagada.</li>
</ul>
A esta altura, tiene que estar clarísimo que nosotros los seres humanos NO podemos ver los rayos infrarojos, y por eso no nos damos cuenta si un arbol refleja más o menos luz infraroja. No señor, no podemos. Pero en nuestras casa, y a diario, contamos con una herramenta que SÍ distingue la luz infraroja: las cámaras de fotos digitales. Si alguna vez te preguntaste por qué yo no puedo ver la luz que emite un control remoto, pero cuando lo miro a través de una cámara de fotos sí puedo verla, ésta es la razón. El sensor de la cámara distingue la luz infraroja y la convierte en luz visible que puede variar entre blanco, violeta, o rosado. (<i>TIP: esto se usa mucho para comprobar si un control remoto funciona, o no</i>).<br />
<br />
Volviendo al señor Compton, lo que hizo de interesante, fue describir algunos modelos que se basaban en relaciones matemáticas entre la luz roja y la luz infraroja, y que permiten comprobar el estado de salud de una vegetación.<br />
<br />
De todas las que hizo, la fórmula que más le convenció, se llama <b>NDVI </b>(<i>Normalized Difference Vegetation Index</i>), o Índice de Diferencia Normalizada de Vegetación, y es la siguiente:<br />
<br />
<div style="text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgbF9Vohs_G249DmQXu2ufB9mxeu-Hz0YYhrx9HdhsrPcl0ZstwkhyX1KLCjIz3mNU_dSRGBDKza-pHIFmJQKuf4WzTWKAbwRNP01V3uEmFlKw9Zin1h94Pvn7ZzGexx07WVX76OYybiNc/s1600/NDVI_Traditional_Formula.jpg" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgbF9Vohs_G249DmQXu2ufB9mxeu-Hz0YYhrx9HdhsrPcl0ZstwkhyX1KLCjIz3mNU_dSRGBDKza-pHIFmJQKuf4WzTWKAbwRNP01V3uEmFlKw9Zin1h94Pvn7ZzGexx07WVX76OYybiNc/s400/NDVI_Traditional_Formula.jpg" /></a></div>
<br />
dónde <i>NIR </i>representa la luz infrarroja, y <i>Red </i>-obviamente- la roja.<br />
<br />
Esta fórmula nos da valores que se encuentran entre -1 y 1. Mientras más cercano al -1, peor es la salud de la planta, y mientras más cercano a 1, mejor es la salud de la planta. Los valores intermedios, corresponden a estados de estrés, aunque para comprender realmente el significado o el motivo de los números, es siempre conveniente consultar con un experto en el tema del agro.<br />
<br />
Mediante la aplicación correcta de esta fórmula, que Compton utilizo con imagnes satelitales, podemos obtener nuevas imágenes que nos muestran el estado de la vegetación que hablamos recién. Aquí tenemos un ejemplo, pero con imagenes no-satelitales:<br />
<br />
Primero, contamos con esta imagen normal tomada por una cámara común y corriente, y que puede también guardar los infrarojos:<br />
<br />
<div style="text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhj8nJqZSxt4v7XiWDVckDc4Ro_W3CcVpnjNTvl2rKAqacEN-6X7_GtNG6YMifRCb4285T8hZsiOK-pyuMn40cTmZVgfLKFNO9E33I1y9kOoBvPnNFedmZt5-KBvkTYqjBi7-dK_qNxCbg/s1600/rgb.jpg" imageanchor="1"><img border="0" height="212" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhj8nJqZSxt4v7XiWDVckDc4Ro_W3CcVpnjNTvl2rKAqacEN-6X7_GtNG6YMifRCb4285T8hZsiOK-pyuMn40cTmZVgfLKFNO9E33I1y9kOoBvPnNFedmZt5-KBvkTYqjBi7-dK_qNxCbg/s320/rgb.jpg" width="320" /></a></div>
<br />
Luego de aplicarle la fórmula del NDVI, obtenemos la siguiente imagen:<br />
<br />
<div style="text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgSi4-CYMld01oYSGLMFawFO7GCM-2INbo9yQ6Er-llTU80OYEJsCIGzt_jtC6WTc2EUynrpK32K4aa66b7E7YAgW1FCCHIYjp4qAwzO_LCF31KohTddnLqH9zqrhZ0CTzq341W9OmM0aM/s1600/ndi.jpg" imageanchor="1"><img border="0" height="212" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgSi4-CYMld01oYSGLMFawFO7GCM-2INbo9yQ6Er-llTU80OYEJsCIGzt_jtC6WTc2EUynrpK32K4aa66b7E7YAgW1FCCHIYjp4qAwzO_LCF31KohTddnLqH9zqrhZ0CTzq341W9OmM0aM/s320/ndi.jpg" width="320" /></a></div>
<br />
Donde podemos distinguir claramente qué parte de la vegetación está más saludable (la verde) y cual más seca (naranja). Y además, podemos distinguir perfectamente aquellas partes que no son vegetación (rojo).<br />
<br />
Es sorprendente la claridad de la información que nos da la imagen! Esto se utiliza mucho con fotos aereas de cultivos, para encontrar problemas en el riego, o de otra indole que pueden estar afectando dichos cultivos. Es una forma relativamente económica y fiable, de la cual también se pueden llegar a encontrar patrones de problemas. <br />
<br />
En este momento estoy desarrollando un software para que, dada una imágen, calcule la imágen NDVI correspondiente. De hecho, de estas imágenes que uso como ejemplo, la del NDVI fue calculada con mi software. Si bien el software está funcionando, aún debo pulir la interfaz de usuario, para hacerlo más amigable. <br />
<br />
Esto es todo por ahora, más adelante presentaré este programa que permite automatizar el cálculo de NDVI, y en tiempos muy cortos.<br />
<br />
Saludos!nachokhanhttp://www.blogger.com/profile/00777464846297951418noreply@blogger.com4tag:blogger.com,1999:blog-2715874640743883267.post-8218676386410927192015-08-21T19:13:00.002-07:002015-08-21T19:15:42.782-07:00Diferencia -y relación- entre los patrones MVC y MVPCreo que este tema se presta un poco a confusión debido a la cantidad de información que existe en internet y el poco tiempo que disponemos para analizar toda esta información.<br />
<br />
El patrón <a href="https://www.google.com/search?q=patron+MVC&ie=utf-8&oe=utf-8" target="_blank">MVC (Modelo-Vista-Controlador)</a> es muy utilizado y creo muy pocas personas del ambiente del desarrollo no lo conocen, por lo que no voy a hablar de este patrón.<br />
<br />
Sin embargo, no tan famoso es éste otro patrón, que ha tomado un poco más de fuerza desde hace unos años, y sigue escalando: el patrón <b>MVP</b>. El patrón MVP hace referencia a: <i>MODELO</i>-<i>VISTA</i>-<i>PRESENTADOR</i>.<br />
<br />
A simple vista, uno se siente tentado a pensar: "<i>Ah! Cambiaron el Controlador del MVC por algo nuevo llamado Presentador, que debe hacer algo parecido pero con otro nombre.</i>." <br />
<br />
Ja! Nada más alejado de la realidad! Absolutamente no! Entonces, ¿qué es?<br />
<br />
Si bien no voy explicar qué es, porque sería seguir agregando más información de lo mismo, y <a href="https://www.google.com/search?q=patron+MVP&ie=utf-8&oe=utf-8" target="_blank">en google ya hay mucha</a> y buena, yo quiero hablar sobre <i>qué relación existe entre ambos patrones,</i> y por ende, también hablar de <i>cuál es la diferencia</i>.<br />
<br />
Primero que nada, recordemos rápidamente que la intención del MVC es separar una aplicación en tres partes fundamenales, pero de alguna manera relacionadas. Tenemos:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj3MEzxnzLbb0sso2CI9qiYuX-VB6i7gmfyPuoJsrL6OJ-d_2EWti4a1iDGn3eHKBtv2R8rF2pniS80fruFt83F4L_8amPMRPqvk43mqohl5YNLIK2b-GemnuRZ6Lhprl1AQtKl4Vbbh3A/s1600/01_MVC.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="57" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj3MEzxnzLbb0sso2CI9qiYuX-VB6i7gmfyPuoJsrL6OJ-d_2EWti4a1iDGn3eHKBtv2R8rF2pniS80fruFt83F4L_8amPMRPqvk43mqohl5YNLIK2b-GemnuRZ6Lhprl1AQtKl4Vbbh3A/s400/01_MVC.png" width="400" /></a></div>
<ul>
<li> <u><b>Modelo</b></u>: que hace referencia a los <b><i>datos </i></b>o, mejor dicho, a la <b><i>representación de la información</i></b>. Según cómo se interprete el patrón, el modelo puede estar directamente ligado a la persistencia de dicha información. Sin mebargo, en una interpretación más amplia, puede abarcar también el estado del negocio en un momento dado. Es decir que, en su versión más amplia, el modelo es el <i>estado del negocio</i> y la <i>persistenca de ese estado</i>. </li>
<li><u><b>Controlador</b></u>: que representa la <i><b>lógica del negocio</b></i>. Las <i><b>reglas </b></i>que hacen al negocio.</li>
<li><u><b>Vista</b></u>: que representa la <i><b>interfaz </b></i>con el usuario.</li>
</ul>
<br />
<blockquote class="tr_bq">
Acá quiero aclarar que yo particularmente no estoy muy de acuerdo con la separación entre "<i>Modelo</i>" y "<i>Controlador</i>", porque pienso que muchas veces es dificil y costoso excluir totalmente al modelo (representación de los datos) de la lógica del negocio (o sea, del controlador). Sin embargo, no estoy diciendo que no se pueda hacer, o no se deba hacer, sino más bien que hay que considerar que existe una separación coneptual de ambas cosas, sin que esto implique necesariamente llevarla a cabo (aspecto que será inherente a cada situación).</blockquote>
<br />
<br />
Ahoa bien, si repasamos brevemente el MVP, nos encontramos con lo siguiente:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEivcbKlFu1Mnwnp9fs4UACJVY5OcC2MofLL8EDIkJTu0paH_pycapa5QMZ15o_Wq7kqmNwoB6SwMrJSRzhgVtodwLlkc5TdtBelopJmVv90v6RsscWp0t37Bdx0gcGFEFABV9bknkyqSMs/s1600/02_MVP.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="65" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEivcbKlFu1Mnwnp9fs4UACJVY5OcC2MofLL8EDIkJTu0paH_pycapa5QMZ15o_Wq7kqmNwoB6SwMrJSRzhgVtodwLlkc5TdtBelopJmVv90v6RsscWp0t37Bdx0gcGFEFABV9bknkyqSMs/s400/02_MVP.png" width="400" /></a></div>
<br />
<ul>
<li><b><u>Modelo</u></b>: aquí el modelo representa el <i><b>modelo del negocio</b></i>. <i>Voilá</i>! No es lo mismo que el modelo del MVC, sino que este modelo incluye tanto la <i>representación de los datos</i> como la <i>lógica del negocio</i>: por eso hablamos de "<b>modelo del negocio</b>". Es decir que este modelo abarca al <i>Modelo y al Controlador del MVC</i> juntos.</li>
<li><u><b>Presentador</b></u>: El presentador es quien dirige: por un lado los <i>eventos de la vista hacia el modelo</i>, y por otro lado <i>actualiza la vista con las información provenientes del modelo</i>. Este concepto no existe directamente dentro del patrón MVC. Digo "<i>directamente</i>" porque es perfectamente válido afirmar que este presentador <i><b>es parte de </b></i>la Vista del MVC. Es decir, es la <i>lógica </i>que le solemos poner a la vista, por más básica que sea! </li>
<li><u><b>Vista</b></u>: La vista, según el MVP, es lo más tonto y fácil del programar que hay. Se trata de una vita propiamente dicha que conoce a su presentador, a quién le <b>delega </b>absolutamente todo. Ella no hace nada, excepto delegar. Ocurre un click: le dice al presentador que se haga cargo del click. Asi de simple. Ocurre algún otro evento: le dice al presentador que se encargue de ese evento. Pero ella no procesa nada, no hace nada. Solo<i><b> delega el trabajo al presentador</b></i>.</li>
</ul>
<br />
Despues de analizar un poco esto, podemos llegar a la conclusión de que ambos patrones no son excluyentes. No es cuestión de decir Vamos a usar el MVC o el MVP, sino más bien, puede ser interesante pensar en una <i>combinación </i>de ambos. Algo así como un<i> VPCM (Vista-Presentador-Controlador-Modelo)</i>.<br />
<br />
Qué!? Eso no existe! De qué estamos hablando?<br />
<br />
De nada nuevo, en realidad. Sólo estamos desglosando la <i><b>Vista </b></i>del <i>MVC </i>en dos partes: una <i>vista </i>que no hace nada, y su <i>presentador </i>que hace todo. Es decir, de la <i><b>Vista del MVC</b></i> obtenemos <i><b>la </b><b>Vista y el Presentador del MVP</b></i>. Algo así:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-Fdhve67yFdzXVgQf02-tkBWzayNMebGABzDqjeNfZStzEcLwjl_ng9i_gSn2NNoXJY7Ztkg9qT55KMa45WcEGSvSGDQptl4g-r2MkU5SQwfd03fuzI9B7SohyphenhyphenaT5DJ4_4c29qNuFhYg/s1600/03_VIEW.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="155" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-Fdhve67yFdzXVgQf02-tkBWzayNMebGABzDqjeNfZStzEcLwjl_ng9i_gSn2NNoXJY7Ztkg9qT55KMa45WcEGSvSGDQptl4g-r2MkU5SQwfd03fuzI9B7SohyphenhyphenaT5DJ4_4c29qNuFhYg/s400/03_VIEW.png" width="400" /></a></div>
<br />
<br />
Otra forma de verlo, es la inversa: agarramos el <i><b>Modelo </b></i>del MVP y los desglosamos en un <i>controlador </i>(con la lógica del negocio) y un <i>modelo </i>de datos (represetación de los datos). Es decir, del <i><b>Modelo del MVP</b></i> obtenemos <i><b>el </b><b>Controlador y el Modelo del MVC</b></i>. Algo así:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjhridmUKgJUVN_8RpsWLGK_3JX2JEzvg08CuOPppncgqWgiXMzifgM3ijb07f6yXeAcnR_c29pmrsFMvAedOUqlXpPowY36efm7dvrKCy2blgAFdN0OcNC34NE6Kv4Ie0C9ZgGY7suBcg/s1600/04_Model.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="156" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjhridmUKgJUVN_8RpsWLGK_3JX2JEzvg08CuOPppncgqWgiXMzifgM3ijb07f6yXeAcnR_c29pmrsFMvAedOUqlXpPowY36efm7dvrKCy2blgAFdN0OcNC34NE6Kv4Ie0C9ZgGY7suBcg/s400/04_Model.png" width="400" /></a></div>
<br />
<br />
Cómo podemos ver después de este análisis, NO son patrones mutuamente excluyentes: son complementarios. Por decirlo así, uno hace más hincapié en el <i>back-end</i>, y el otro en el <i>front-end</i>.<br />
Me gustaría leer opiniones que aporten a este análisis y ésta conclusión. <br />
Saludos!nachokhanhttp://www.blogger.com/profile/00777464846297951418noreply@blogger.com8tag:blogger.com,1999:blog-2715874640743883267.post-15467480335743989882015-07-31T17:22:00.003-07:002015-07-31T17:22:52.092-07:00Aspectos en C# con Postsharp (Un ejemplo muy simple)Buenas!<br />
Como algunos recordarán, hace unos años comencé a incursionar en el mundo de la Programación Orientada a Aspectos. En aquella época, hice tres entradas explicando <a href="http://includeblogh.blogspot.com.ar/2011/04/programacion-orientada-aspectos-que-es.html" target="_blank">qué es</a> la programación orientada a aspectos, <a href="http://includeblogh.blogspot.com.ar/2011/04/programacion-orientada-aspectos-por-que.html" target="_blank">qué ventajas tiene</a>, y hasta di <a href="http://includeblogh.blogspot.com.ar/2011/04/programacion-orientada-aspectos.html" target="_blank">un ejemplo de cómo implementarla en Java</a> usando AspectJ. De eso hae ya unos años. Sin embargo, dichas entradas siguen más vigentes que nunca!! (Así que si no sabés muy bien lo que son y querés una introducción corta y clara, te recomiendo las dos primeras antes de seguir leyendo)<br />
<br />
Sin embargo, la vida y los gustos me fueron llevando al mundo de .NET y C#, por lo que comencé a buscar nuevas herramientas. Hoy en día, hay realmente muchas herramientas y framworks para trabajar con aspectos. Sin embargo, yo preferí inclinarme al framework <a href="https://www.postsharp.net/" target="_blank">Postsharp</a>. <br />
<br />
Hasta ahora, admito me parece una maravilla la simplicidad de este framework comparado con otros, como <a href="http://www.codeproject.com/Articles/26726/AOP-Using-Spring-NET-Part" target="_blank">Spring.NET AOP</a>, que requieren un poquito más de maña con la configuración. Postsharp no necesita configurarse!!<br />
<br />
Bueno, vamos al grano, y veamos un ejemplo de como funciona este sencillo framework.<br />
<br />
Quizás lo correcto sería comenzar con algunas definiciones que no aclaré en los posts pasados, y que son de utilidad conceptual. Pero no: hoy comenzamos con un ejemplo, directamente. Después, en otro post aclararemos esos puntos.<br />
<br />
Muy bien: vamos a suponer un escenario muy simple. Se trata de una compleja aplicación conocida como "Hola Mundo", a la cual le vamos a añadir un aspecto. Vamos a crear un solo aspecto que se encargue de interceptar la ejecución justo antes de ejecutar el método que escribe "Hola Mundo".<br />
<br />
<h3>
Paso 1: Agregar Postsharp al proyecto.</h3>
Una vez creado un proyecto vacío (por ejemplo, una consola), le agregamos el paquete de Postsharp. La manea más simple de hacerlo es, naturalmente, con <a href="https://www.blogger.com/%3Cpre%20class=%22brush:csharp%22%3E" target="_blank">NuGet</a>. Tan simple como abrir NuGet y buscar el framework por su nombre. Luego agregarlo al proyecto.<br />
<blockquote class="tr_bq">
Aquí vale aclarar, que una vez agregado al proyecto, Postsharp pide al usuario de descargar un ejecutable e instalarlo. (version VS > 2010). Esto es para instalar un componente agregado al Visual Studio. Postsharp tiene una versión gratuita que es suficiente para escribir aspectos con total libertad, pero también posee dos opciones más (que son pagas, y muy caras) las cuales proveen aspectos ya escritos y funcionando. Personalmente no he tenido el agrado de probar estas versiones aún. Pero sin miedo: instalá la versión Express (gratuita) para poder seguir. Esta instalación solo se realiza una vez.</blockquote>
<br />
<h3>
Paso 2: Crear el Hola Mundo.</h3>
En realidad, los pasos 1 y 2 son intercambiables. Realmente es lo mismo. Podemos hacer primero el proyecto "base" (sin aspectos) y luego instalar Postsharp.<br />
<br />
Para crear el proyecto "Hola Mundo", nada tan simple como en la clase principal hacer una llamada a la consola para escribir en pantalla<br />
<br />
<pre class="brush:csharp">using System;
namespace PruebaPostSharp
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hola Mundo!");
}
}
}
</pre>
<br />
<br />
Este paso fue fácil, no? ;) Sigamos!<br />
<br />
<h3>
Paso 3: Crear el Aspecto.</h3>
El aspecto es, nada más y nada menos, que una clase. Si así de simple. Bueno, es una clase con dos particularidades:<br />
<br />
<ol>
<li>SIEMPRE debe ser <a href="https://msdn.microsoft.com/es-es/library/ms233843%28v=vs.120%29.aspx" target="_blank">serializable</a>. Esto se logra aplicando el atributo "<a href="https://msdn.microsoft.com/es-es/library/system.serializableattribute%28v=vs.110%29.aspx" target="_blank">Serializable</a>" como ya veremos.</li>
<li>SIEMPRE hereda de alguna clase padre proveniente de Postsharp, la cual le da los "<i>poderes</i>" de aspecto, por decirlo de alguna manera. En realidad, no son poderes, sino son clases que le permiten redefinir <i>métodos </i>(<a href="https://msdn.microsoft.com/es-es/library/ebca9ah3%28v=vs.120%29.aspx" target="_blank">override</a>) que ya tienen y que luego el <i>core </i>de Postsharp va ubicar en nuestro código. (Para nuestro ejemplo, lo ubicaría justo antes del llamado a "<i>Main()</i>".</li>
</ol>
<br />
Creemos una nueva clase, que se llame AspectoQueInterceptaUnMetodo y dice así:<br />
<br />
<pre class="brush:csharp">using PostSharp.Aspects;
using System;
namespace PruebaPostSharp
{
[Serializable]
class AspectoQueInterceptaUnMetodo : OnMethodBoundaryAspect
{
public override void OnEntry(MethodExecutionArgs args)
{
Console.WriteLine("Entrado al método: " + args.Method.Name);
}
}
}
</pre>
<br />
Brevemente quiero dar por sentado que ya sabemos donde esta indicado que la clase es serializable, y que el método hereda de la clase <a href="http://doc.postsharp.net/t_postsharp_aspects_onmethodboundaryaspect" target="_blank">OnMethodBoundaryAspect</a>, la cual le provee la posibilidad de reescribir algunos métodos. En este caso, elegimos el método OnEntry() que indica que lo que haga ese método se hará justo antes de ejecutar los métodos a los que se les aplica ese aspecto. Pero también hay otros: OnExit(), OnSuccess() y OnException(). Veamoslo en acción.<br />
<br />
Otro detalle, es que en args.Method.Name tenemos almacenado el nombre del método que fue interceptado antes de llamar al aspecto. Esto, y mucho más (como acceso a los parametros, y los valores de los parámetros) son poderosas herramientas con las que podemos contar.<br />
<br />
<h3>
4) Indicar qué metodos serán afectados por el aspecto.</h3>
Hasta aquí tenemos un aspecto hecho y derecho. Pero está aislado: es decir, nadie lo usa. Para usarlo, solo debemos definirlo como atributo de el/los métodos a los que queremos que afecte.<br />
<br />
De la siguiente manera:<br />
<br />
<pre class="brush:csharp"> [AspectoQueInterceptaUnMetodo]
static void Main(string[] args)
{
</pre>
<br />
<br />
Si ahora ejecutamos el proyecto, vemos que se muestra primero el texto del aspecto, y luego el del método Main. Esto es porque el aspecto siempre se ejecuta antes que el método. Si quisieramos que se ejecute después, deberíamos sobre escribir el método <i>OnExit</i>(). Si queremos que se ejecute antes y después, deberíamos sobreescribir <u>ambos </u>métodos: <i>OnEntry</i>() y <i>OnExit</i>(), y asi de simple funciona esto.<br />
<br />
Otro tip interesante, es que si queremos que el aspecto se aplique a TODOS los métodos de una clase, solo basta con colocar el atriuto sobre la clase en cuestión:<br />
<br />
<pre class="brush:csharp"> [AspectoQueInterceptaUnMetodo]
class Program
{
static void Main(string[] args)
{</pre>
<br />
<br />
Y de esta forma, cualquier método automáticamente será víctima de nuestro aspecto!! Simple, no?<br />
<br />
Te invito a que pruebes vos mismo!<br />
<br />
Esto fue una sencilla introducción a un aspecto muy simple. El mundo de aspectos es mucho más grande e interesante! Se pueden hacer cosas realmente estupendas pero, sin embargo, como todo en el software, siempre hay que analizar las ventajas y soluciones reales que esta (o cualquier otra) práctica puede conllevar. No siempre se necesitan aspectos.<br />
<br />
En mi experiencia, el uso de aspectos es una tendencia que está creciendo, porque mucha gente está animándose a desarrollar con aspectos cada vez más gracias a los frameworks como postsharp, Spring.NET, etc Y si bien aún no es tan común, y aún no se lo práctica de la mejor forma, esto está cambiando de forma veloz, como todo en este campo.<br />
<br />
Próximamente iremos mostrando cosas nuevas, más interesantes y más avanzadas!<br />
<br />
Saludos!nachokhanhttp://www.blogger.com/profile/00777464846297951418noreply@blogger.com1tag:blogger.com,1999:blog-2715874640743883267.post-62687479394864579682015-06-05T09:16:00.000-07:002015-06-05T09:16:31.755-07:00Web Service que recibe y devuelve JSON La verdad es que si <i>JSON </i>se utiliza tanto hoy en día, decididamente su simplicidad de uso es uno de los factores más decisivos para esta tendencia.<br />
<br />
Actualmente estoy haciendo unos Web Services en C# que deben ser consultados por un cliente enviando como request una cadena en formato <i>JSON</i>. Y no solo eso, el cliente también espera que el WS le devuelva su respuesta.. también en formato <i>JSON</i>.<br />
<br />
Esto es, tremendamente simple de lograr. Para mostrar, voy a crear un proyecto muy simple. No será un WS porque no amerita la complicación. Quiero decir que lo que voy a hacer aquí se aplica a un WS o a un proyecto cualquiera... de hecho, voy a hacerlo para un método aislado.<br />
<br />
Vamos a hacer una calculadora. Pero para darle un poco de "complejidad", vamos a hacer la calculadora reciba una lista de pares de números. Se tomarán los números de cada uno de estos pares y se los sumará. De forma que se devolverá una lista de "sumas". Por ejemplo:<br />
<br />
Una lista para enviarle a la calculadora podría ser:<br />
listaEntrada = [5,2],[1,-3],[-2,-2]<br />
y la correspondiente lista de respuesta:<br />
listaRepsuesta = 7,-2,-4<br />
<br />
¿Estamos de acuerdo? Bien. Sigamos.<br />
<br />
Si el Web Service va a recibir la lista de pares en formato JSON, debemos definir exactamente cómo.Podemos imaginar algo así:<br />
<br />
<pre class="brush:csharp">{
"pares":
[
{
"numero1":"5",
"numero2":"2"
},
{
"numero1":"1",
"numero2":"-3"
},
{
"numero1":"-2",
"numero2":"-2"
}
]
}
</pre>
<br />
<br />
y por lo tanto, la siguiente respuesta:<br />
<br />
<pre class="brush:csharp">{
"sumas":[
7,
-2,
-4
]
}
</pre>
<br />
<br />
Entonces, considerando esta decisión, vamos a armar dos entidades: una entidad para tener los datos de entrada, y otra entidad para poner los datos de salida. Las mismas podrían llamarse <i>sumaRequest </i>y <i>sumaResponse</i>, por ejemplo.<br />
<br />
Escribamos estas entidades entonces, pero además agregamos una nueva: "Pares". Esta entidad, es el "tipo de dato" (por llamarlo de alguna manera) que contiene dos enteros, los cuales vamos a sumar.<br />
<br />
<pre class="brush:csharp">public class sumaRequest
{
public List<pares> pares { get; set; }
}
public class Pares
{
public int numero1 { get; set; }
public int numero2 { get; set; }
}
public class sumaResponse
{
public List<int> sumas { get; set; }
}
</pre>
<br />
<br />
Bueno, creando objetos de estas entidades y llenándolos con la información necesaria, ya podríamos trabajar perfectamente.
LA operación suma, recibe la lista, y simplemente la procesa. Es decir, esto:<br />
<br />
<pre class="brush:csharp">sumaResponse hacerTodasLasSumas(sumaRequest request)
{
sumaResponse ret = new sumaResponse();
ret.sumas = new List<int>();
foreach(Pares pareja in request.pares)
{
int suma = pareja.numero1 + pareja.numero2;
ret.sumas.Add(suma);
}
return ret;
}
</pre>
<br />
Bueno, esto es muy lindo, pero aún no hemos metido a <i>JSON </i>en todo esto. Pero en realidad, es muy poco lo de tenemos que hacer. Solo hay que desserializar <i>sumaRequest </i>de un string que nos llegue, por otra parte serializar <i>sumaResponse </i>antes de devolverlo al usuario/cliente.<br />
<br />
Para esto vamos a agregar el paquete JSON.NET desde Nuget (o la línea de comandos, como quieran). Una vez agregado, modificamos las entidades que directa o indirectamente entran en el JSON.<br />
<blockquote class="tr_bq">
La diferencia entre entrar directamente o indirectamente, es simple: sumaRequest entra directamente, porque es la entidad que contiene la información del JSON de forma directa. Lo mismo ocurre con sumaResponse, que contiene la información directa que queremos almacenar. Sin embargo la entidad Pares entra de manera indirecta, ya que en realidad, debemos utilizar su contenido dentro del JSON, pero sólo por ser parte de la entidad sumaRequest.</blockquote>
Las entidades, las modificamos de la siguiente manera:<br />
<br />
<pre class="brush:csharp">[JsonObject(MemberSerialization.OptIn)]
public class sumaRequest
{
[JsonProperty(PropertyName = "pares")]
public List<Pares> pares { get; set; }
}
[JsonObject(MemberSerialization.OptIn)]
public class Pares
{
[JsonProperty(PropertyName = "numero1")]
public int numero1 { get; set; }
[JsonProperty(PropertyName = "numero2")]
public int numero2 { get; set; }
}
[JsonObject(MemberSerialization.OptIn)]
public class sumaResponse
{
[JsonProperty(PropertyName = "sumas")]
public List<int> sumas { get; set; }
}
</pre>
<br />
<br />
Lo que hemos hecho con el atributo <b><i>JsonObject</i></b>, es indicar que una clase forma parte de una Serializacion/Deserialización JSON (sin importar si es de forma directa o indirecta). Y con el atributo <b><i>JsonProperty </i></b>indicamos que el campo en cuestión debe ser serializado. Este atributo además permite especificar cómo se llama el campo cuando la información ser serializa/deserializa a JSON. Claramente, ambos nombres no tienen porqué coincidir.<br />
<br />
Finalmente, y para terminar, debemos indicar en algun punto de código, que una string recibida debe deserializarse como entradaRequest, y que sumaResponse debe serializarse como salida. Esto lo hacemos con las funciones <b><i>JsonConvert.DeserializeObject </i></b>e <b><i>JsonConvert.SerializeObject </i></b>respectivamente. Nos queda así:<br />
<br />
<pre class="brush:csharp">public string operacionDelWebService (string jsonRequest)
{
sumaRequest objectRequest = JsonConvert.DeserializeObject<sumaRequest>(jsonRequest); // (1)
sumaResponse objectResponse = hacerTodasLasSumas(objectRequest); // (2)
string response = JsonConvert.SerializeObject(objectResponse); // (3)
return response;
}
</pre>
<br />
<br />
Como vemos, la funcion -que podría ser la operación visible del WS- recibe y devuelve una cadena, ambas en formato JSON.<br />
<br />
En (1) convertimos la cadena en formato JSON a objetos conocidos por nosotros (Deserializamos).<br />
En (2) operamos normalmente con los objetos que conocemos.<br />
En (3) convertimos la respuesta a un string con formato JSON. (Serializamos).<br />
<br />
Luego se devuelve el string y todos contentos.<br />
Espero que haya sido de utilidad y simple de entender.<br />
<br />
Dejo el código completo funcional.<br />
<br />
<br />
<pre class="brush:csharp">
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
namespace Borrar02
{
class Program
{
static void Main(string[] args)
{
string stringEntrada = " {\"pares\":[{\"numero1\":\"5\",\"numero2\":\"2\"},{\"numero1\":\"1\",\"numero2\":\"-3\"},{\"numero1\":\"-2\",\"numero2\":\"-2\"}]}";
string stringSalida;
WebService ws = new WebService();
stringSalida = ws.operacionDelWebService(stringEntrada);
Console.WriteLine("Respuesta del WS:\n{0}", stringSalida);
}
}
class WebService
{
public string operacionDelWebService(string jsonRequest)
{
sumaRequest objectRequest = JsonConvert.DeserializeObject<sumaRequest>(jsonRequest);
sumaResponse objectResponse = hacerTodasLasSumas(objectRequest); // (2)
string response = JsonConvert.SerializeObject(objectResponse); // (3)
return response;
}
sumaResponse hacerTodasLasSumas(sumaRequest request)
{
sumaResponse ret = new sumaResponse();
ret.sumas = new List<int>();
foreach (Pares pareja in request.pares)
{
int suma = pareja.numero1 + pareja.numero2;
ret.sumas.Add(suma);
}
return ret;
}
[JsonObject(MemberSerialization.OptIn)]
public class sumaRequest
{
[JsonProperty(PropertyName = "pares")]
public List<Pares> pares { get; set; }
}
[JsonObject(MemberSerialization.OptIn)]
public class Pares
{
[JsonProperty(PropertyName = "numero1")]
public int numero1 { get; set; }
[JsonProperty(PropertyName = "numero2")]
public int numero2 { get; set; }
}
[JsonObject(MemberSerialization.OptIn)]
public class sumaResponse
{
[JsonProperty(PropertyName = "sumas")]
public List<int> sumas { get; set; }
}
}
}
</pre>nachokhanhttp://www.blogger.com/profile/00777464846297951418noreply@blogger.com0tag:blogger.com,1999:blog-2715874640743883267.post-33646668190710976162015-05-27T06:50:00.003-07:002015-05-27T06:50:48.716-07:00Registrar un Plug-in en Microsoft Dynamics CRM 2015Ahora si, llegó el momento prometido. Hemos hecho plugins, hemos llamado a webservices desde los plugins... pero ¡no hemos dicho como registrarlos para poder usarlos! Bueno, ahora mostramos cómo (es muy fácil!)<br />
<br />
Igual, quiero aclarar que no pretendo dar información exhaustiva, sino solo la necesaria para probar los plugins que escribimos, y por lo tanto las indicaciones que voy a dar son muy básicas. Para más detalles y profundizar en el registro de plugins en el CRM, favor de remitirse a <a href="https://msdn.microsoft.com/en-us/library/hh237515(v=crm.6).aspx#bkmk_RegImage" rel="nofollow" target="_blank">documentación</a> más precisa.<br />
<br />
Lo primero es descargar el SDK del Dynamics, esto se consigue <a href="https://www.microsoft.com/en-us/download/details.aspx?id=44567" rel="nofollow" target="_blank">acá</a>. Después de descomprimirlo, vemos que hay una carpeta<b> /tools</b> y allí dentro, una carpeta llamada <b>/PluginRegistration</b>. Allí es dónde está la herramienta que se usa para registrar los plugins.<br />
<br />
La abrimos, y vemos que necesitamos conectarnos al Dynamics. Creamos la conexión y entramos. Si, así de simple. Una vez dentro, vemos una pantalla igual -o similar- a ésta:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi0_qjQSYSzM_EP8QKED3hjcjg7zJrj2VGzhfxGxKYlT3nn1CRPotRGqYXGOiBlhjqNm-JdhChIoufY22tL1FzYMFwJoaVUBDpfDPVskU9KPATP0w7CQq8ewD9ctT81q0EIb6dhwRzr678/s1600/A_PLUG_01.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="282" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi0_qjQSYSzM_EP8QKED3hjcjg7zJrj2VGzhfxGxKYlT3nn1CRPotRGqYXGOiBlhjqNm-JdhChIoufY22tL1FzYMFwJoaVUBDpfDPVskU9KPATP0w7CQq8ewD9ctT81q0EIb6dhwRzr678/s400/A_PLUG_01.jpg" width="400" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<br />
Daremos click a "<b>Register</b>" y luego "<b>Register New Assembly</b>":<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjJWBtsXD7evMaoSVR3e5a8DQagpf7NrTalfVXKeHDcPXocNoWvbp_wjQnrk8J095jYX2qYrw4anz990NnQqoNvr1RWPMUvljo_BmGtqmHBQ9uPWnFsDF24Zt-2G3q521eh-6RjcZSdCxw/s1600/A_PLUG_02.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="165" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjJWBtsXD7evMaoSVR3e5a8DQagpf7NrTalfVXKeHDcPXocNoWvbp_wjQnrk8J095jYX2qYrw4anz990NnQqoNvr1RWPMUvljo_BmGtqmHBQ9uPWnFsDF24Zt-2G3q521eh-6RjcZSdCxw/s320/A_PLUG_02.jpg" width="320" /></a></div>
<br />
<br />
<br />
Luego nos aparece una ventana para elegir el ensamblado que queremos agregar. Obviamente, el ensamblado es el de nuestro plugin, el cual ha compilado perfectamente. Seleccionamos la DLL correspondiente a nuestro plugin. Para buscar la DLL, le damos click a los 3 puntitos:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjsJ_R7YW7nGTkS8rBofs-1pNGLS9sv2Jp8f05uogy30yDKy8z9dTOdIrqf4pjVjHcNqCxXLND3ofRlFiZ-F6OAn1VeexMHmBRecAezb5WiW8Y1tufMxaBodEYMI9xQfIjfcPC_kmAN1VM/s1600/A_PLUG_03.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjsJ_R7YW7nGTkS8rBofs-1pNGLS9sv2Jp8f05uogy30yDKy8z9dTOdIrqf4pjVjHcNqCxXLND3ofRlFiZ-F6OAn1VeexMHmBRecAezb5WiW8Y1tufMxaBodEYMI9xQfIjfcPC_kmAN1VM/s320/A_PLUG_03.jpg" width="250" /></a></div>
<br />
Una vez seleccionado el ensamblado, le damos click a "<b>Register Selected Plugin</b>", y si todo sale bien, aparece un cuadro de diálogo informándonos de dicha situación. Además, el plugin figura ahora en la lista de los plugins y workflows registrados.<br />
<br />
Con ésto ya tenemos registrado el plugin, es decir, ya está "adentro" del CRM. Pero no hemos dicho <i>cuándo </i>queremos que se ejecute, por lo que ahora solo nos resta registrar un <b>paso</b>. Desplegamos el elemento de nuestro plugin y hacemos click derecho y "<b>Register New Step</b>":<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh4H6sooDATgcqhDpH3JA883bQNtKLCMmut5MrTrhwqs51du6FFoSoPh0lDFWW8zoHXPp31_75ajDft-HMKe4tPU8XkzsFYXSAAwI5n7pWn6h2AOpn_VY3OGnRuaA1wTUq3iVE-LJmuivk/s1600/A_PLUG_04.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="168" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh4H6sooDATgcqhDpH3JA883bQNtKLCMmut5MrTrhwqs51du6FFoSoPh0lDFWW8zoHXPp31_75ajDft-HMKe4tPU8XkzsFYXSAAwI5n7pWn6h2AOpn_VY3OGnRuaA1wTUq3iVE-LJmuivk/s320/A_PLUG_04.jpg" width="320" /></a></div>
Aparecerá una ventana así, o similar:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhjbEl9vUpkIwRodaC3tL-gGGbpUCUQrZzqRt9W4W7jYg_2AIJxr3NO1sVo2rAre6xIj8-OFBOliq5_5YR5aInAYbA_Yv7Ap1OXspZix04y1HMdgrhKam4YAhJYGCq7TvHeOubSKVYppY8/s1600/A_PLUG_05.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhjbEl9vUpkIwRodaC3tL-gGGbpUCUQrZzqRt9W4W7jYg_2AIJxr3NO1sVo2rAre6xIj8-OFBOliq5_5YR5aInAYbA_Yv7Ap1OXspZix04y1HMdgrhKam4YAhJYGCq7TvHeOubSKVYppY8/s320/A_PLUG_05.jpg" width="320" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
Aca detengámosnos un poco. El campo <i><b>Message</b> </i>indica el "evento" sobre el cual queremos actuar. En este caso, yo seleccioné el evento de <i>creación </i>de una nueva entidad. Otra opción podría ser "Update", "Delete", etc.<br />
El campo <b><i>Primary Entity</i></b> permite especificar la entidad sobre la que vamos a atender el evento de creación. Yo seleccioné "<i>account</i>" por ser una que viene por default. Y finalmente, indiqué que el plug-in se ejecute previamente a la validación (<i><b>pre-validation</b></i>) de los datos ingresados. Esto lo hice para evitar que me almacene siempre una nueva <i>account</i>, ya que yo solo quiero probar si funciona el plugin, no andar registrando una nueva cuenta cada vez. Aquí por supuesto elijan la opción más conveniente para su caso.<br />
<br />
Finalmente, le damos a "Register New Step" y se registrará el paso. Con esto, ya podemos ir al CRM, y probar de crear una nueva cuenta y ver si el plugin se ejecuta, o no.<br />
<br />
<blockquote class="tr_bq">
<i><u>Un tip</u>: el campo "Run in User´s Context" indica bajo cuál usuario el plugin se ejecutará. Con la opción "Calling User", con cualquier usuario que intente registrar una cuenta se ejecutará el plugin.</i></blockquote>
<br />
Espero que haya sido útil, y bienvenidos los buenos comentarios y sin spam! ;)<br />
<br />
Saludos!nachokhanhttp://www.blogger.com/profile/00777464846297951418noreply@blogger.com4tag:blogger.com,1999:blog-2715874640743883267.post-55194107109679608102015-05-26T13:18:00.002-07:002015-05-28T09:00:11.634-07:00Llamar a un Web Service desde un Plug-in de MS DynamicsEn este post vamos a ver como se hace para que un plug-in que hemos realizado nosotros mismos realice una llamada a un web service (que puede o no ser nuestro). Como siempre, estoy utilizando la versión de Dynamics 2015.<br />
En este caso vamos a utilizar un web service que se pueda encontrar por Internet. Se trata de un conversor de unidades que encontré en la siguiente página:<br />
<br />
<a href="http://www.webservicex.net/">http://www.webservicex.net/</a><br />
<br />
El servicio en cuestión está en <a href="http://www.webservicex.net/ws/WSDetails.aspx?CATID=2&WSID=10">http://www.webservicex.net/ws/WSDetails.aspx?CATID=2&WSID=10</a>, aunque si por algún motivo no se encuentra, no tienen más que buscar el servicio "Currency Convertor" desde la página principal. Ahora, si la página principal no funciona más, mala surte. Eso ya es algo que me trasciende.<br />
<br />
<h3>
Primero: Agregar la referencia al servicio.</h3>
Bien, primero lo primero: agregamos la referencia al servicio web haciendo click con el botón izquierdo sobre la carpeta "<i>References</i>" del proyecto, y después en "<i>Add Service Reference</i>".<br />
Cuando se abre la vetana, vemos un cuadro de texto que dice "Adress". Bueno, allí vamos a copiar el WSDL del servicio.<br />
<br />
<blockquote class="tr_bq">
El WSDL es un archivo XML que contiene el contrato del Web Service: <b>qué hace</b>, y <b>con qué lo hace</b>. Así podemos saber qué métodos tiene para ofrecernos, y qué parámetros requieren esos métodos. Ah, y también -lógicamente- que datos devuelven!</blockquote>
Para nosotros, esta URL es la siguiente:<br />
<br />
<span style="background-color: white; font-family: Verdana; font-size: x-small;"><a href="http://www.webservicex.net/CurrencyConvertor.asmx?WSDL">http://www.webservicex.net/CurrencyConvertor.asmx?WSDL</a></span><br />
<br />
Bien, una vez que ponemos la URL del WSDL del servicio que vamos a usar, le damos a "Go". Aparecerá una lista con los servicios que podemos utilizar. Abajo de la lista, hay una cuadro que dice "Namespace". Allí pondremos el nombre del <i>namespace </i>que "<i>contedrá</i>" el servicio después. Para nuestro caso, podría ser, por ejemplo: "WS_Curr" (que como habéis adivinado viene de "Web Service CURRency" ;)<br />
Le damos aceptar.<br />
Con esto, ya podemos usar los métodos y tipos que provee el web service sin que Visual Studio se enoje y lo subraye con rojo. La variable channel es la que está representando la instancia de nuestro web service.<br />
<br />
<h3>
Segundo: Instanciar el Web Service</h3>
Segundo lo segundo: creamos una factory utlizando un Endpoint y un Binding para crear canales de comunicación con el web service. En realidad, nosotros solo vamos a utilizar un canal.<br />
<br />
<pre class="brush:csharp">BasicHttpBinding myBinding = new BasicHttpBinding();
myBinding.Name = "CurrencyConvertorSoap";
myBinding.Security.Mode = BasicHttpSecurityMode.None;
myBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
myBinding.Security.Transport.ProxyCredentialType = HttpProxyCredentialType.None;
myBinding.Security.Message.ClientCredentialType = BasicHttpMessageCredentialType.UserName;
EndpointAddress endPointAddress = new EndpointAddress("http://www.webservicex.net/CurrencyConvertor.asmx?WSDL");
ChannelFactory<currencyconvertorsoap> factory = new ChannelFactory<currencyconvertorsoap>(myBinding, endPointAddress);
CurrencyConvertorSoap channel = factory.CreateChannel();
</currencyconvertorsoap></currencyconvertorsoap></pre>
<br />
<h3>
Tercero (y último): Utilizar el servicio.</h3>
Esta es la parte más sencilla de explicar para mi, y entender para ustedes -o para mí si me olvido :)-<br />
Para usarlo, se usa como se usaría cualquier método que está en otro namespace. Si queremos ver a cuantos pesos argentinos está el euro, no tenemos más que llamar al método adecuado, con los parámetros adecuados. Y en la segunda y última linea, mostramos una excepción desde el plug-in para ver si esto funcionó o no. He aquí:<br />
<br />
<pre class="brush:csharp">double lalala = channel.ConversionRate(Currency.EUR, Currency.ARS);
throw new InvalidPluginExecutionException("No se si te interesa, pero el EURO está a: " + lalala.ToString());
</pre>
<br />
<br />
Al ejecutarse este plug-in en MS Dynamics, deberíamos ver una excepción que mostrara el mensaje con el preciod el euro.<br />
En definitiva, llamar a un web service desde un plug-in en Dynamics es algo extremadamente simple, como pueden ver. A continuación dejo el código completo del ejemplo, y sigue la promesa de en un próximo post mostrar como registrar un plug-in en Ms Dynamics.<br />
<br />
<pre class="brush:csharp">using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.Text;
using System.Threading.Tasks;
namespace Plugin_Prueba
{
public class Class1 : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
BasicHttpBinding myBinding = new BasicHttpBinding();
myBinding.Name = "CurrencyConvertorSoap";
myBinding.Security.Mode = BasicHttpSecurityMode.None;
myBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
myBinding.Security.Transport.ProxyCredentialType = HttpProxyCredentialType.None;
myBinding.Security.Message.ClientCredentialType = BasicHttpMessageCredentialType.UserName;
EndpointAddress endPointAddress = new EndpointAddress("http://www.webservicex.net/CurrencyConvertor.asmx?WSDL");
ChannelFactory<currencyconvertorsoap> factory = new ChannelFactory<currencyconvertorsoap>(myBinding, endPointAddress);
CurrencyConvertorSoap channel = factory.CreateChannel();
double lala = channel.ConversionRate(Currency.EUR, Currency.ARS);
throw new InvalidPluginExecutionException("No se si te interesa, pero el EURO está a: " + lala.ToString());
}
}
}
</pre>
<br />
<br />
Saludos y bienvenidos los comentarios con buena onda -y sin spam!-<br />nachokhanhttp://www.blogger.com/profile/00777464846297951418noreply@blogger.com0tag:blogger.com,1999:blog-2715874640743883267.post-91075159545411854072015-05-22T06:38:00.001-07:002015-05-28T07:03:53.601-07:00Escribir un Plug-In para MS Dynamics CMR 2015Esta vez vamos a escribir un poco de código. Hoy quiero mostrar cómo escribir un plug-in para ejecutar desde Microsoft Dynamics. Si bien yo estoy utilizando la version 2015, sirve para algunas anteriores, aunque desconozco exactamente hasta donde se aplica. Próximamente, en otra entrada del blog, voy a mostrar como registrar el plugin en MS Dynamics y asociarlo a algún evento particular.<br />
<br />
Bien, la verdad es que crear un plug-in, en términos básicos, es muy, pero muy simple! De hecho vamos a comenzar con lo primero: agregar las referencias necesarias.<br />
<br />
<h3>
1. Primero lo primero.</h3>
<div>
Obviamente, comenzamos creando un proyecto. Este proyecto será simplemente una librería de clases (Class Library).<br />
<br /></div>
<h3>
2. Agregar las referencias necesarias.</h3>
<div>
Lo segundo que tenemos que hacer es agregar las siguientes referencias:</div>
<br />
<ol>
<li>System.Runtime.Serialization</li>
<li>System.ServiceModel</li>
<li>Microsoft.Xrm.Sdk.dll</li>
<li>Microsoft.Xrm.Client.dll</li>
<li>Microsoft.Crm.Sdk.Proxy.dll</li>
</ol>
<br />
Las dos primeras se encuentran dentro de los ensamblados del framework. Las tres restantes debemos buscarlas dentro de la carpeta <b>/bin</b> de la carpeta descargada del "Dynamics CRM SDK".<br />
<br />
<h3>
3. Comenzar a escribir el plug-in.</h3>
Lo primero que vamos a tener en cuenta, es que la clase del plug-in debe implementar la interfaz <i>IPlugin</i>, el cual nos va a obligar a implementar el método <i>Execute</i>, cuyo código será ejecutado por el motor de ejecución de plug-ins dentro del CRM. En definitiva, tendremos esto:
<br />
<br />
<pre class="brush:csharp">public class Class1 : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
}
}
</pre>
Dentro de <i>Execute </i>escribimos nuestro plug-in. Por supuesto, podemos valernos de otras clases y métodos para implementar lo que querramos.
<br />
<br />
<h3>
4. Firmar el Plugin</h3>
Este paso es escencial, de lo contrario ¡no podremos registrar el Plugin! Igualmente es fácil: Vamos a las propiedades del proyecto (Click izquierdo sobre el nombre del proyecto, y después en <b>properties</b>), y alli buscamos la pestaña "<b>Signing</b>". Checamos "<b>Sign the assembly</b>" y en "<b>Choose a strong name key file</b>" seleccionamos "<b>New</b>". Allí penemos un nombre cualquiera, pero <i style="font-weight: bold;">SIN contraseña. </i>Y listo, ya podemos compilar y <a href="http://includeblogh.blogspot.com.ar/2015/05/registrar-un-plug-in-en-microsoft.html" target="_blank">registrar </a>el plugin.<br />
<br />
<h3>
Trazas para la ejecución.</h3>
Bastante común es el caso de utilizar traces para hacer una traza de la ejecución. ¿Y ésto por qué? Porque un plug-in, como podréis imaginaros, es bastante difícil de debuggear. De hecho, la única forma es ejecutando es plug-in desde el CRM y mirando luego la traza de la ejecución. Para esto, creamos el servicio de traza con lo siguiente:<br />
<br />
<pre class="brush:csharp">ITracingService tracingService;
tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
</pre>
y luego ir escribiendo algo que nos permita seguir la traza. Esto lo haremos llamando a:
<br />
<pre class="brush:csharp">tracingService.Trace("Paso 1");
</pre>
Si intercalamos llamadas a tracingService.Trace("Paso X") dentro de nuestro código, podremos saber hasta dónde se ejecutó el plug-in. Si, por ejemplo falla, veremos que la traza nos muestras el lugar al que llegó antes de lazar la excepción. Acá muestro un ejemplo de traza:
<br />
<pre class="brush:csharp">using Microsoft.Xrm.Sdk;
using System;
namespace Plugin_Prueba
{
public class Class1 : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
ITracingService tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
tracingService.Trace("Paso 1");
Entity entidad = (Entity)context.InputParameters["Target"];
tracingService.Trace("Paso 2");
if (entidad.Attributes.Contains("name") && entidad.Attributes["name"].ToString().Length > 5)
{
tracingService.Trace("Paso 3");
throw new InvalidPluginExecutionException("Epa! El nombre tiene que tener más de 5 caracteres.");
}
tracingService.Trace("Paso 4");
}
}
}
</pre>
Entonces, si durante la evaluación de la primera condición del if lanza una excepeción (si por ejemplo, el atributo "name" no existiera), se lanzará una excepción en el CRM y tendremos la opción de ver la traza. En la misma, figurara algo parecido a esto:
<br />
<blockquote>
Paso 1
<br />
Paso 2
</blockquote>
Pero no veremos "Paso 3" porque se lanzó la excepción y no se llegó a ejecutar esa parte. En la próxima entrada, veremos<a href="http://includeblogh.blogspot.com.ar/2015/05/registrar-un-plug-in-en-microsoft.html" target="_blank"> como registrar el plug-in</a> para que el CRM lo pueda ejecutar. Es también algo muy sencillo.
<br />
<br />
Saludos!nachokhanhttp://www.blogger.com/profile/00777464846297951418noreply@blogger.com0tag:blogger.com,1999:blog-2715874640743883267.post-81019182090320021952014-12-31T10:40:00.000-08:002015-06-09T14:45:13.465-07:00¿Cómo testear métodos privados?<!--[if gte mso 9]><xml>
<o:OfficeDocumentSettings>
<o:AllowPNG/>
</o:OfficeDocumentSettings>
</xml><![endif]-->
<span lang="ES-AR" style="mso-ansi-language: ES-AR;">A la hora de hacer los unit
testing, tarde o temprano nos llega indefectiblemente la pregunta:<i> ¿cómo
testeo un método privado?</i></span><br />
<br />
<span lang="ES-AR" style="mso-ansi-language: ES-AR;">El asunto en cuestión es
motivo de distintos puntos de vista, opiniones, y discusiones. Sin embargo
basado un poco en la experiencia personal y la ajena, más un poco de
literatura, ofrezco mi punto de vista.</span><br />
<br />
<span lang="ES-AR" style="mso-ansi-language: ES-AR;">Existen dos opiniones
diferentes al respecto: unos dicen que los métodos privados no deben testearse,
y otros que si. Ah, y bueno, están los grises que dicen que "depende de la
situación". Yo me incluyo en este último grupo. La ingeniería de software
es una ciencia muy "gris" todavía.</span><br />
<br />
<span lang="ES-AR" style="mso-ansi-language: ES-AR;">La cruda realidad, es que
con muchos de los frameworks no podemos testear métodos privados, por la simple
razón de que son privados y no podemos acceder a ellos. (No lo dije antes, pero
doy por hecho que los tests se encuentran en un proyecto aparte de aquel que
contiene el código que estamos testeando y por eso no podemos accederlos).</span><br />
<br />
<span lang="ES-AR" style="mso-ansi-language: ES-AR;">Los que dicen que no deben
testearse métodos privados, tienen sus razones. Principalmente sostienen que
solo debe testearse la interfaz de, pongámosle, una clase. Es decir, todos
aquellos métodos públicos que sirven de interfaz al mundo exterior (los
programadores que utilizan estos métodos). Y que a su vez, los métodos privados
son solo "ayudantes" de los métodos públicos. Estos pueden cambiar
con mayor frecuencia que los métodos públicos, y por tanto, una eventual
modificación de los métodos privados que no debiera alterar los resultados de
los métodos públicos, podría hacer fallar los tests que testean los métodos
privados. Es decir que se cambió el comportamiento interno sin alterar el
comportamiento externo y que, por lo tanto, solo debemos preocuparnos por
testear el comportamiento externo.</span><br />
<br />
<span lang="ES-AR" style="mso-ansi-language: ES-AR;">Yo no estoy precisamente de
acuerdo con este punto de vista. Si queremos testear el método, deberíamos
poder. La realidad es que así nomás no se puede. Entonces salen alguna
alternativas que voy a mencionar, y me alegraría siempre de escuchar o leer
nuevas.</span><br />
<br />
<b><span lang="ES-AR" style="mso-ansi-language: ES-AR;">1) Mover los métodos
privados a una nueva clase.</span></b><span lang="ES-AR" style="mso-ansi-language: ES-AR;"></span><br />
<span lang="ES-AR" style="mso-ansi-language: ES-AR;">Si el/los método/s <i>privados
</i>que queremos testear lo ameritan, los refactorizamos creando una -o más-
nueva clase que solo los contenga a ellos -pero como métodos <i>públicos</i>- y
<u>no formen parte de la interfaz</u> de nuestra clase. De esta forma seguirán
estando "ocultos" hacia el mundo exterior (porque no pertenecen a la
interfaz), y sin embargo ya podemos accederlos tranquilamente desde el proyecto
de testing. </span><br />
<br />
<b><span lang="ES-AR" style="mso-ansi-language: ES-AR;">2) Convertir los
métodos <i>privados </i>en <i>públicos</i>.</span></b><span lang="ES-AR" style="mso-ansi-language: ES-AR;"></span><br />
<span lang="ES-AR" style="mso-ansi-language: ES-AR;">No soy un gran entusiasta
de esta opción. Creo que si un método se pensó <i>privado</i>, <i>privado </i>debe
permanecer. Si se cambia a <i>público</i>, la razón debería tener que ver con
otras cuestiones y no con el testing. Pero ya dicho, es una opción más.</span><br />
<br />
<b><span lang="ES-AR" style="mso-ansi-language: ES-AR;">3) Convertir los métodos
en <i>internal</i>.</span></b><span lang="ES-AR" style="mso-ansi-language: ES-AR;"></span><br />
<span lang="ES-AR" style="mso-ansi-language: ES-AR;">Desconozco si existe en
otros lenguajes el equivalente a <i>internal </i>de C#. Con esto permitimos que
las clases dentro del mismo ensamblado se puedan ver. Sin embargo, fuera del
ensamblado siguen permaneciendo ocultas. Y debemos especificar a qué ensamblado
le damos visibilidad (con [assembly:InternalsVisibleTo("MyTests")]).</span><br />
<br />
<span lang="ES-AR" style="mso-ansi-language: ES-AR;">Cuando puede utilizarse,
soy partidario de la tercera forma, ya que es bastante más limpia. En otro
caso, habrá que evaluar qué hacer, o incluso si coincidir con quienes dicen que
los métodos privados no tiene estrictamente que testearse. </span><br />
<br />
Hasta pronto!<br />
<!--[if gte mso 9]><xml>
<w:WordDocument>
<w:View>Normal</w:View>
<w:Zoom>0</w:Zoom>
<w:TrackMoves/>
<w:TrackFormatting/>
<w:DoNotShowRevisions/>
<w:DoNotPrintRevisions/>
<w:DoNotShowMarkup/>
<w:DoNotShowComments/>
<w:DoNotShowInsertionsAndDeletions/>
<w:DoNotShowPropertyChanges/>
<w:HyphenationZone>21</w:HyphenationZone>
<w:PunctuationKerning/>
<w:ValidateAgainstSchemas/>
<w:SaveIfXMLInvalid>false</w:SaveIfXMLInvalid>
<w:IgnoreMixedContent>false</w:IgnoreMixedContent>
<w:AlwaysShowPlaceholderText>false</w:AlwaysShowPlaceholderText>
<w:DoNotPromoteQF/>
<w:LidThemeOther>DE</w:LidThemeOther>
<w:LidThemeAsian>X-NONE</w:LidThemeAsian>
<w:LidThemeComplexScript>X-NONE</w:LidThemeComplexScript>
<w:Compatibility>
<w:BreakWrappedTables/>
<w:SnapToGridInCell/>
<w:WrapTextWithPunct/>
<w:UseAsianBreakRules/>
<w:DontGrowAutofit/>
<w:SplitPgBreakAndParaMark/>
<w:EnableOpenTypeKerning/>
<w:DontFlipMirrorIndents/>
<w:OverrideTableStyleHps/>
</w:Compatibility>
<m:mathPr>
<m:mathFont m:val="Cambria Math"/>
<m:brkBin m:val="before"/>
<m:brkBinSub m:val="--"/>
<m:smallFrac m:val="off"/>
<m:dispDef/>
<m:lMargin m:val="0"/>
<m:rMargin m:val="0"/>
<m:defJc m:val="centerGroup"/>
<m:wrapIndent m:val="1440"/>
<m:intLim m:val="subSup"/>
<m:naryLim m:val="undOvr"/>
</m:mathPr></w:WordDocument>
</xml><![endif]--><!--[if gte mso 9]><xml>
<w:LatentStyles DefLockedState="false" DefUnhideWhenUsed="true"
DefSemiHidden="true" DefQFormat="false" DefPriority="99"
LatentStyleCount="267">
<w:LsdException Locked="false" Priority="0" SemiHidden="false"
UnhideWhenUsed="false" QFormat="true" Name="Normal"/>
<w:LsdException Locked="false" Priority="9" SemiHidden="false"
UnhideWhenUsed="false" QFormat="true" Name="heading 1"/>
<w:LsdException Locked="false" Priority="9" QFormat="true" Name="heading 2"/>
<w:LsdException Locked="false" Priority="9" QFormat="true" Name="heading 3"/>
<w:LsdException Locked="false" Priority="9" QFormat="true" Name="heading 4"/>
<w:LsdException Locked="false" Priority="9" QFormat="true" Name="heading 5"/>
<w:LsdException Locked="false" Priority="9" QFormat="true" Name="heading 6"/>
<w:LsdException Locked="false" Priority="9" QFormat="true" Name="heading 7"/>
<w:LsdException Locked="false" Priority="9" QFormat="true" Name="heading 8"/>
<w:LsdException Locked="false" Priority="9" QFormat="true" Name="heading 9"/>
<w:LsdException Locked="false" Priority="39" Name="toc 1"/>
<w:LsdException Locked="false" Priority="39" Name="toc 2"/>
<w:LsdException Locked="false" Priority="39" Name="toc 3"/>
<w:LsdException Locked="false" Priority="39" Name="toc 4"/>
<w:LsdException Locked="false" Priority="39" Name="toc 5"/>
<w:LsdException Locked="false" Priority="39" Name="toc 6"/>
<w:LsdException Locked="false" Priority="39" Name="toc 7"/>
<w:LsdException Locked="false" Priority="39" Name="toc 8"/>
<w:LsdException Locked="false" Priority="39" Name="toc 9"/>
<w:LsdException Locked="false" Priority="35" QFormat="true" Name="caption"/>
<w:LsdException Locked="false" Priority="10" SemiHidden="false"
UnhideWhenUsed="false" QFormat="true" Name="Title"/>
<w:LsdException Locked="false" Priority="1" Name="Default Paragraph Font"/>
<w:LsdException Locked="false" Priority="11" SemiHidden="false"
UnhideWhenUsed="false" QFormat="true" Name="Subtitle"/>
<w:LsdException Locked="false" Priority="22" SemiHidden="false"
UnhideWhenUsed="false" QFormat="true" Name="Strong"/>
<w:LsdException Locked="false" Priority="20" SemiHidden="false"
UnhideWhenUsed="false" QFormat="true" Name="Emphasis"/>
<w:LsdException Locked="false" Priority="59" SemiHidden="false"
UnhideWhenUsed="false" Name="Table Grid"/>
<w:LsdException Locked="false" UnhideWhenUsed="false" Name="Placeholder Text"/>
<w:LsdException Locked="false" Priority="1" SemiHidden="false"
UnhideWhenUsed="false" QFormat="true" Name="No Spacing"/>
<w:LsdException Locked="false" Priority="60" SemiHidden="false"
UnhideWhenUsed="false" Name="Light Shading"/>
<w:LsdException Locked="false" Priority="61" SemiHidden="false"
UnhideWhenUsed="false" Name="Light List"/>
<w:LsdException Locked="false" Priority="62" SemiHidden="false"
UnhideWhenUsed="false" Name="Light Grid"/>
<w:LsdException Locked="false" Priority="63" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Shading 1"/>
<w:LsdException Locked="false" Priority="64" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Shading 2"/>
<w:LsdException Locked="false" Priority="65" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium List 1"/>
<w:LsdException Locked="false" Priority="66" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium List 2"/>
<w:LsdException Locked="false" Priority="67" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 1"/>
<w:LsdException Locked="false" Priority="68" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 2"/>
<w:LsdException Locked="false" Priority="69" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 3"/>
<w:LsdException Locked="false" Priority="70" SemiHidden="false"
UnhideWhenUsed="false" Name="Dark List"/>
<w:LsdException Locked="false" Priority="71" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful Shading"/>
<w:LsdException Locked="false" Priority="72" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful List"/>
<w:LsdException Locked="false" Priority="73" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful Grid"/>
<w:LsdException Locked="false" Priority="60" SemiHidden="false"
UnhideWhenUsed="false" Name="Light Shading Accent 1"/>
<w:LsdException Locked="false" Priority="61" SemiHidden="false"
UnhideWhenUsed="false" Name="Light List Accent 1"/>
<w:LsdException Locked="false" Priority="62" SemiHidden="false"
UnhideWhenUsed="false" Name="Light Grid Accent 1"/>
<w:LsdException Locked="false" Priority="63" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Shading 1 Accent 1"/>
<w:LsdException Locked="false" Priority="64" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Shading 2 Accent 1"/>
<w:LsdException Locked="false" Priority="65" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium List 1 Accent 1"/>
<w:LsdException Locked="false" UnhideWhenUsed="false" Name="Revision"/>
<w:LsdException Locked="false" Priority="34" SemiHidden="false"
UnhideWhenUsed="false" QFormat="true" Name="List Paragraph"/>
<w:LsdException Locked="false" Priority="29" SemiHidden="false"
UnhideWhenUsed="false" QFormat="true" Name="Quote"/>
<w:LsdException Locked="false" Priority="30" SemiHidden="false"
UnhideWhenUsed="false" QFormat="true" Name="Intense Quote"/>
<w:LsdException Locked="false" Priority="66" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium List 2 Accent 1"/>
<w:LsdException Locked="false" Priority="67" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 1 Accent 1"/>
<w:LsdException Locked="false" Priority="68" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 2 Accent 1"/>
<w:LsdException Locked="false" Priority="69" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 3 Accent 1"/>
<w:LsdException Locked="false" Priority="70" SemiHidden="false"
UnhideWhenUsed="false" Name="Dark List Accent 1"/>
<w:LsdException Locked="false" Priority="71" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful Shading Accent 1"/>
<w:LsdException Locked="false" Priority="72" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful List Accent 1"/>
<w:LsdException Locked="false" Priority="73" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful Grid Accent 1"/>
<w:LsdException Locked="false" Priority="60" SemiHidden="false"
UnhideWhenUsed="false" Name="Light Shading Accent 2"/>
<w:LsdException Locked="false" Priority="61" SemiHidden="false"
UnhideWhenUsed="false" Name="Light List Accent 2"/>
<w:LsdException Locked="false" Priority="62" SemiHidden="false"
UnhideWhenUsed="false" Name="Light Grid Accent 2"/>
<w:LsdException Locked="false" Priority="63" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Shading 1 Accent 2"/>
<w:LsdException Locked="false" Priority="64" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Shading 2 Accent 2"/>
<w:LsdException Locked="false" Priority="65" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium List 1 Accent 2"/>
<w:LsdException Locked="false" Priority="66" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium List 2 Accent 2"/>
<w:LsdException Locked="false" Priority="67" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 1 Accent 2"/>
<w:LsdException Locked="false" Priority="68" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 2 Accent 2"/>
<w:LsdException Locked="false" Priority="69" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 3 Accent 2"/>
<w:LsdException Locked="false" Priority="70" SemiHidden="false"
UnhideWhenUsed="false" Name="Dark List Accent 2"/>
<w:LsdException Locked="false" Priority="71" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful Shading Accent 2"/>
<w:LsdException Locked="false" Priority="72" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful List Accent 2"/>
<w:LsdException Locked="false" Priority="73" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful Grid Accent 2"/>
<w:LsdException Locked="false" Priority="60" SemiHidden="false"
UnhideWhenUsed="false" Name="Light Shading Accent 3"/>
<w:LsdException Locked="false" Priority="61" SemiHidden="false"
UnhideWhenUsed="false" Name="Light List Accent 3"/>
<w:LsdException Locked="false" Priority="62" SemiHidden="false"
UnhideWhenUsed="false" Name="Light Grid Accent 3"/>
<w:LsdException Locked="false" Priority="63" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Shading 1 Accent 3"/>
<w:LsdException Locked="false" Priority="64" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Shading 2 Accent 3"/>
<w:LsdException Locked="false" Priority="65" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium List 1 Accent 3"/>
<w:LsdException Locked="false" Priority="66" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium List 2 Accent 3"/>
<w:LsdException Locked="false" Priority="67" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 1 Accent 3"/>
<w:LsdException Locked="false" Priority="68" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 2 Accent 3"/>
<w:LsdException Locked="false" Priority="69" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 3 Accent 3"/>
<w:LsdException Locked="false" Priority="70" SemiHidden="false"
UnhideWhenUsed="false" Name="Dark List Accent 3"/>
<w:LsdException Locked="false" Priority="71" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful Shading Accent 3"/>
<w:LsdException Locked="false" Priority="72" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful List Accent 3"/>
<w:LsdException Locked="false" Priority="73" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful Grid Accent 3"/>
<w:LsdException Locked="false" Priority="60" SemiHidden="false"
UnhideWhenUsed="false" Name="Light Shading Accent 4"/>
<w:LsdException Locked="false" Priority="61" SemiHidden="false"
UnhideWhenUsed="false" Name="Light List Accent 4"/>
<w:LsdException Locked="false" Priority="62" SemiHidden="false"
UnhideWhenUsed="false" Name="Light Grid Accent 4"/>
<w:LsdException Locked="false" Priority="63" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Shading 1 Accent 4"/>
<w:LsdException Locked="false" Priority="64" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Shading 2 Accent 4"/>
<w:LsdException Locked="false" Priority="65" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium List 1 Accent 4"/>
<w:LsdException Locked="false" Priority="66" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium List 2 Accent 4"/>
<w:LsdException Locked="false" Priority="67" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 1 Accent 4"/>
<w:LsdException Locked="false" Priority="68" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 2 Accent 4"/>
<w:LsdException Locked="false" Priority="69" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 3 Accent 4"/>
<w:LsdException Locked="false" Priority="70" SemiHidden="false"
UnhideWhenUsed="false" Name="Dark List Accent 4"/>
<w:LsdException Locked="false" Priority="71" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful Shading Accent 4"/>
<w:LsdException Locked="false" Priority="72" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful List Accent 4"/>
<w:LsdException Locked="false" Priority="73" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful Grid Accent 4"/>
<w:LsdException Locked="false" Priority="60" SemiHidden="false"
UnhideWhenUsed="false" Name="Light Shading Accent 5"/>
<w:LsdException Locked="false" Priority="61" SemiHidden="false"
UnhideWhenUsed="false" Name="Light List Accent 5"/>
<w:LsdException Locked="false" Priority="62" SemiHidden="false"
UnhideWhenUsed="false" Name="Light Grid Accent 5"/>
<w:LsdException Locked="false" Priority="63" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Shading 1 Accent 5"/>
<w:LsdException Locked="false" Priority="64" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Shading 2 Accent 5"/>
<w:LsdException Locked="false" Priority="65" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium List 1 Accent 5"/>
<w:LsdException Locked="false" Priority="66" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium List 2 Accent 5"/>
<w:LsdException Locked="false" Priority="67" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 1 Accent 5"/>
<w:LsdException Locked="false" Priority="68" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 2 Accent 5"/>
<w:LsdException Locked="false" Priority="69" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 3 Accent 5"/>
<w:LsdException Locked="false" Priority="70" SemiHidden="false"
UnhideWhenUsed="false" Name="Dark List Accent 5"/>
<w:LsdException Locked="false" Priority="71" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful Shading Accent 5"/>
<w:LsdException Locked="false" Priority="72" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful List Accent 5"/>
<w:LsdException Locked="false" Priority="73" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful Grid Accent 5"/>
<w:LsdException Locked="false" Priority="60" SemiHidden="false"
UnhideWhenUsed="false" Name="Light Shading Accent 6"/>
<w:LsdException Locked="false" Priority="61" SemiHidden="false"
UnhideWhenUsed="false" Name="Light List Accent 6"/>
<w:LsdException Locked="false" Priority="62" SemiHidden="false"
UnhideWhenUsed="false" Name="Light Grid Accent 6"/>
<w:LsdException Locked="false" Priority="63" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Shading 1 Accent 6"/>
<w:LsdException Locked="false" Priority="64" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Shading 2 Accent 6"/>
<w:LsdException Locked="false" Priority="65" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium List 1 Accent 6"/>
<w:LsdException Locked="false" Priority="66" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium List 2 Accent 6"/>
<w:LsdException Locked="false" Priority="67" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 1 Accent 6"/>
<w:LsdException Locked="false" Priority="68" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 2 Accent 6"/>
<w:LsdException Locked="false" Priority="69" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 3 Accent 6"/>
<w:LsdException Locked="false" Priority="70" SemiHidden="false"
UnhideWhenUsed="false" Name="Dark List Accent 6"/>
<w:LsdException Locked="false" Priority="71" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful Shading Accent 6"/>
<w:LsdException Locked="false" Priority="72" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful List Accent 6"/>
<w:LsdException Locked="false" Priority="73" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful Grid Accent 6"/>
<w:LsdException Locked="false" Priority="19" SemiHidden="false"
UnhideWhenUsed="false" QFormat="true" Name="Subtle Emphasis"/>
<w:LsdException Locked="false" Priority="21" SemiHidden="false"
UnhideWhenUsed="false" QFormat="true" Name="Intense Emphasis"/>
<w:LsdException Locked="false" Priority="31" SemiHidden="false"
UnhideWhenUsed="false" QFormat="true" Name="Subtle Reference"/>
<w:LsdException Locked="false" Priority="32" SemiHidden="false"
UnhideWhenUsed="false" QFormat="true" Name="Intense Reference"/>
<w:LsdException Locked="false" Priority="33" SemiHidden="false"
UnhideWhenUsed="false" QFormat="true" Name="Book Title"/>
<w:LsdException Locked="false" Priority="37" Name="Bibliography"/>
<w:LsdException Locked="false" Priority="39" QFormat="true" Name="TOC Heading"/>
</w:LatentStyles>
</xml><![endif]--><!--[if gte mso 10]>
<style>
/* Style Definitions */
table.MsoNormalTable
{mso-style-name:"Tabla normal";
mso-tstyle-rowband-size:0;
mso-tstyle-colband-size:0;
mso-style-noshow:yes;
mso-style-priority:99;
mso-style-parent:"";
mso-padding-alt:0cm 5.4pt 0cm 5.4pt;
mso-para-margin-top:0cm;
mso-para-margin-right:0cm;
mso-para-margin-bottom:10.0pt;
mso-para-margin-left:0cm;
line-height:115%;
mso-pagination:widow-orphan;
font-size:11.0pt;
font-family:"Calibri","sans-serif";
mso-ascii-font-family:Calibri;
mso-ascii-theme-font:minor-latin;
mso-hansi-font-family:Calibri;
mso-hansi-theme-font:minor-latin;
mso-fareast-language:EN-US;}
</style>
<![endif]-->nachokhanhttp://www.blogger.com/profile/00777464846297951418noreply@blogger.com0tag:blogger.com,1999:blog-2715874640743883267.post-27494891462784887132014-12-25T09:57:00.000-08:002014-12-30T22:34:43.510-08:00Fakes, Stubs y Mocks... ¿cuál es la diferencia?Buenas!<br />
Como me viene pasando últimamente, pasan décadas de un posteo a otro. Sin embargo y pese a ello: sigo posteando ;)<br />
<br />
El tema de este post surgió nada menos que de darme cuenta que los conceptos que voy a explicar, no estaban adecuadamente explicados en la facultad a la que asistí. Quizás esto ya haya cambiado, pero considero que son términos importantes como para estar mezclándolos cual ensalada de frutas.<br />
<br />
Básicamente, donde yo estudié la idea -muy genérica y nada desarrollada- de un Stub nunca se mencionó la diferencia entre Stubs y Mocks, y tampoco la diferencia entre éstos dos con un Fake. Es más, me atrevo a decir que nunca se habló de Fakes y Mocks. Pero bueno, no es el fin de este artículo evaluar ni criticar lo que tuve que estudiar, sino más bien, aclarar los conceptos.<br />
<br />
Todo esto viene a colación de la importancia que han tomado los Unit Tests (Tests de Unidad) de los cuales no voy a hablar ahora para no extenderme en el tema. Doy por hecho que ya sabes lo que son.<br />
<br />
<b>Pero, ¿por qué es importante? </b><br />
<br />
Es de suprema importancia realizar UTs que sean legibles, que cualquier persona que nunca los vio antes pueda leerlos y darse cuenta de inmediato qué quiso testear quien lo escribió. Si esto no es así, se pierde la filosofía que está detrás de los UT, que es justamente proporcionar un método muy rápido de testeo. Si un programador tiene que dedicar mucho tiempo a entender un test que ha dado FAIL (o peor aún: muchos tests!) por el solo hecho de que se escribió mal o que es ilegible, el resultado es terriblemente predecible: no lo va a leer y lo va a dejar de lado porque entender y/o arreglar ese test le va a consumir mucho tiempo, y seguramente "tiene cosas más importantes que hacer". Se ignora el test, se lo marca como obsoleto, etc. Y allí comienza la decadencia del sistema. Y de la calidad. Y de la mantenibilidad. Y etc, etc.<br />
<br />
Siempre es una necesidad que tengas claro los conceptos referidos a tu especialidad, a lo que te dedicas a hacer.<br />
<br />
Antes que nada, esta entrada presupone que ya sabes lo que es un test de unidad, cual es su finalidad, y que significan las tres A (Arrange-Act-Assert). Si no es asi, sería bueno que le googlearas por ahí.<br />
<br />
Bien, comencemos entonces:<br />
<br />
<b>¿Qué es un Fake?</b><br />
El verbo "to fake" en inglés es fingir, falsificar. Eso es lo que es un Fake: una falsificación de un objeto. (Hablo de "objetos" por que normalmente en mi blog me refiero a la OO (orientación a objetos) por ser la que más utilizo, pero es aplicable también a otros paradigmas.)<br />
<br />
Es decir, es un objeto que es una falsificación de otro. Se ve "igual" pero se comporta distinto.<br />
<br />
<b>¿Y para qué sirve?</b><br />
Porque si queremos realizar pruebas de unidad, es indispensable que estas sean rápidas! Pero no sólo por esto! Sobre todo que el comportamiento de la clase (o unidad de trabajo) que estamos testeando (llamémsole UUT de "Unit Under Test") no se debe ver influenciado por el comportamiento de algún otro objeto del que pueda hacer uso. Si por ejemplo queremos testear el método de una clase que por dentro hace uso de un objeto que se comunica con la base de datos, tenemos dos problemas:<br />
<br />
<ol>
<li>El objeto que utiliza la base de datos (llamemosle "ConexionConBD") hará muy lento el testing, ya que debe conectarse con la mismísima base de datos, entorpeciendo este principio.</li>
<li>Si el objeto "ConexionConBD" falla cuando nosotros esperamos que no falle, entonces fallará también el UUT (nuestra unidad bajo testing)..</li>
<li>Si la mismísima base de datos falla (por la razón que fuere, cualquiera), nuevamente fallará también nuestro UUT.</li>
</ol>
<br />
Y el problema con las fallas que son "ajenas" a nuestro UUT, es que finalmente no vamos a saber si lo que falla es el UUT, o es alguno de los objetos que éste utiliza. Y pierde sentido el concepto de "Test de UNIDAD".<br />
<br />
Entonces, para solucionarlo, inventemos un "ConexionConBD" que responda lo que nosotros queramos, cuando nosotros queramos, y como nosotros queramos. Es decir, falsifiquemos esa clase. Para ello, crearemos un Fake de la clase "ConexionConBD", la cual podemos controlar desde el test que estamos escribiendo.<br />
<br />
No voy a entrar en detalles técnicos de como se hace, ya mucho en internet sobre como hacerlo. <br />
<br />
Entonces....<br />
<br />
<b>¿Qué es un Stub?</b><br />
El Stub, es ni más ni menos, un subtipo de Fake. Si, asi de simple: los Fakes se dividen en dos clases: o son Stubs, o son Mocks. ¿Y de qué depende? De según como se use, de eso depende!<br />
<br />
En el caso anterior, podemos utilizar el Fake "<i>ConexionConBD</i>" para que siempre nos devuelva un valor esperado, así no nos encontramos con sorpresas. Una vez que el fake nos devuelve el valor que esperamos, podemos proseguir con el test.<br />
<br />
Pero al usar este fake como un Stub, éste sólo puede utilizarse para devolver un valor o arrojar una exepción. Pero nada más. NADA MÁS! Es decir, el test se sigue haciendo directamente contra nuestro UUT.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi2XDMak6jOb1UFkqmEWBlNnwxGIaJh1VTc4X3h_o62QdOVBYGMxlYidwCbBAiDaYMS2L8xF88TRLOJIFV26odjssONXUfjNwiqSpKrZEoVQLsTliMDw-xzLzwnMdxcOCsT7_0ObWy3-cc/s1600/STUB.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi2XDMak6jOb1UFkqmEWBlNnwxGIaJh1VTc4X3h_o62QdOVBYGMxlYidwCbBAiDaYMS2L8xF88TRLOJIFV26odjssONXUfjNwiqSpKrZEoVQLsTliMDw-xzLzwnMdxcOCsT7_0ObWy3-cc/s1600/STUB.JPG" height="264" width="320" /></a></div>
<br />
<br />
<b>¿Y qué es un Mock?</b><br />
En el caso anterior, el fake se utiliza solo para que devuelva un valor esperado, es decir, que realice un comportamiento definido y así poder proseguir con nuestro test.<br />
<br />
Pero en otras ocasiones, el test mismo consiste en saber si el UUT se comunicó de forma esperada con otro objeto. Por ejemplo, queremos saber si el UUT llamó al objeto "<i>ConexionConDB</i>" de forma correcta. En este caso, no podemos hacer un <i>assert </i>directamente contra la UUT, ya que la UUT mismo no sabe si la llamada -a un método por ejemplo- fue realizada con éxito. Quién sabe esto, es el objeto llamado en cuestión, es decir, el objeto "<i>ConexionConBD</i>". Pero el "ConexionConBD" original (aquel que se conecta con la base de datos) no está programado para decirnos si la llamada que le hicieron fue correcta, y encima, se conecta con la base de datos! (algo que queremos evitar!).<br />
<br />
Solución: le hacemos un fake. Pero este fake no devuelve un valor, sino que simplemente recibe la llmada que le hace la UUT y, nuestro test, en lugar de hacer un <i>assert </i>contra la UUT, lo hace contra el Fake.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjuYAw0Trf-HlE_YhYcVJ7mp0epRPtm7Zb81AFRjm55IyprWJIv6TohIrs1Ak2uExddxYQwZfPGPKZc-8r4h_4xhhnnc8sOSl-2hN2zMAs62k6QXp4lXF9BrUv-NE71G6MiMxWPHZmdV1M/s1600/MOCK.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjuYAw0Trf-HlE_YhYcVJ7mp0epRPtm7Zb81AFRjm55IyprWJIv6TohIrs1Ak2uExddxYQwZfPGPKZc-8r4h_4xhhnnc8sOSl-2hN2zMAs62k6QXp4lXF9BrUv-NE71G6MiMxWPHZmdV1M/s1600/MOCK.JPG" height="265" width="320" /></a></div>
<br />
<br />
Y esta es la clave: cuándo nuestro test verifica (<i>assert</i>) contra un fake, este fake es un Mock.<br />
Si en cambio la verificación se hace contra la UUT, entonces cualquier fake que haya será un Stub.<br />
<br />
<b>Resumiendo</b>.<br />
<br />
Todo fake será indefectiblemente un Stub o un Mock dependiendo de cómo se lo utilice. Es más, un fake puede ser Stub en un test, y Mock en otro test. La diferencia está en el uso que se le da a ese fake. Nada más y nada menos.<br />
<br />
Según Roy Osherove (The Art Of Unit Testing, un libro que recomiendo
totalmente y del cual saqué la idea para las imágenes) cada UT (Unit Test) no debería tener más de un Mock, y sí
puede tener más de un Stub. Osherove es partidario de que si un UT tiene
más de un Mock, el test pierde legibilidad y se hace más complicado de
entender "inmediatamente" qué es lo que se quiere testear. <br />
<br />
Espero que haya sido suficientemente claro, ya que es un tema de muchas confusiones por lo que he visto yo.<br />
<br />
Hasta pronto, espero! nachokhanhttp://www.blogger.com/profile/00777464846297951418noreply@blogger.com0tag:blogger.com,1999:blog-2715874640743883267.post-36585068292866667942013-05-16T07:31:00.000-07:002013-05-16T14:35:12.100-07:00Ejemplo: Implementacion del Patron Singleton en C++Buenas! Si, después de casi dos años de inactividad, volví para mostrar la implementación de un patrón Singleton en C++.<br />
<br />
Como sabemos, una clase que implementa este patrón, tiene una característica muy importante: no puede crearse mas que una instancia (objeto) de esa clase. Pero claro, esta caracteristica es relevante o toma importancia en algunos casos. Personalmente, donde mas utilizo este patron es cuando creo "fabricas", es decir, utilizo el patron Factory para con alguna finalidad. El patron Factory lo dejo para otro momento, y es igual de interesante y util que el Singleton pero ahora no viene al caso.<br />
<br />
Entonces, lo importante que debemos tener en cuenta, es que sin importar cuantos objetos queramos crear, solo estaremos creando una y solo una <u>instancia </u>de esta clase. Si por ejemplo creamos 5 variables de una clase llamada MiClase e instanciamos dichas variables, todas las variables corresponderán a la misma instancia. Es decir, sera indistinto utilizar una u otra de estas variables para acceder a los métodos y atributos del objeto.<br />
<br />
Pero este patron tiene dos puntos que hay que tener en cuenta:<br />
<br />
<u>Punto 1. No puede inicializarse "manualmente".</u><br />
Es decir, no podemos hacer la asignación con new:<br />
<pre class="brush:c">MiClase ObjetoA = new MiClase();</pre>
<br />
Es imposible! Y por que? Porque el <i>constructor </i><u>debe</u> ser privado. De esta forma nos aseguramos que no se pueda andar creando instancias libre y salvajemente por ahí. Bueno, si no se pueden crear instancias... cómo hacemos para exista esa famosa única instancia?<br />
<br />
<u>Punto 2. Alguien debe crear la instancia única.</u><br />
Y si, nada es magia en esto de la programación. Quien puede crear la instancia? Bueno, pues la misma clase! Es decir, que de alguna manera, la clase debe crear una instancia de si misma y asegurarse que sea solo una. Esto lo haremos con un método que le llamaremos, por convención nomás: <i>getInstance()</i>.<br />
<br />
Es decir, que para obtener la unica instancia de una clase que implementa el patron Singleton, haremos algo asi:<br />
<pre class="brush:c">MiClase ObjetoA = MiClase()::getInstance();</pre>
<br />
Súper fácil! Y ahora si, <i>ObjetoA </i>apunta directamente a la instancia única de la clase <i>MiClase</i>. Ahora veamos que se esconde detrás de todo esto, en términos de C++. Como toda clase de C++ tendremos los archivos .h y .cpp:<br />
<br />
<b><u>MiClase.h</u></b><br />
En este archivo lo importante a tener en cuenta es:<br />
<ul>
<li>El constructor <i>MiClase()</i> es privado. No podrá usarse la palabra clave <i>new</i>.</li>
<li>Tenemos un atributo estático llamado <i>unica_instancia</i> que es un puntero a la clase que lo contiene y que tambien es privado. No podra accederse desde fuera de un objeto.</li>
<li>El método <i>getInstance()</i> es también estático! Claro, sino no podría accederse hasta tener un objeto de la clase, y justamente este el método que crea el primer (y único) objeto, con lo cual debe ser estático si o si. </li>
</ul>
<pre class="brush:c">class MiClase
{
private:
MiClase(void);
static MiClase* unica_instancia;
public:
~MiClase(void);
static MiClase *getInstance()
{
if(unica_instancia == NULL)
unica_instancia = new MiClase();
return unica_instancia;
}
};</pre>
<br />
<br />
<u><b>MiClase.cpp</b></u><br />
Acá lo importante es inicializar la variable estática <i>unica_instancia</i>. La inicializamos en <i>NULL </i>de forma que nos aseguremos que no se crea ningún objeto hasta que se llame por primera vez a <i>getInstance()</i>.<br />
<u><b><span style="color: red;">Atención</span></b></u>! El caso particular de C++ requiere que las variables estáticas sean definidas en el archivo .CPP y no en el .H. Caso contrario se obtiene un error de compilación.<br />
<pre class="brush:c">#include "MiClase.h"
MiClase* MiClase::unica_instancia = NULL;
MiClase::MiClase(void)
{
}
MiClase::~MiClase(void)
{
}</pre>
<br />
Por ultimo, cabe explicar brevemente lo que hace el método <i>getInstance()</i>. Básicamente, crea una instancia de la clase solo en el caso de que no exista una (por eso el <i>if</i>). Luego devuelve la instancia creada o, sino creó porque ya existia, devuelve la existente. Asi es seguro que no tendremos mas de una instancia.<br />
Saludos! nachokhanhttp://www.blogger.com/profile/00777464846297951418noreply@blogger.com1tag:blogger.com,1999:blog-2715874640743883267.post-51316253988599630202011-07-08T12:37:00.000-07:002011-07-08T12:49:32.515-07:00Error: Clase No Registrada.Tenía un proyecto en .NET que en una máquina compilaba y corría sin problemas. Pero al querer pasar el proyecto a otra máquina, luego de compilar, me aparecía la siguiente excepción:<br /><br /><span style="color: rgb(255, 0, 0); font-weight: bold; font-style: italic;font-size:85%;" >Clase no registrada (Exception from HRESULT: 0x80040154 (REGDB_E_CLASSNOTREG)</span><span style="color: rgb(255, 0, 0); font-weight: bold; font-style: italic;font-size:85%;" >)</span><br /><br />La solución es simple: además de agregar las referencias al proyecto, debemos hacer click derecho (desde un adminsitrador de archivos) en los archivos DLLs que hemos referenciado, y luego click en "Registrar".<br /><br />Con eso se soluciona el problema.<br /><br />S2nachokhanhttp://www.blogger.com/profile/00777464846297951418noreply@blogger.com0tag:blogger.com,1999:blog-2715874640743883267.post-62773665431350231922011-04-22T17:22:00.000-07:002011-04-25T10:53:39.432-07:00Programación Orientada a Aspectos. Implementando un Aspecto. (Parte 3)En la <a href="http://includeblogh.blogspot.com/2011/04/programacion-orientada-aspectos-por-que.html">parte 2</a> planteamos una situación utópica: que una clase realizara algo sin indicarle que lo hiciera. Bueno, sí, eso es utópico. Pero, podemos pensar en otra "entidad" que se encargue de realizar esa tarea (en nuestro caso es registrar un movimiento) detectando el momento preciso en el que debe realizarla. Es decir, tener "algo" que sepa cuando registrar un movimiento, pero que ese "algo" no tenga nada que ver con la clase <span style="font-style: italic;">Cuenta</span>. Ese algo, es lo que llamamos aspecto. Un aspecto, entonces, es algo que entrecruza una o más clases. Dicho de otra forma, un aspecto es un concepto que ofrece <span style="font-weight: bold;">funcionalidad transversal</span> a los objetos del negocio.<br /><br />Imaginémoslo como algo que vigila un programa a medida que este se ejecuta; y a partir de ciertas acciones que se producen en el programa, este algo realiza acciones propias de él. Llamemos a este algo "aspecto". Podemos decirle a un aspecto, que cuando se termine de ejecutar cualquier método que comience con la palabra "hacer" realice un registro de movimiento. La clase <span style="font-style: italic;">Cuenta</span> de nuestro ejemplo, no se enteraría jamás de la existencia de este aspecto, pero este aspecto está atento a las ejecuciones <span style="font-style: italic;">hacerAlgo...() </span>de esa clase.<br /><br />Vamos a definir rápidamente este aspecto para poder visualizar mejor lo que estamos tratando de decir. Para poder escribir un aspecto y compilar el programa con este aspecto, es necesario tener instalado el plugin de AspectJ en el editor de Java que estemos usando, o poder compilar configurando el editor. Bueno, pasemos a definir el aspecto de una vez. Agregamos un nuevo archivo con extensión .aj (por "<span style="font-style: italic;">AspectJ</span>") al proyecto:<br /><br /><pre class="brush:java">public aspect RegistroDeMovimientos {<br /><br /> after():execution (public * hacer*(..)) {<br /> Date hora = new Date(); <br /> SimpleDateFormat formatoDeFecha = new SimpleDateFormat("yyyy.MM.dd G 'at' HH:mm:ss");<br /> System.out.println("Movimiento realizado a las " + formatoDeFecha.format(hora)) ;<br /> }<br />}</pre><br /><br />Observemos este código. Lo primero que vemos, es la declaración del aspecto bajo el nombre de <span style="font-style: italic;">RegistroDeMovimientos</span>. Por ende, al igual que las clases de Java, el archivo del aspecto debe llamarse <span style="font-style: italic;">RegistroDeMovimientos.aj</span>. Si seguimos vemos la linea que dice:<br /><br /><pre class="brush:java">after():execution (public * hacer*(..)) {</pre><br /><br />Esta línea es clave para definir unos conceptos que explicaré más adelante. Pero por el momento, analizaremos de manera simple qué significa. Si tuviera que decirlo en lenguaje informal, la describiría asi: "<span style="font-style: italic;">Después de la ejecución de cualquier método público, sin importar el tipo que retorne, que comience con hacer, y sin importar la cantidad y tipo de parámetros que requiera, hacer lo siguiente:</span>". Eso significaría mas o menos. Ahora examinemos lo que significa cada parte:<br /><br /><ul><li><span style="font-weight: bold;">after()</span>: Esto indica que el cuerpo se ejecutará después de algo. Ahora veamos ese algo.</li><li><span style="font-weight: bold;">execution ( )</span>: esto significa que se toma como referencia una ejecución de lo que sigue:<br /></li><li><span style="font-weight: bold;">public * hacer*(..)</span>: <span style="font-style: italic;">public</span> nos indica que se buscará un método público cualquiera. El asterisco que sigue, indica que ademas no importa el tipo que retorna el método. <span style="font-style: italic;">hacer*</span> significa que el nombre del método debe comenzar con la palabra "<span style="font-style: italic;">hacer</span>" y no importa cómo termine (para eso está el asterisco luego de "hacer"). Y finalmente, los dos puntos entre paréntesis significa que no importa ni el tipo ni la cantidad de parámetros del método. </li></ul><br />Entonces, revisemos la descripción informal del método: "<span style="font-style: italic;">Después dela ejecución de cualquier método que sea público, que retorne cualquier tipo de dato, que comience con "hacer" y sin importar el tipo y cantidad de parámetros</span>", se debe ejecutar lo que se indica en el cuerpo. El cuerpo es muy simple: no es mas que aquello que hacía el método <span style="font-style: italic;">RegistrarMovimiento()</span> que hicimos antes. Esto es un aspecto. Simple, ¿no? El aspecto, se encargará de ejecutar ese cuerpo de código cada vez que ocurra esa descripción informal.<br /><br />Con esto, hemos solucionado el problema del apartado anterior. ¿Por qué? Porque hemos logrado aislar el comportamiento de registrar un movimiento, de forma tal que la clase <span style="font-style: italic;">Cuenta</span> no conoce de su existencia. Por lo tanto, no hemos tenido que repetir ningún código dentro de la clase, y además, al aislar ese comportamiento en un sólo aspecto, este se hace más fácilmente mantenible, ya que si queremos modificar la forma en que se registran los movimientos sólo debemos modificar el cuerpo de ejecución del aspecto, y no preocuparnos por las clases del negocio.<br /><br />Por supuesto, para compilar aspectos, no podemos usar el compilador de java, para eso usamos el compilador <span style="font-style: italic;">AspectJ</span> que lo que hace es armar un código complementando el código base con el código de aspectos. Una vez que esta herramienta genera el código final, ahí si se puede correr sobre la máquina virtual de java.nachokhanhttp://www.blogger.com/profile/00777464846297951418noreply@blogger.com16tag:blogger.com,1999:blog-2715874640743883267.post-64208641648752440082011-04-20T06:28:00.000-07:002015-07-31T16:12:40.767-07:00Programación Orientada a Aspectos. ¿Por qué usar POA? (Parte 2)En la <a href="http://includeblogh.blogspot.com/2011/04/programacion-orientada-aspectos-que-es.html">parte 1</a>, di una introducción a lo que es la programación orientada a aspectos de una forma bastante teórica. En esta segunda parte, voy a mostrar un ejemplo práctico muy simplificado para poder asentar un poco más el concepto. Para el ejemplo utilicé el lenguaje Java, y la extensión del lenguaje Java que implementa aspectos AspectJ. Ya he contado <a href="http://includeblogh.blogspot.com/2011/04/instalar-aspectj-en-netbeans-691.html">como compilar con AspectJ en NetBeans</a>.<br />
<br />
Bueno, ¿en qué consiste la POA? Voy a poner un ejemplo muy fácil que ilustra el potencial que nos ofrece la POA Supongamos una aplicación de transacciones bancarias. (Este es el típico ejemplo que se menciona en todos lados, y lo voy a poner en mi versión porque es muy ilustrativo).<br />
<br />
Vamos a tener una clase cuyo nombre es <span style="font-style: italic;">Cuenta</span> y es una versión muy simplificada de una cuenta de un banco. Esta clase, tiene un atributo llamado <span style="font-style: italic;">saldo</span> de tipo <span style="font-style: italic;">Double</span> que indica el saldo en la cuenta. Y vamos a tener tres métodos: Uno que llamado <span style="font-style: italic;">hacerDeposito()</span> con un único parámetro de tipo <span style="font-style: italic;">Double</span> que indica la cantidad a depositar en la cuenta. Otro llamado <span style="font-style: italic;">hacerExtraccion()</span> que cuenta con un solo parámetro que indica la cantidad de dinero que se quiere extraer de la cuenta. El método restante se llama <span style="font-style: italic;">hacerTransferencia()</span> y tiene dos parámetros: uno indica la cantidad de dinero y el otro es la cuenta a la que se quiere hacer la transferencia. Además tendremos una típica -y muy simplificada- clase que representa un cliente del supuesto "banco" llamada <span style="font-style: italic;">Cliente</span>. Para seguir simplificando (cosa que se me está haciendo mala costumbre ya) un cliente sólo puede tener una cuenta. Ilustremos con UML:<br />
<br />
<br />
<a href="http://www.conocimiento-libre.org/wiki/images/1/16/Poa_diagrama_clases_1.JPG" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"><img alt="" border="0" src="http://www.conocimiento-libre.org/wiki/images/1/16/Poa_diagrama_clases_1.JPG" style="cursor: hand; cursor: pointer; display: block; height: 145px; margin: 0px auto 10px; text-align: center; width: 573px;" /></a><br />
A continuación, les muestro un ejemplo en java bien simple. Lo que haré en el método <span style="font-style: italic;">Main()</span> será crear dos clientes (Pepe y Juan) y crear una cuenta para cada uno. Después, le asignaré a Pepe una cuenta y a Juan la otra. Finalmente, haré una extracción de la cuenta de Pepe; y -felizmente para Juan- terminaré con una transferencia de dinero de la cuenta de Pepe a la de Juan. Aunque lo que realiza el método <span style="font-style: italic;">Main()</span>, por el momento no nos interesa. Veamos el código de ambas clases (sin los <span style="font-style: italic;">getter</span> y los <span style="font-style: italic;">setters</span> porque son muy triviales).<br />
<br />
Veamos la definición de los métodos <span style="font-style: italic;">hacer...()</span> de la clase <span style="font-style: italic;">Cuenta</span>:<br />
<br />
<pre class="brush:java">public void hacerDeposito(Double cantidad) {
if(cantidad <= 0) {
System.out.println("No hay dinero suficiente para hacer el depósito");
}
else {
Date hora = new Date();
SimpleDateFormat formatoDeFecha = new SimpleDateFormat("yyyy.MM.dd G 'at' HH:mm:ss");
//Realizamos el depósito
this.saldo += cantidad;
//Registramos el movmiento
System.out.println("Movimiento realizado a las " + formatoDeFecha.format(hora)) ;
}
}
public void hacerTransferencia(Double cantidad, Cuenta cuentaDestino)
{
if(this.saldo < cantidad) {
System.out.println("No hay fondos suficientes para la transferencia.");
}
else {
Date hora = new Date();
SimpleDateFormat formatoDeFecha = new SimpleDateFormat("yyyy.MM.dd G 'at' HH:mm:ss");
cuentaDestino.hacerDeposito(cantidad);
this.saldo -= cantidad;
//Registramos el movmiento
System.out.println("Movimiento realizado a las " + formatoDeFecha.format(hora)) ;
}
}
public void hacerExtraccion(Double cantidad) {
if(this.saldo < cantidad) {
System.out.println("No hay fondos suficientes para la extracción.");
}
else {
Date hora = new Date();
SimpleDateFormat formatoDeFecha = new SimpleDateFormat("yyyy.MM.dd G 'at' HH:mm:ss");
//Extraemos la cantidad del saldode la cuenta.
this.saldo -= cantidad;
//Registramos el movmiento
System.out.println("Movimiento realizado a las " + formatoDeFecha.format(hora)) ;
}
}</pre>
Examinemos un poco el código: en todos métodos comenzamos con una verificación de la cantidad que se quiere extraer, depositar o transferir. Esta verificación es necesaria, ya que ciertamente sería un error, por ejemplo, depositar una cantidad negativa de dinero de una cuenta; también sería un error permitir una extracción de más cantidad de dinero de la que se dispone. Estas situaciones deben evitarse si o si.<br />
<br />
Por otra parte, vemos que una vez que se comprueban las cantidades y que esta todo bien, se procede a realizar el deposito o la extracción o la transferencia (según sea el caso). Pero para esto, he supuesto (a modo de regla de negocio) que el banco necesita tener un registro (<span style="font-style: italic;">logging</span>) de cada movimiento realizado en cada cuenta. Por eso es que creé una variable llamada hora y la mostré en pantalla. Lo más común no es mostrarlo en pantalla sino almacenarlo en un archivo de logs, pero lo hice así por practicidad.<br />
<br />
Lo interesante, curioso, y que tenemos que tener en cuenta de estos métodos, es que los tres repiten el proceso de registrar un movimiento. Y no solo eso, sino que además se hace en los tres métodos de la misma forma! Lo cual es una muy mala práctica. Nunca debería repetirse código, eso lo sabemos bien. Entonces, he aquí un problema. ¿Cómo lo solucionamos?...<br />
<br />
Algún iluminado podría indicar hacer lo siguiente: Creamos un cuarto método dentro de la clase al que podríamos llamar <span style="font-style: italic;">registrarMovimiento()</span>. Este método se encargaría sólo de la parte del registro del movimiento; y cada uno de los anteriores tres métodos lo llama cuando ha concluido un movimiento. Genial. Veamos cómo quedaría <span style="font-style: italic;">registrarMovimiento()</span>:<br />
<br />
<pre class="brush:java">private void registrarMovimiento()
{
Date hora = new Date();
SimpleDateFormat formatoDeFecha = new SimpleDateFormat("yyyy.MM.dd G 'at' HH:mm:ss");
System.out.println("Movimiento realizado a las " + formatoDeFecha.format(hora)) ;
}</pre>
<br />
Y ahora, el código de los otros tres métodos se modifica para llamar a <span style="font-style: italic;">registrarMovimiento()</span> quedando así (sólo mostramos dos, pero el tercero seguiría la misma lógica que éstos):<br />
<br />
<pre class="brush:java">public void hacerTransferencia(Double cantidad, Cuenta cuentaDestino)
{
if(this.saldo < cantidad) {
System.out.println("No hay fondos suficientes para la transferencia.");
}
else {
cuentaDestino.hacerDeposito(cantidad);
this.saldo -= cantidad;
//Registramos el movmiento
registrarMovimiento();
}
}
public void hacerExtraccion(Double cantidad) {
if(this.saldo < cantidad) {
System.out.println("No hay fondos suficientes para la extracción.");
}
else {
//Extraemos la cantidad del saldode la cuenta.
this.saldo -= cantidad;
//Registramos el movmiento
registrarMovimiento();
}
}</pre>
Pero lo malo de esta solución, es que si se efectuara otro movimiento que no fuera "dentro" de la clase <span style="font-style: italic;">Cuenta</span> sino "dentro" de otra clase, esta clase tendría que tener su propio método <span style="font-style: italic;">registrarMovimiento()</span>, y nuevamente estaríamos repitiendo código, pero entre clases. Ante este nuevo problema, podemos proponer otra solución: creamos una clase que se encargue específicamente de registrar movimientos; y cualquier método de cualquier clase puede instanciarla y llamar al método que registra movimientos. Podríamos, por ejemplo, crear una clase llamada <span style="font-style: italic;">RegistroDeMovimientos</span> con un método público llamado <span style="font-style: italic;">registrarMovimiento()</span>. Parece que esta es la mejor solución. Sin embargo no es así. Sigue habiendo "algo malo". ¿Qué es?...<br />
<br />
Está claro: aunque tengamos una clase aparte que registre los movimientos, cada clase debe instanciar y llamar al método de esa clase. Es decir, seguimos repitiendo código. Cada método que deba registrar un movimiento, debe repetir el mismo código que los otros métodos que también deban registrar un movimiento. ¡Que feo! ¡Y no solo eso! Además, el hecho de registrar un movimiento -ya sea dentro del mismo método o llamando a <span style="font-style: italic;">RegistroDeMovimientos.registrarMovimiento()</span>- significa introducir código que no pertenece exclusivamente al movimiento. El movimiento sólo debería realizar el movimiento y nada más. El registro debería hacerse aparte, no debería ser responsabilidad de un depósito, o una extracción o una transferencia. Esta forma de registrar un movimiento, ensucia el código que se refiere a los métodos de la clase <span style="font-style: italic;">Cuenta</span>. Es decir, lo ideal sería que estos métodos se vieran así:<br />
<br />
<pre class="brush:java">public void hacerDeposito(Double cantidad) {
if(cantidad <= 0) {
System.out.println("No hay dinero suficiente para hacer el depósito");
}
else {
this.saldo += cantidad;
}
}
public void hacerTransferencia(Double cantidad, Cuenta cuentaDestino)
{
if(this.saldo < cantidad) {
System.out.println("No hay fondos suficientes para la transferencia.");
}
else {
cuentaDestino.hacerDeposito(cantidad);
this.saldo -= cantidad;
}
}
public void hacerExtraccion(Double cantidad) {
if(this.saldo < cantidad) {
System.out.println("No hay fondos suficientes para la extracción.");
}
else {
this.saldo -= cantidad;
}
}</pre>
Como dije, sería ideal que a pesar de no llamar a <span style="font-style: italic;">registrarMevimiento()</span>, el movimiento se registrara igual. Eso sería ideal, ¿no? No ensuciaríamos código y tampoco lo repetiríamos. La clase sería tremendamente cohesiva. Parece utópico ¿verdad? Bueno, para nuestra felicidad, esto es realizable y es acá donde entran en juego los "<span style="font-style: italic;">aspectos</span>".<br />
<br />
En la <a href="http://includeblogh.blogspot.com.ar/2011/04/programacion-orientada-aspectos.html" target="_blank">tercer parte</a>, voy a mostrar cómo implementar un aspecto que solucione el problema planteado.nachokhanhttp://www.blogger.com/profile/00777464846297951418noreply@blogger.com3tag:blogger.com,1999:blog-2715874640743883267.post-17210426988778659082011-04-15T06:58:00.000-07:002015-07-31T16:11:58.625-07:00Programación Orientada a Aspectos. ¿Qué es? (Parte 1)Para explicar la programación orientada a aspectos, quizás lo ideal sería comenzar con una breve descripción de la historia de la ingeniería de software, como lo hacen la mayoría de los artículos o PDFs que podemos encontrar en la web, pero como justamente la mayoría lo hace así, yo no. Quien quiera verlo así, busque en la web el tema y ahí van a tener mucho para leer (y no está demás). Pero yo prefiero comenzar yendo al grano.<br />
<br />
La programación orientada a objetos trata la manera de <a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgPU78hdv-INRkjf05-XDnm-bZ5G-POUg2mTnl02TwJXe3Ay0WbdE87_1iJL4AdVEuCEtUMB_t6UHiq2CT_RQa6nnKRCn8a9eFcr-XYsljYqa1znPGPtfFbr96HhPQUHmphHSxxXv4-mJE/s1600/poa2.JPG" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"><img alt="" border="0" id="BLOGGER_PHOTO_ID_5596642640046394146" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgPU78hdv-INRkjf05-XDnm-bZ5G-POUg2mTnl02TwJXe3Ay0WbdE87_1iJL4AdVEuCEtUMB_t6UHiq2CT_RQa6nnKRCn8a9eFcr-XYsljYqa1znPGPtfFbr96HhPQUHmphHSxxXv4-mJE/s320/poa2.JPG" style="cursor: hand; cursor: pointer; float: right; height: 320px; margin: 0 0 10px 10px; width: 153px;" /></a>modularizar "conceptos comunes" de software. Sin embargo, existen otros conceptos o asuntos que se extienden a lo largo de estos "conceptos comunes" y que no pueden ser modularizados con el diseño orientado a objetos. En un intento más de subsanar lo que la orientación a objetos no puede, aparece una nueva solución: la Programación Orientada a Aspectos (y su consecuente Diseño Orientado a Aspectos).<br />
<br />
La programación orientada a aspectos se ha planteado como un nuevo paradigma de programación (con lo que no estoy de acuerdo), el cual consiste en separar los conceptos que entrecruzan varias clases y se extienden a lo largo de éstas, pero que no pertenecen a esas clase en sí mismas. Incluso, los conceptos pueden aparecer no sólo en varias clases, sino de una sola. Ahora la pregunta: ¿de qué tipo de conceptos hablamos? Pensemos como ejemplo, en una aplicación que requiere del registro (logging) de las acciones que realiza. Bien, estos registros "alguien" tiene que llevarlos a cabo, y es normal cargar a muchas clases con la responsabilidad de hacerlo cuando, seguramente, no sea la función principal de esas clases hacer dicha tarea. Como vemos, se trata de una misma funcionalidad (registrar acciones) que se encuentra entrecruzada en varias clases, pero que a la vez no es parte de ninguna.<br />
<br />
Otro ejemplo, simple de entender, es el manejo de errores. Los errores hay que tratarlos sí o si en una aplicación. Pero, desde un punto de vista "purista" de objetos, no tenemos clases que traten los errores por sí solos. Siempre las clases que definen la funcionalidad de la aplicación van a tener que lidiar con los errores, y esa no es su función principal. En cierta forma, esto hace a dichas clases menos cohesivas. Un tercer ejemplo puede ser el manejo de la sincronización. Nuevamente, los objetos del negocio deben tratar la sincronización además de su función principal.<br />
<br />
En todo los ejemplos estam<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhoJnNVKFp1U7-moAm88Ld3kIEdh71wMW50aY0mrn8BXcsVgYp7_CbxQHkl0y8VjRWYxBtvwlt0lK4vfbttxqwc09A-N6qQrDa88iMPYCBrboeIi9igoU-KB8HYxvXL_nqRD01JB8NojOw/s1600/poa3.JPG" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"><img alt="" border="0" id="BLOGGER_PHOTO_ID_5596642954925297074" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhoJnNVKFp1U7-moAm88Ld3kIEdh71wMW50aY0mrn8BXcsVgYp7_CbxQHkl0y8VjRWYxBtvwlt0lK4vfbttxqwc09A-N6qQrDa88iMPYCBrboeIi9igoU-KB8HYxvXL_nqRD01JB8NojOw/s400/poa3.JPG" style="cursor: pointer; float: left; height: 205px; margin: 0pt 10px 10px 0pt; width: 311px;" /></a>os viendo que hay ciertos asuntos que no pertenecen al negocio pero que sin embargo, deben ser tratados. Y lo negativo de esto, es que según el paradigma de objetos, son los objetos del negocio quienes deben tratar estos asuntos y, como si fura poco que hagan tareas que no les corresponden, estas tareas se esparcen a lo largo de toda la aplicación, llevando a dispersar código y enredarlo por la misma (a esto último se llama Code Tangling y Code Scattering).<br />
<br />
Entonces, para darle más sentido a la POA, veamos en qué se afecta el desarrollo de aplicaciones cuando surgen estos asuntos:<br />
<ul>
<li><span style="font-style: italic;">Baja cohesión</span>: las clases realizan tareas que no le corresponden específicamente a ellas.</li>
<li><span style="font-style: italic;">Baja reusabilidad</span>: Cuando una clase trata un asunto que no le corresponde, esta clase probablemente sea difícil de re usar en otro sistema.</li>
<li><span style="font-style: italic;">Peor calidad de código</span>: El código se complica, y cuesta más entenderlo y mantenerlo.</li>
<li><span style="font-style: italic;">Evolución dificultosa</span>: Se vuelve más costoso que el sistema evoluciones al tener código disperso y enredado por toda la aplicación.</li>
</ul>
<br />
Estos asuntos son denominados "conceptos transversales". La POA viene para tratar de separar y encapsularlos en una aplicación y aislarlos para independizar a los objetos del negocio de éstos conceptos. Este aislamiento se encapsula en "aspectos".<br />
<br />
Por lo tanto, la POA permite la separación de conceptos transversales a través de mecanismos que permiten abstraer y encapsular estos conceptos en un sistema. Un <span style="font-weight: bold;">aspecto</span> es la unidad que encapsula uno o más conceptos transversales, y que con la programación orientada a objetos no es posible diferenciarlo de forma clara.<br />
<br />
La definición actual de <span style="font-weight: bold;">aspecto</span> dice: "<span style="font-style: italic;">Un aspecto es una unidad modular que se disemina por la estructura de otras unidades funcionales. Los aspectos existen tanto en la etapa de diseño como en la de implementación. Un aspecto de diseño es una unidad modular del diseño que se entremezcla en la estructura de otras partes del diseño. Un aspecto de programa o de código es una unidad modular del programa que aparece en otras unidades modulares del programa.</span>”<br />
<br />
La principal ventaja de la POA es que permite tratar la funcionalidad pura por un lado, y los aspectos por otro, de forma separada. Luego ambos se combinan con un tipo de programa llamado '<span style="font-style: italic;">Weaver</span>') para dar por resultado el sistema final.<br />
<br />
Hay mucho más por hablar de POA aún. En la próxima parte (<a href="http://includeblogh.blogspot.com.ar/2011/04/programacion-orientada-aspectos-por-que.html" target="_blank">parte 2</a>), voy a mostrar un ejemplo muy claro, para asentar toda esta teoría en algo más palpable, así que a no abandonar si esta pequeña introducción a resultado medio difícil de entender, en el ejemplo de la Parte 2 quedará muy claro de que se trata.nachokhanhttp://www.blogger.com/profile/00777464846297951418noreply@blogger.com4