zoukankan      html  css  js  c++  java
  • 什么是好的代码

    我希望能够编写优美的代码。

    优美的代码就像一篇散文,易懂易读,而且看起来很漂亮。在《代码之美》一书中,收录了Ruby之父松本行宏的一篇文章,名为《把代码当作文章》,大约表达了同样的含义。Thoughtworks的一位工程师在《软件开发沉思录》一书中提出,每个类的方法最好不要超过5行。最初让我感觉很惊诧,继而觉得不可能。虽然这位工程师言之凿凿,提到在自己参与的项目中,所有代码都完全遵循了这一规范,我仍然表示怀疑。最近,阅读了Robert C. Martin的著作《代码整洁之道》(英文版名为Clean Code),看到Uncle Bob演示的代码,真是漂亮极了。仔细一看,这些好的代码在每个方法中大多数都没有超过5行。诀窍在哪里?那就是重构手法中最常用的Extract Method。进一步讲,如果我们能够为每个类与方法以及变量定义出好的名字,代码确实可以变成一篇散文。当然,是英文散文。

    今天,我在重温.NET的序列化时,在MSDN上找到一篇演示Xml序列化的示范代码。或许是因为示范代码的缘故,这一段代码写得极其地不优雅,甚至显得有些丑陋:

    public class Test {
        public static void Main() {
            // Read and write purchase orders.
            Test t = new Test();
            t.CreatePO("po.xml");
            t.ReadPO("po.xml");
        }

        private void CreatePO(string filename) {
            // Create an instance of the XmlSerializer class;
            // specify the type of object to serialize.
            XmlSerializer serializer =
            new XmlSerializer(typeof(PurchaseOrder));
            TextWriter writer = new StreamWriter(filename);
            PurchaseOrder po = new PurchaseOrder();

            // Create an address to ship and bill to.
            Address billAddress = new Address();
            billAddress.Name = "Teresa Atkinson";
            billAddress.Line1 = "1 Main St.";
            billAddress.City = "AnyTown";
            billAddress.State = "WA";
            billAddress.Zip = "00000";
            // Set ShipTo and BillTo to the same addressee.
            po.ShipTo = billAddress;
            po.OrderDate = System.DateTime.Now.ToLongDateString();

            // Create an OrderedItem object.
            OrderedItem i1 = new OrderedItem();
            i1.ItemName = "Widget S";
            i1.Description = "Small widget";
            i1.UnitPrice = (decimal)5.23;
            i1.Quantity = 3;
            i1.Calculate();

            // Insert the item into the array.
            OrderedItem[] items = { i1 };
            po.OrderedItems = items;
            // Calculate the total cost.
            decimal subTotal = new decimal();
            foreach (OrderedItem oi in items) {
                subTotal += oi.LineTotal;
            }
            po.SubTotal = subTotal;
            po.ShipCost = (decimal)12.51;
            po.TotalCost = po.SubTotal + po.ShipCost;
            // Serialize the purchase order, and close the TextWriter.
            serializer.Serialize(writer, po);
            writer.Close();
        }

        protected void ReadPO(string filename) {
            // Create an instance of the XmlSerializer class;
            // specify the type of object to be deserialized.
            XmlSerializer serializer = new XmlSerializer(typeof(PurchaseOrder));
            /* If the XML document has been altered with unknown
            nodes or attributes, handle them with the
            UnknownNode and UnknownAttribute events.*/
            serializer.UnknownNode += new
            XmlNodeEventHandler(serializer_UnknownNode);
            serializer.UnknownAttribute += new
            XmlAttributeEventHandler(serializer_UnknownAttribute);

            // A FileStream is needed to read the XML document.
            FileStream fs = new FileStream(filename, FileMode.Open);
            // Declare an object variable of the type to be deserialized.
            PurchaseOrder po;
            /* Use the Deserialize method to restore the object's state with
            data from the XML document. */
            po = (PurchaseOrder)serializer.Deserialize(fs);
            // Read the order date.
            Console.WriteLine("OrderDate: " + po.OrderDate);

            // Read the shipping address.
            Address shipTo = po.ShipTo;
            ReadAddress(shipTo, "Ship To:");
            // Read the list of ordered items.
            OrderedItem[] items = po.OrderedItems;
            Console.WriteLine("Items to be shipped:");
            foreach (OrderedItem oi in items) {
                Console.WriteLine("\t" +
                oi.ItemName + "\t" +
                oi.Description + "\t" +
                oi.UnitPrice + "\t" +
                oi.Quantity + "\t" +
                oi.LineTotal);
            }
            // Read the subtotal, shipping cost, and total cost.
            Console.WriteLine("\t\t\t\t\t Subtotal\t" + po.SubTotal);
            Console.WriteLine("\t\t\t\t\t Shipping\t" + po.ShipCost);
            Console.WriteLine("\t\t\t\t\t Total\t\t" + po.TotalCost);
        }

        protected void ReadAddress(Address a, string label) {
            // Read the fields of the Address object.
            Console.WriteLine(label);
            Console.WriteLine("\t" + a.Name);
            Console.WriteLine("\t" + a.Line1);
            Console.WriteLine("\t" + a.City);
            Console.WriteLine("\t" + a.State);
            Console.WriteLine("\t" + a.Zip);
            Console.WriteLine();
        }

        private void serializer_UnknownNode
        (object sender, XmlNodeEventArgs e) {
            Console.WriteLine("Unknown Node:" + e.Name + "\t" + e.Text);
        }

        private void serializer_UnknownAttribute
        (object sender, XmlAttributeEventArgs e) {
            System.Xml.XmlAttribute attr = e.Attr;
            Console.WriteLine("Unknown attribute " +
            attr.Name + "='" + attr.Value + "'");
        }
    }

    看看CreatePO()和ReadPO(),多么地冗长。虽然这个实现极为简单,但对于代码的阅读者而言,想要一下子抓住该方法的中心思想,仍然比较困难。此外,方法中的注释也显得多余,因为,代码本身就可以给予很好的说明。

    下面,是我对这段代码的重构,大家可以对比对比,是否更加容易阅读呢?

        public static class PurchaseOrderHandler {
            public static void CreatePurchaseOrder(string filename) {
                PurchaseOrder po = BuildPurchaseOrder();
                XmlSerializer serializer = new XmlSerializer(typeof(PurchaseOrder));
                using (var writer = new StreamWriter(filename)) {
                    serializer.Serialize(writer, po);
                }
            }

            private static PurchaseOrder BuildPurchaseOrder() {
                Address address = CreateAddress();
                OrderedItem i1 = CreateOrderedItem();
                OrderedItem[] items = { i1 };

                PurchaseOrder po = new PurchaseOrder();
                po.ShipTo = address;
                po.OrderDate = System.DateTime.Now.ToLongDateString();
                po.OrderedItems = items;
                po.SubTotal = CalculateSubTotal(items);
                po.ShipCost = (decimal)12.51;
                po.TotalCost = po.SubTotal + po.ShipCost;

                return po;
            }

            private static decimal CalculateSubTotal(OrderedItem[] items) {
                decimal subTotal = new decimal();
                foreach (OrderedItem oi in items) {
                    subTotal += oi.LineTotal;
                }

                return subTotal;
            }


            private static OrderedItem CreateOrderedItem() {
                OrderedItem i1 = new OrderedItem();
                i1.ItemName = "Widget S";
                i1.Description = "Small widget";
                i1.UnitPrice = (decimal)5.23;
                i1.Quantity = 3;
                i1.Calculate();
                return i1;
            }

            private static Address CreateAddress() {
                Address billAddress = new Address();
                billAddress.Name = "Bruce Zhang";
                billAddress.Line1 = "1 Main St.";
                billAddress.City = "Chong Qing";
                billAddress.State = "Chong Qing";
                billAddress.Zip = "400000";

                return billAddress;
            }

            public static void ReadPurchaseOrder(string filename) {
                XmlSerializer serializer = new XmlSerializer(typeof(PurchaseOrder));

                serializer.UnknownNode += new XmlNodeEventHandler(serializer_UnknownNode);
                serializer.UnknownAttribute += new XmlAttributeEventHandler(serializer_UnknownAttribute);

                FileStream fs = new FileStream(filename, FileMode.Open);

                PurchaseOrder po;
                po = (PurchaseOrder)serializer.Deserialize(fs);
                PurchaseOrderPrinter.PrintPurchaseOrder(po);
            }


            private static void serializer_UnknownNode
            (object sender, XmlNodeEventArgs e) {
                Console.WriteLine("Unknown Node:" + e.Name + "\t" + e.Text);
            }

            private static void serializer_UnknownAttribute
            (object sender, XmlAttributeEventArgs e) {
                System.Xml.XmlAttribute attr = e.Attr;
                Console.WriteLine("Unknown attribute " +
                attr.Name + "='" + attr.Value + "'");
            }

            private static class PurchaseOrderPrinter {
                public static void PrintPurchaseOrder(PurchaseOrder po) {
                    PrintOrderDate(po);
                    PrintAddress(po.ShipTo);
                    PrintOrderedItem(po.OrderedItems);
                    PrintOrderCost(po);
                }

                private static void PrintOrderCost(PurchaseOrder po) {
                    Console.WriteLine("\t\t\t\t\t Subtotal\t" + po.SubTotal);
                    Console.WriteLine("\t\t\t\t\t Shipping\t" + po.ShipCost);
                    Console.WriteLine("\t\t\t\t\t Total\t\t" + po.TotalCost);
                }

                private static void PrintOrderDate(PurchaseOrder po) {
                    Console.WriteLine("OrderDate: " + po.OrderDate);
                }

                private static void PrintOrderedItem(OrderedItem[] items) {
                    Console.WriteLine("Items to be shipped:");
                    foreach (OrderedItem oi in items) {
                        Console.WriteLine("\t" +
                        oi.ItemName + "\t" +
                        oi.Description + "\t" +
                        oi.UnitPrice + "\t" +
                        oi.Quantity + "\t" +
                        oi.LineTotal);
                    }
                }

                private static void PrintAddress(Address a) {
                    // Read the fields of the Address object.
                    Console.WriteLine("Ship To:");
                    Console.WriteLine("\t" + a.Name);
                    Console.WriteLine("\t" + a.Line1);
                    Console.WriteLine("\t" + a.City);
                    Console.WriteLine("\t" + a.State);
                    Console.WriteLine("\t" + a.Zip);
                    Console.WriteLine();
                }
            }
        }

    阅读代码时,我们可以先关注最主要的方法,即CreatePurchaseOrder()和ReadPurchaseOrder()方法。如果并不希望了解过多构造PO对象的细节,通过阅读这样简短的方法,可以很容易地抓住这两个方法的实现,那就是通过构建一个PO对象,进行序列化,而在反序列化时,将获得的PO对象信息打印出来。

    其实,糟糕的代码不一定就是初学者的“专利”,让我们看看NHibernate中的一段代码:

    public SessionFactoryImpl(Configuration cfg, IMapping mapping, Settings settings, EventListeners listeners)
    {
        Init();
        log.Info("building session factory");

        properties = new Dictionary<string, string>(cfg.Properties);
        interceptor = cfg.Interceptor;
        this.settings = settings;
        sqlFunctionRegistry = new SQLFunctionRegistry(settings.Dialect, cfg.SqlFunctions);
        eventListeners = listeners;
        filters = new Dictionary<string, FilterDefinition>(cfg.FilterDefinitions);
        if (log.IsDebugEnabled)
        {
            log.Debug("Session factory constructed with filter configurations : " + CollectionPrinter.ToString(filters));
        }

        if (log.IsDebugEnabled)
        {
            log.Debug("instantiating session factory with properties: " + CollectionPrinter.ToString(properties));
        }

        try
        {
            if (settings.IsKeywordsImportEnabled)
            {
                SchemaMetadataUpdater.Update(this);
            }
            if (settings.IsAutoQuoteEnabled)
            {
                SchemaMetadataUpdater.QuoteTableAndColumns(cfg);
            }
        }
        catch (NotSupportedException)
        {
            // Ignore if the Dialect does not provide DataBaseSchema
        }

        #region Caches
        settings.CacheProvider.Start(properties);
        #endregion

        #region Generators
        identifierGenerators = new Dictionary<string, IIdentifierGenerator>();
        foreach (PersistentClass model in cfg.ClassMappings)
        {
            if (!model.IsInherited)
            {
                IIdentifierGenerator generator =
                    model.Identifier.CreateIdentifierGenerator(settings.Dialect, settings.DefaultCatalogName,
                                                               settings.DefaultSchemaName, (RootClass) model);

                identifierGenerators[model.EntityName] = generator;
            }
        }
        #endregion

        #region Persisters

        Dictionary<string, ICacheConcurrencyStrategy> caches = new Dictionary<string, ICacheConcurrencyStrategy>();
        entityPersisters = new Dictionary<string, IEntityPersister>();
        implementorToEntityName = new Dictionary<System.Type, string>();

        Dictionary<string, IClassMetadata> classMeta = new Dictionary<string, IClassMetadata>();

        foreach (PersistentClass model in cfg.ClassMappings)
        {
            model.PrepareTemporaryTables(mapping, settings.Dialect);
            string cacheRegion = model.RootClazz.CacheRegionName;
            ICacheConcurrencyStrategy cache;
            if (!caches.TryGetValue(cacheRegion, out cache))
            {
                cache =
                    CacheFactory.CreateCache(model.CacheConcurrencyStrategy, cacheRegion, model.IsMutable, settings, properties);
                if (cache != null)
                {
                    caches.Add(cacheRegion, cache);
                    allCacheRegions.Add(cache.RegionName, cache.Cache);
                }
            }
            IEntityPersister cp = PersisterFactory.CreateClassPersister(model, cache, this, mapping);
            entityPersisters[model.EntityName] = cp;
            classMeta[model.EntityName] = cp.ClassMetadata;

            if (model.HasPocoRepresentation)
            {
                implementorToEntityName[model.MappedClass] = model.EntityName;
            }
        }
        classMetadata = new UnmodifiableDictionary<string, IClassMetadata>(classMeta);

        Dictionary<string, ISet<string>> tmpEntityToCollectionRoleMap = new Dictionary<string, ISet<string>>();
        collectionPersisters = new Dictionary<string, ICollectionPersister>();
        foreach (Mapping.Collection model in cfg.CollectionMappings)
        {
            ICacheConcurrencyStrategy cache =
                CacheFactory.CreateCache(model.CacheConcurrencyStrategy, model.CacheRegionName, model.Owner.IsMutable, settings,
                                         properties);
            if (cache != null)
            {
                allCacheRegions[cache.RegionName] = cache.Cache;
            }
            ICollectionPersister persister = PersisterFactory.CreateCollectionPersister(cfg, model, cache, this);
            collectionPersisters[model.Role] = persister;
            IType indexType = persister.IndexType;
            if (indexType != null && indexType.IsAssociationType && !indexType.IsAnyType)
            {
                string entityName = ((IAssociationType) indexType).GetAssociatedEntityName(this);
                ISet<string> roles;
                if (!tmpEntityToCollectionRoleMap.TryGetValue(entityName, out roles))
                {
                    roles = new HashedSet<string>();
                    tmpEntityToCollectionRoleMap[entityName] = roles;
                }
                roles.Add(persister.Role);
            }
            IType elementType = persister.ElementType;
            if (elementType.IsAssociationType && !elementType.IsAnyType)
            {
                string entityName = ((IAssociationType) elementType).GetAssociatedEntityName(this);
                ISet<string> roles;
                if (!tmpEntityToCollectionRoleMap.TryGetValue(entityName, out roles))
                {
                    roles = new HashedSet<string>();
                    tmpEntityToCollectionRoleMap[entityName] = roles;
                }
                roles.Add(persister.Role);
            }
        }
        Dictionary<string, ICollectionMetadata> tmpcollectionMetadata = new Dictionary<string, ICollectionMetadata>(collectionPersisters.Count);
        foreach (KeyValuePair<string, ICollectionPersister> collectionPersister in collectionPersisters)
        {
            tmpcollectionMetadata.Add(collectionPersister.Key, collectionPersister.Value.CollectionMetadata);
        }
        collectionMetadata = new UnmodifiableDictionary<string, ICollectionMetadata>(tmpcollectionMetadata);
        collectionRolesByEntityParticipant = new UnmodifiableDictionary<string, ISet<string>>(tmpEntityToCollectionRoleMap);
        #endregion

        #region Named Queries
        namedQueries = new Dictionary<string, NamedQueryDefinition>(cfg.NamedQueries);
        namedSqlQueries = new Dictionary<string, NamedSQLQueryDefinition>(cfg.NamedSQLQueries);
        sqlResultSetMappings = new Dictionary<string, ResultSetMappingDefinition>(cfg.SqlResultSetMappings);
        #endregion

        imports = new Dictionary<string, string>(cfg.Imports);

        #region after *all* persisters and named queries are registered
        foreach (IEntityPersister persister in entityPersisters.Values)
        {
            persister.PostInstantiate();
        }
        foreach (ICollectionPersister persister in collectionPersisters.Values)
        {
            persister.PostInstantiate();
        }
        #endregion

        #region Serialization info

        name = settings.SessionFactoryName;
        try
        {
            uuid = (string) UuidGenerator.Generate(null, null);
        }
        catch (Exception)
        {
            throw new AssertionFailure("Could not generate UUID");
        }

        SessionFactoryObjectFactory.AddInstance(uuid, name, this, properties);

        #endregion

        log.Debug("Instantiated session factory");

        #region Schema management
        if (settings.IsAutoCreateSchema)
        {
            new SchemaExport(cfg).Create(false, true);
        }

        if ( settings.IsAutoUpdateSchema )
        {
            new SchemaUpdate(cfg).Execute(false, true);
        }
        if (settings.IsAutoValidateSchema)
        {
             new SchemaValidator(cfg, settings).Validate();
        }
        if (settings.IsAutoDropSchema)
        {
            schemaExport = new SchemaExport(cfg);
        }
        #endregion

        #region Obtaining TransactionManager
        // not ported yet
        #endregion

        currentSessionContext = BuildCurrentSessionContext();

        if (settings.IsQueryCacheEnabled)
        {
            updateTimestampsCache = new UpdateTimestampsCache(settings, properties);
            queryCache = settings.QueryCacheFactory.GetQueryCache(null, updateTimestampsCache, settings, properties);
            queryCaches = new ThreadSafeDictionary<string, IQueryCache>(new Dictionary<string, IQueryCache>());
        }
        else
        {
            updateTimestampsCache = null;
            queryCache = null;
            queryCaches = null;
        }

        #region Checking for named queries
        if (settings.IsNamedQueryStartupCheckingEnabled)
        {
            IDictionary<string, HibernateException> errors = CheckNamedQueries();
            if (errors.Count > 0)
            {
                StringBuilder failingQueries = new StringBuilder("Errors in named queries: ");
                foreach (KeyValuePair<string, HibernateException> pair in errors)
                {
                    failingQueries.Append('{').Append(pair.Key).Append('}');
                    log.Error("Error in named query: " + pair.Key, pair.Value);
                }
                throw new HibernateException(failingQueries.ToString());
            }
        }
        #endregion

        Statistics.IsStatisticsEnabled = settings.IsStatisticsEnabled;

        // EntityNotFoundDelegate
        IEntityNotFoundDelegate enfd = cfg.EntityNotFoundDelegate;
        if (enfd == null)
        {
            enfd = new DefaultEntityNotFoundDelegate();
        }
        entityNotFoundDelegate = enfd;
    }

    这是类SessionFactoryImpl(它实现了ISessionFactoryImplementor接口)的构造函数,其目的时是通过Configuration以及Setting中的某些值,去初始化SessionFactoryImpl,然后构建该类的对象。坦白说,我从来没有看过如此“浩瀚无垠”的构造函数。幸好,Visual Studio提高了Region,否则,更让人头疼。(我在想,既然代码的编写者已经利用了Region来分割实现,为何不进一步将其分割为小的方法呢?)

    看这样的代码,我们能够轻易读懂吗?

    拙劣代码可谓遗患无穷。在《程序员修炼之道》一书中,提到了所谓“破窗效应”,即“没修复的破窗,导致更多的窗户被打破”。丑陋的代码如果只有一个小的片段,看似无关紧要,就像一幢大楼的一扇破窗一般容易让人忘记。随着时间的推移,当这些丑陋代码不知不觉蔓延到整个项目中时,我们才发现这一幢大楼已经满目疮痍了。“一屋不扫,何以扫天下”,程序员应该从小处着手,未来才可能写出优雅的代码。

  • 相关阅读:
    【jsp】怎么在jsp文件中引入静态文件(.js .css)
    【MyBatis】MyBatis之分页
    【MyBatis】MyBatis之如何存储NULL
    【MyBatis】MyBatis之如何配置
    【MyBatis】MyBatis之别名typeAliases标签的使用
    【Spring】SpringMVC之详解AOP
    【Spring】SpringMVC之REST编程风格
    【Spring】SpringMVC之上传文件
    【Spring】SpringMVC之拦截器
    【Spring】SpringMVC之异常处理
  • 原文地址:https://www.cnblogs.com/wayfarer/p/1727378.html
Copyright © 2011-2022 走看看