Nadie dice que sea razonable pensar en métodos de una sóla línea, pero todos
sabemos que uno de 20 es mejor que uno de 200 ¿o alguien lo duda?
El caso es:¿por que no 10 en vez de 20? Y sólo 5? llevar la ídea al extremo y
pensar en métodos de una línea nos puede ayudar a aprender algo que luego
podamos aplicar en nuestros métodos de 100+ líneas.
(Mejor explicado en: http://blogs.msdn.com/jaybaz_ms/archive/2004/06/13/154918.aspx y
http://www.langrsoft.com/articles/smallMethods.shtml y
http://c2.com/cgi/wiki?LotsOfShortMethods )
Y por supuesto no estamos hablando de "concentrar" el código en una sóla
línea que haga muchas cosas (tipo cosas Perl o C++)
Ejemplos
En las pruebas unitarias de la EnterpriseLibrary hay métodos de una línea:
[TearDown]
public void CleanOutDatabase()
{
backingStore.Flush();
}
[Test]
public void CanGetCountOfItems()
{
Assert.AreEqual(0, backingStore.Count);
}
Y una clase de código de producción de entlib con métodos de una sóla línea
namespace Microsoft.Practices.EnterpriseLibrary.Caching
{
internal class StartScavengingMsg : IQueueMessage
{
private BackgroundScheduler callback;
public StartScavengingMsg(BackgroundScheduler callback)
{
this.callback = callback;
}
public void Run()
{
callback.DoStartScavenging();
}
}
}
El otro día viendo el código de BTSUnit nos encontramos con la clase.
public class StreamFromString : MemoryStream
{
public StreamFromString(string input) : base(Encoding.UTF8.GetBytes(input))
{
}
}
Que no tiene más métodos que el constructor con una sóla línea.
Mi conclusión personal, es que siempre que me encuentro con métodos de
pocas líneas suelen representar un buen diseño.
Por otro lado, pensar en que la recomendación "muchos métodos cortos mejor
que pocos métodos largos" no creo que haga daño a nadie y es el primer paso
para realizar la transición entre programación estructurada y OO.
Normalmente no es fácil preveer a priori el número de clases que vamos a
necesitar para implementar una determinada funcionalidad, siempre se puede
empezar con una clase a la que añadir una gran método, después partirlo más
métodos y cuando tenga muchos agruparlos en clases, después de unos pocos
Refactorings se puede ir "moldeando" el diseño, con el número de clases por
assembly, métodos por clase, y líneas por método justos y necesarios para
expresar el dominio del problema con código que funcione
Clean code that works !!
Desde que tengo mi blog en msdn practicamente no escribo nada aqui, y es que no se si a alguien le resultaria interesante leer lo que escribo en inglés traducido al castellano.
Además ultimamente no paro de tener esa sensación de que las cosas se quedan obsoletas tan rápido que a veces no da tiempo ni a escribir nada acerca de ellas.
Esta es la gran apuesta de la industria, debemos usar XSD para definir los tipos de nuestros interfaces publicos
al fin y al cabo es lo que necesita SOA no
Una buena discusion acerca de XSD en Whidbey, la tenemos
aqui
De vez en cuando me gusta echar un vistazo a las herramientas que van saliendo para .NET.
En sitios como http://sharptoolbox.madgeek.com/Default.aspx mantienen una lista bastante completa y bien ordenada de las mejores herramientas que nos podemos encontrar.
Este es el numerito mágico que hay que dar a VS para que deje de dar la coña con los comentarios XML...
No se cual es la frase exacta en inglés, pero el caso es que los
errores generados por los compiladores pueden ser una gran
herramienta que nos ayude a generar la lista de tareas
para la implementación, siempre y cuando claro, usemos TDD.
A continuación un ejemplo de una lista de tareas para implementar la clase RequestDAL (DataAccessLayer) generado por el compilador de VS.
line (29): 'GdN.AsyncDispatcher.Test.DbUtil.TestDbCommands' does not contain a definition for 'DeleteRequest'
line (37): The type or namespace name 'RequestDAL' could not be found (are you missing a using directive or an assembly reference?)
line (37): The type or namespace name 'RequestMode' could not be found (are you missing a using directive or an assembly reference?)
line (38): The best overloaded method match for 'GdN.AsyncDispatcher.Test.RequestDALTester.assertOperation_expectedFromDb(System.Guid, string, string)' has some invalid arguments
line (38): Argument '1': cannot convert from 'int' to 'System.Guid'
line (44): The type or namespace name 'RequestDAL' could not be found (are you missing a using directive or an assembly reference?)
line (45): The type or namespace name 'RequestDAL' could not be found (are you missing a using directive or an assembly reference?)
line (46): The best overloaded method match for 'GdN.AsyncDispatcher.Test.RequestDALTester.assertOperation_expectedFromDb(System.Guid, string, string)' has some invalid arguments
line (46): Argument '1': cannot convert from 'int' to 'System.Guid'
line (52): The type or namespace name 'RequestDAL' could not be found (are you missing a using directive or an assembly reference?)
line (53): The type or namespace name 'RequestDAL' could not be found (are you missing a using directive or an assembly reference?)
line (65): 'GdN.AsyncDispatcher.Test.DbUtil.TestDbCommands' does not contain a definition for 'GetRequest'
Ayer estaba programando en casa, donde tengo salida directa a internet.
Cuando he llegado hoy a la oficina, habia un par de pruebas que ya no funcionaban, el error era que no podia resolver el nombre
de un web service remoto, ya que desde la ofi siempre salimos con un proxy,
Como no parecia muy buena idea añadir código para indicarle el proxy, me he puesto a buscar cual seria la mejor forma de de hacerlo con
ficheros de configuración.
Y aqui esta la solución:
Hoy he vuelto a intentar la macro, sigue dando problemas,
el UI no es útil, y hacerlo bien me va a llevar tiempo asi que lo
necesito es algo ... "quick and dirty".
Paso de UI, y con unas ctes me apaño, de paso hago
un poco de Refactoring y para terminar lo dejo todo en un ficherito como este..
'--------------------------------------------------------------
'written by rido'03 (rido@ya.com)
'------------------------------------------------------------
Imports System
Imports EnvDTE
Imports System.Diagnostics
Imports VSLangProj
#Region "Path to NUnit"
Public Module NUnitConstants
Public Const BASE_PATH As String = "c:\Program Files\NUnit v2.1\bin\"
Public Const NUNIT_FX_ID As String = "nunit.framework.dll"
Public Const NUNIT_FX As String = BASE_PATH & "nunit.framework.dll"
Public Const NUNITGUI_RUNNER As String = BASE_PATH & "nunit-gui.exe"
End Module
#End Region
Public Module TestProjectConfigurator
Dim currentProject As VSProject
#Region "Private"
Private Sub removeReference(ByVal referenceIdentity As String)
currentProject.References.Find(referenceIdentity).Remove()
End Sub
Private Sub addReference(ByVal referencePath As String)
currentProject.References.Add(referencePath)
End Sub
Private Sub resetDebugProperties()
Dim configProps As Properties = currentProject.Project.ConfigurationManager.ActiveConfiguration.Properties
With configProps
.Item("StartAction").Value = prjStartAction.prjStartActionProject
.Item("StartProgram").Value = ""
.Item("StartArguments").Value = ""
End With
End Sub
Private Sub setDebugProperties(ByVal runnerPath As String)
Dim assemblyName As EnvDTE.Property = CType(currentProject.Project.Properties.Item("AssemblyName"), EnvDTE.Property)
Dim configProps As Properties = currentProject.Project.ConfigurationManager.ActiveConfiguration.Properties
With configProps
.Item("StartAction").Value = prjStartAction.prjStartActionProgram
.Item("StartProgram").Value = runnerPath
.Item("StartArguments").Value = assemblyName.Value + ".dll"
End With
End Sub
Private Sub resetProject()
removeReference(NUNIT_FX_ID)
resetDebugProperties()
End Sub
Private Sub configureProject()
addReference(NUNIT_FX)
setDebugProperties(NUNITGUI_RUNNER)
End Sub
#End Region
Public Sub Run()
currentProject = DTE.ActiveSolutionProjects(0).Object
configureProject()
End Sub
Public Sub ResetConfig(ByVal nada As Object)
currentProject = DTE.ActiveSolutionProjects(0).Object
resetProject()
End Sub
End Module
He tenido problemas para ejecutar un bat y redirigir la salida a la consola, aqui está el ejemplo:
Process p = null;
ProcessStartInfo psi = new ProcessStartInfo();
psi.FileName = Path.GetFullPath( MOCK_BASE_PATH + "buildMockAssemblies.bat");
psi.WorkingDirectory = Path.GetFullPath(MOCK_BASE_PATH);
psi.UseShellExecute = false;
psi.RedirectStandardOutput = true;
p = Process.Start(psi);
string output = p.StandardOutput.ReadToEnd();
p.WaitForExit();
Console.WriteLine(output);
La serialización de objetos es una tecnología que nos permite persistir el estado de un objeto y rehidratarlo después.
Gracias a este mecanismo podemos desacolar subsistemas facilmente.
Pero a veces nos econtramos con objetos que no cumplen con los requisitos de serialización estándar.
Estos objetos se pueden serializar gracias al Memento Design Pattern ...
http://www.ondotnet.com/pub/a/dotnet/2002/09/09/memento.html
La serialización de objetos siempre me ha parecido una característica muy importante en cualquier arquitectura, nos permite:
En .NET tenemos dos tipos de serializadores: XmlSerializer y SoapFormatter (tb está el BinaryFormatter, pero ese lo dejamos para otro día).
La principal diferencia radica en que el primero serializa el interfaz público de la clase y los Formatter serializan el ObjectGraph de la instancia en memoria.
Gracias a esta clase podemos serializar objetos como strings XML, lo mejor es que no es necesario crear un fichero físico y podemos serializarlo en memoria:
using System; using System.IO; using System.Xml.Serialization;namespace ListSerializerDemo
{
···public class XmlSerializerHelper
···{
······static public string Serialize(object o)
······{
·········string result = String.Empty;
·········XmlSerializer ser = new XmlSerializer(o.GetType());
·········MemoryStream s = new MemoryStream();
·········ser.Serialize(s, o);
·········s.Position = 0;
·········StreamReader sr = new StreamReader(s, System.Text.Encoding.UTF8);
·········result = sr.ReadToEnd();
·········sr.Close();
·········s.Close();
·········return result;
·········}
······}
}
Hoy me ha tocado añadir UnitTests a un proyecto ya empezado, por lo que no he podido usar TDD.
Buscando he encontrado una macro que genera el equeleto de las pruebas basado en el codigo actual.
http://www.pcisys.net/~gweakliem/Gordon/UnitTesting.html
Otro web con buenas utilidades para .NET
http://www.sliver.com/dotnet/index.aspx
Esta entrada en el blog de Iván da un buen resumen de lo que se puede hacer y como con Code Access Security
dotNet Reflector es una gran herramienta para examinar el contenido de Assemblies. Como dicen en inglés "a must tool"
Se puede encontrar en : http://www.aisto.com/roeder/dotnet/
Ahora el 95 (%) del código que escribo está acompañado de PruebasUnitarias.
Esto significa que cada vez que quiero (ejecutar, depurar, probar..) tengo varias opciones.
1)Abrir NUnit, cargar el assembly y RunTests !
2)Pegarme al proceso con DebugProcess de VS
3) configurar el proyecto para que al ejecutar arranque NUnit.
Esta ultima es la que hago más amenudo, asi que harto de repetir
la misma tarea una y otra vez he decido hacer una macro:
Imports EnvDTE Imports System.Diagnostics Imports VSLangProjPublic Module NUnitProjectEnabler
Private CurrentProject As VSProject
Private Const NUNIT_PATH As String = "c:\Program Files\NUnit V2.0\bin\nunit.framework.dll"
Private Const NUNITGUI_PATH As String = "c:\Program Files\NUnit V2.0\bin\nunit-gui.exe"Private Sub setTargetProject()
CurrentProject = DTE.ActiveSolutionProjects(0).Object
End Sub
Private Sub AddNUnitRef()
CurrentProject.References.Add(NUNIT_PATH)
End SubPrivate Sub SetDebugProperties()
Dim assemblyName As EnvDTE.Property = CType(CurrentProject.Project.Properties.Item("AssemblyName"), EnvDTE.Property)
Dim configProps As Properties = CurrentProject.Project.ConfigurationManager.ActiveConfiguration.Properties
With configProps
.Item("StartAction").Value = prjStartAction.prjStartActionProgram
.Item("StartProgram").Value = NUNITGUI_PATH
.Item("StartArguments").Value = "/assembly:" + assemblyName.Value + ".dll"
End WithEnd Sub
Public Sub EnableProject()
setTargetProject()
AddNUnitRef()
SetDebugProperties()
End Sub
End Module
como veis las rutas estan puestas a "capon"..
El parser XML con el que estoy más familiarizado sigue siendo MSXML, sorprendente ¿no?
Tiene una explicación, en .NET vemos XML en todos sitios (ficheros de configuración, DataSets, esquemas...)
pero siempre encuntras unas clases que te abstraen de la tediosa tarea de masticar el XML.
Hoy ha sido el primer día que me he visto en la necesidad de consultar un stream XML, ha costado un
poco, sobre todo por que los Namespaces que definian el documento.
El documento:
Obtener el contenido del nodo faultcode con MSXML es tan sencillo como
Public Function parseSoapFault(xml as String) as String ···Dim doc as new MSXML.DOMDocument ···Dim node as MSXML.IXMLNode ···doc.loadXml(xml) ···node = selectSingleNode("/soap:Envelope/soap:Body/soap:Fault/faultcode") ...parseSoapFault = node.value End Public
Lo que no me gusta de este método es que necesito guardar todo el DOM en memoria,
igual que si fuese a modificar el documento, con System.Xml tenemos otra alternativa,
que se basa en usar la clase XPathNavigator para poder cargar el DOM en modo
sólo lectura. Además en vez de trabajar con strings cargamos el documento
directamente desde un Stream. Este el código resultante:
public static string parseSoapFault(Stream responseStream) { string result = String.Empty; XPathDocument doc = new XPathDocument(responseStream); XPathNavigator nav = doc.CreateNavigator(); XPathExpression expr = nav.Compile("/soap:Envelope/soap:Body/soap:Fault/faultcode"); XmlNamespaceManager context = new XmlNamespaceManager(nav.NameTable); context.AddNamespace("soap", "http://schemas.xmlsoap.org/soap/envelope/"); expr.SetContext(context); XPathNodeIterator iter = nav.Select(expr); iter.MoveNext(); result = iter.Current.Value; responseStream.Close(); return result; }
Desde que leí Test Driven Development siempre
trabajo con Lista de "por hacer", en inglés:
ToDo List.
Las "TODO Lists" Son una herramienta muy util para ayudar a gestionar las prioridades a medida que avanza el desarrollo.
Como ejemplo la lista que tengo para el sencillo control XMLViewer.
El problema (como siempre) es tener tiempo para implementar todo.