One Of The Methodologies To Write Clean, Maintainable & Extensible Software
Posted by 10/31/2012 12:41:00 AM with 4 comments
on
One of the most important goals when writing a software is to make your code:
So, the question now is how to achieve this in the most appropriate way without tending to increase code complexity and make it very hard to read and understand. Some may think that this is a quite easy task to be done and it doesn't need a hassle, if you are one of these, hang on for a while.
Don't get me wrong, I don't mean that it is an impossible goal to achieve or a so complex task to be carried out, all what I mean is that as far as you understand the main concept behind each aspect, as easy you will find it. So, don't just imitate, try to understand and get it well.
To imagine what we are talking about here, let's suppose the following:
To do so, we will try to develop the software described above but I will keep secondary things as simple as possible so that the main focus will be on the design concept and so that we are not confused by minor things.
I will break down the code into small blocks to make it easy to understand and spare our minds for the big bang parts :)
So, let's start ..........
Analysis:
- Clean
- Maintainable: easy to be maintained, debugged and fixed when needed
- Extensible: easy to be extended and added to when new requirements are introduced
So, the question now is how to achieve this in the most appropriate way without tending to increase code complexity and make it very hard to read and understand. Some may think that this is a quite easy task to be done and it doesn't need a hassle, if you are one of these, hang on for a while.
Don't get me wrong, I don't mean that it is an impossible goal to achieve or a so complex task to be carried out, all what I mean is that as far as you understand the main concept behind each aspect, as easy you will find it. So, don't just imitate, try to understand and get it well.
To imagine what we are talking about here, let's suppose the following:
- You are required to write a software, let's say a windows forms application
- With very simple user interface
- The client should be able to use this application to carry out two tasks:
- Get the details (ID, Name, Age) of an employee by searching for him by ID
- Get the details (ID, Name, Age) of a list of employees by searching for them by Name
- So, far it is so simple, right?
- But, there is a main requirement, the system should be able to get these employees from SQL & Oracle databases one at each time
- Also, the system user should be able to switch between databases at run-time through user interface
- Also, it may be requested by client that the system supports an extra new database at any day and this feature should take as minimum cost (time and money) as possible
To do so, we will try to develop the software described above but I will keep secondary things as simple as possible so that the main focus will be on the design concept and so that we are not confused by minor things.
I will break down the code into small blocks to make it easy to understand and spare our minds for the big bang parts :)
So, let's start ..........
Analysis:
- We will write our application in a 3-tier architecture
- The UI/Presentation layer will be called "MainProgram" as we will merge it with the driver application for simplicity
- The Business Logic layer will be called "BLL" and it will be presented by a class called "BusinessLogicLayerManager" which is a manager class including all business related methods and implementations. This class is independent on the database type and it is oriented to the system business needs. It doesn't care if the main data storage back-end is SQL or Oracle database or even XML
- The Data Access layer will be called "DAL". It should be presented by a manager class which includes all related methods and implementations for retrieving data from databases. This class is dependent on the main data storage back-end type as it is the one responsible for actual retrieving of data from this back-end. So, if the back-end is XML, the class should include some code to get employees from an XML file by parsing nodes and so on. On the other hand, if the back-end is SQL database, then this class should include some code to get employees from a SQL database by executing some SQL stored procedures, functions, routines and so on......
- But, we know that according to the requirements our application should deal with more than one back-end type, as a start, it should deal with SQL and Oracle databases. Also, the application should be able to switch between them at run-time. How????!!!!! Let's wait till we reach this point
- The system deals with only one entity which is "Employee" but we will add another one which is a collection of employees and it will be called "EmployeeCollection"
- Also, we will need some helping classes to be shared between all layers, so to avoid circular references we will isolate these classes in a separate project called "CommonEntities"
- So, let's start with the "CommonEntities" project which is a class library project including a class with this code inside
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Collections.ObjectModel; namespace CommonEntities { public class Employee { int id; public int Id { get { return id; } set { id = value; } } string name; public string Name { get { return name; } set { name = value; } } int age; public int Age { get { return age; } set { age = value; } } } public class EmployeeCollection : Collection<Employee> { } }
- As you see the "Employee" class is so simple and the "EmployeeCollection" class is just an inherited class from Collection<Employee>
- Since we already know that the application should be able to switch between SQL and Oracle databases, then at some point we will need to choose from UI one of these two database types
- So, we will need to retrieve the user input in an appropriate way and also encapsulate it in a strong typed way, so I thought we will need an "enum" with both choices. This enum is called "DALManagerType", don't get stuck at the name right now, we will get to it soon and you will understand why I named it so
- So, we need to have another cs file including this enum, the code is as follows
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace CommonEntities.Enums { public enum DALManagerType { SQL = 0, Oracle = 1 } }
- Now, according to the requirements, the application should be able to:
- Get the details (ID, Name, Age) of an employee by searching for him by ID
- Get the details (ID, Name, Age) of a list of employees by searching for them by Name
- So, in order to achieve that, we need our business logic layer to include two methods:
- A method which takes the id of an employee as an input parameter and returns this employee
- A method which takes a name as an input parameter and returns all employees which have this name
- The implementation of these two methods should be oriented to business and not related by any means to the back-end type. Actually, these two methods should be calling the corresponding ones in the data access layer and apply some few business manipulations on the results if needed, nothing less, nothing more
- So, to achieve this, we will create a new class library project named "BLL" and add to it the methods as below
using System; using System.Collections.Generic; using System.Linq; using System.Text; using DAL; using CommonEntities.BaseClasses; using CommonEntities.Enums; using DAL.Factories; using CommonEntities; namespace BLL { public class BusinessLogicLayerManager { public Employee GetEmployeeById(int employeeId) { } public EmployeeCollection GetEmployeesByName(string employeeName) { } } }
- Now, inside the two methods, we should call corresponding methods in the data access layer to actually get the employee(s) data. This leads us to the next point, the data access layer
- The data access layer should also include two methods to do the same job as the ones in the "BLL" but this time the methods should actually retrieve data from the back-end according to its type (SQL, Oracle, XML, .....)
- Here we have a problem, the system should support 2 types of back-ends (SQL & Oracle) at the same time with the ability to switch between them at run-time. We can do this by creating 2 classes for "DAL", one for SQL and the other for Oracle. Then, at run-time, when system user chooses from UI to use SQL, our "BLL" should use the SQL DAL class, and when he chooses to use Oracle, our "BLL" should use the Oracle DAL class
- So, at this point, our "BLL" could be as follows
using System; using System.Collections.Generic; using System.Linq; using System.Text; using DAL; using CommonEntities.BaseClasses; using CommonEntities.Enums; using DAL.Factories; using CommonEntities; namespace BLL { public class BusinessLogicLayerManager { private DALManagerType dbType; public BusinessLogicLayerManager(DALManagerType dALMngrType) { dbType = dALMngrType; } public Employee GetEmployeeById(int employeeId) { switch(dbType) { case DALManagerType.SQL //use corresponding method in the SQL DAL class break; case DALManagerType.Oracle //use corresponding method in the Oracle DAL class break; } } public EmployeeCollection GetEmployeesByName(string employeeName) { switch(dbType) { case DALManagerType.SQL //use corresponding method in the SQL DAL class break; case DALManagerType.Oracle //use corresponding method in the Oracle DAL class break; } } } }
- So now, each time the system user switches the DB type, a new BLL class is initialized using the constructor which takes the DB type enum as an input parameter. Then, each time you try to use the BLL methods, they will check the DB type and correspondingly decide which DAL class to use
- This will work, but this is not the best way cause now the coupling between the BLL and DAL is very very strict. This is because:
- You have to check for the DB type everywhere inside the BLL which is not logical as it should be independent on the DB type
- Any tiny change in the DAL classes structure will need a corresponding change in the BLL logic to match
- Any added extra DB type will require changes in the BLL to support this new DB type
- The code will be unstable due to duplicate logic all around the BLL (like the switch-case block)
- For these reasons and more, we should somehow reduce the coupling between the BLL and DAL as far as we can
- Also, the logic says that the BLL should not be dependent on the DB type and it should only be oriented to the business requirements. This doesn't mean that the BLL should deal with only one type of DAL, but the way it interacts with many DAL types should be the same
- Does this ring any bells? Do you feel you already know or encountered this case when a module (class) only cares about what other module (class) offers not the way it offers it? If you knew that the answer is "Interfaces", you can go on, if not, I recommend you read Interfaces - The Concept first to be able to fully understand the coming part
- So now I assume you already know the main concept behind interfaces but let's check why interfaces fits in our case:
- The BLL class expects all DAL classes to provide 2 methods with the header definitions we already know
- So, BLL wants to guarantee that all DAL classes will obey this requirement and accordingly, the BLL will be able to deal with all DAL classes blindly without any need to know their exact definitions (implementations)
- This way, we will be preferring "abstraction" to "implementation"
- So, the best way to do this is to define an interface which includes the headers of the 2 required methods and all DAL classes should implement this interface
- Now, the BLL should only work with any class implementing this interface and the compiler will guarantee the rest, no room for errors or misunderstanding
- So, to implement this part using interfaces, we first need to create the interface, so in the "CommonEntities" class library project, we will add a cs file with this code inside
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace CommonEntities.Interfaces { public interface IDALManager { Employee GetEmployeeById(int employeeId); EmployeeCollection GetEmployeesByName(string employeeName); } }
- Now, we are ready to work on our DAL classes, but first, lets discuss something. We already said that each of our DAL classes should implement this interface and the BLL will deal with instances from this interface as a substitute for the real DAL classes instances. This is good but I think we can tune it up a bit
- I think it will be more appropriate to introduce a layer in-between the interface and each DAL class. This layer will be an abstract class
- Why will we do this? we will do this because the in-between abstract class will provide us with the ability to add common implementation between all DAL classes if we need to do so. May be in our case we don't need it cause the DAL classes are so simple with only 2 methods which can not by any way share common implementations, but I am just pointing out a note which could be useful in other cases
- So, following the in-between layer approach:
- We will have an abstract class
- This abstract class (not the DAL classes) implements the interface
- Each DAL class will inherit from this abstract class (instead of the interface)
- The BLL class will operate using definition of the abstract class (not the interface)
- So, in the "CommonEntities" class
library project, we will add a cs file with this code inside
using System; using System.Collections.Generic; using System.Linq; using System.Text; using CommonEntities.Interfaces; namespace CommonEntities.BaseClasses { public abstract class DALManagerBase : IDALManager { public abstract Employee GetEmployeeById(int employeeId); public abstract EmployeeCollection GetEmployeesByName(string employeeName); } }
- Note that we marked the 2 methods in the abstract class as "abstract" cause we don't need to write any implementation right now cause there is no common implementation between child DAL classes for these two methods and each DAL class should decide its own implementations
- So, now we have our abstract class which all DAL classes should inherit and the BLL class should use to reference the appropriate DAL class
- Now we have to update the BLL class to use the new approach (abstract DAL class) rather than using the old one (concrete DAL classes). So, the code of the BLL will be as follows
using System; using System.Collections.Generic; using System.Linq; using System.Text; using DAL; using CommonEntities.BaseClasses; using CommonEntities.Enums; using DAL.Factories; using CommonEntities; namespace BLL { public class BusinessLogicLayerManager { private DALManagerBase dALMngr; public BusinessLogicLayerManager(DALManagerType dALMngrType) { switch (dALMngrType) { case DALManagerType.SQL: dALMngr = new SQLDataAccessLayerManager(); break; case DALManagerType.Oracle: dALMngr = new OracleDataAccessLayerManager(); break; } } public Employee GetEmployeeById(int employeeId) { return dALMngr.GetEmployeeById(employeeId); } public EmployeeCollection GetEmployeesByName(string employeeName) { return dALMngr.GetEmployeesByName(employeeName); } } }
- Now, the DAL implementation can be switched at run-time depending on the type of the DB used without too much coupling between the BLL and DAL except for the "switch-case" block. This is good, but believe it or not, it could get better
- We can also get rid of the switch-case block inside the BLL by moving it to a separate class which will be responsible for returning an appropriate DAL class corresponding to the DB type chosen at run-time
- So, to do this, we will create a class in the DAL class library project which has only one method. This method takes the DB type enum and returns a corresponding DAL class to be passed to the BLL
- So, add a new cs file to the "DAL" class library project with the code below
using System; using System.Collections.Generic; using System.Linq; using System.Text; using CommonEntities.BaseClasses; using CommonEntities.Enums; namespace DAL.Factories { public static class DataAccessLayerManagerFactory { public static DALManagerBase GetDALManager(DALManagerType dALManagerType) { DALManagerBase result = null; switch (dALManagerType) { case DALManagerType.SQL: result = new SQLDataAccessLayerManager(); break; case DALManagerType.Oracle: result = new OracleDataAccessLayerManager(); break; } return result; } } }
- This factory class is a static class with one static method cause we don't need to create an instance of it. Also, note that the return type of the method is the abstract DAL class not the interface or any of the concrete DAL classes. This is logic cause this method will be called from BLL which expects an instance of the abstract DAL class, not an interface or any concrete class
- So, now, our BLL will be modified as follows
using System; using System.Collections.Generic; using System.Linq; using System.Text; using DAL; using CommonEntities.BaseClasses; using CommonEntities.Enums; using DAL.Factories; using CommonEntities; namespace BLL { public class BusinessLogicLayerManager { private DALManagerBase dALMngr; public BusinessLogicLayerManager(DALManagerType dALMngrType) { dALMngr = DataAccessLayerManagerFactory.GetDALManager(dALMngrType); } public Employee GetEmployeeById(int employeeId) { return dALMngr.GetEmployeeById(employeeId); } public EmployeeCollection GetEmployeesByName(string employeeName) { return dALMngr.GetEmployeesByName(employeeName); } } }
- As you can see, our BLL is now loosely coupled with DAL and doesn't depend on its implementation by any way. This enables us to apply any updates on DAL classes without the need to apply any updates on the BLL, but note that this statement is valid only when preserving the inheritance between the DAL classes and the abstract DAL class
- I have one note to add here, did you ask yourself why we needed to add the factory class in the DAL class library project? We did so cause we need the BLL class to reference this class (because we used it to call the method to get the appropriate DAL class instance). Also, the factory class itself needed to reference the DAL classes (to be able to return the appropriate instance between these DAL classes). So, the best place to define this factory class with avoiding circular reference is in the DAL namespace itself
- Now, finally we are going to work on the DAL classes. We will create a class library project with the name "DAL"
- Then create a cs file with the code below
using System; using System.Collections.Generic; using System.Linq; using System.Text; using CommonEntities.BaseClasses; using CommonEntities; namespace DAL { public class SQLDataAccessLayerManager : DALManagerBase { public override Employee GetEmployeeById(int employeeId) { //should write here the actual implementation which //gets an employee by id from SQL database Employee dummyEmployee = new Employee(); dummyEmployee.Id = 1; dummyEmployee.Name = "Ahmed from SQL"; dummyEmployee.Age = 27; return dummyEmployee; } public override EmployeeCollection GetEmployeesByName(string employeeName) { //should write here the actual implementation which //gets a collection of employees by name from SQL database EmployeeCollection dummyEmployeeCollection = new EmployeeCollection(); dummyEmployeeCollection.Add(new Employee() { Id = 1, Name = "Ahmed 1 from SQL", Age = 27 }); dummyEmployeeCollection.Add(new Employee() { Id = 2, Name = "Ahmed 2 from SQL", Age = 27 }); dummyEmployeeCollection.Add(new Employee() { Id = 3, Name = "Ahmed 3 from SQL", Age = 27 }); return dummyEmployeeCollection; } } }
- Then create another cs file with the code below
using System; using System.Collections.Generic; using System.Linq; using System.Text; using CommonEntities.BaseClasses; using CommonEntities; namespace DAL { public class OracleDataAccessLayerManager : DALManagerBase { public override Employee GetEmployeeById(int employeeId) { //should write here the actual implementation which //gets an employee by id from Oracle database Employee dummyEmployee = new Employee(); dummyEmployee.Id = 1; dummyEmployee.Name = "Ahmed from Oracle"; dummyEmployee.Age = 27; return dummyEmployee; } public override EmployeeCollection GetEmployeesByName(string employeeName) { //should write here the actual implementation which //gets a collection of employees by name from Oracle database EmployeeCollection dummyEmployeeCollection = new EmployeeCollection(); dummyEmployeeCollection.Add(new Employee() { Id = 1, Name = "Ahmed 1 from Oracle", Age = 27 }); dummyEmployeeCollection.Add(new Employee() { Id = 2, Name = "Ahmed 2 from Oracle", Age = 27 }); dummyEmployeeCollection.Add(new Employee() { Id = 3, Name = "Ahmed 3 from Oracle", Age = 27 }); return dummyEmployeeCollection; } } }
- Now, we have created two DAL classes, one for SQL DB and the other for Oracle DB. You will find that the implementation of the 2 methods in the 2 classes is dummy code which is not actually retrieving data from any databases, just returning some hard-coded employee and employee collection with the words SQL and Oracle in the details to distinguish between the source of data
- I meant to make the implementation stupid simple so that the full focus is on the concept and methodology rather than complex business implementation
- Now, we are going to build our driver application which will use our BLL to retrieve data and populate some UI controls
- Create a windows forms application project with the name "MainProgram"
- The code for the designer will be as follows
namespace MainProgram { partial class Form1 { /// <summary> /// Required designer variable. /// </summary> private System.ComponentModel.IContainer components = null; /// <summary> /// Clean up any resources being used. /// </summary> /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param> protected override void Dispose(bool disposing) { if (disposing && (components != null)) { components.Dispose(); } base.Dispose(disposing); } #region Windows Form Designer generated code /// <summary> /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// </summary> private void InitializeComponent() { this.cmbDbType = new System.Windows.Forms.ComboBox(); this.btnGetEmployee = new System.Windows.Forms.Button(); this.label1 = new System.Windows.Forms.Label(); this.lblID = new System.Windows.Forms.Label(); this.lblName = new System.Windows.Forms.Label(); this.lblAge = new System.Windows.Forms.Label(); this.btnGetEmployees = new System.Windows.Forms.Button(); this.grdEmployees = new System.Windows.Forms.DataGridView(); ((System.ComponentModel.ISupportInitialize)(this.grdEmployees)).BeginInit(); this.SuspendLayout(); // // cmbDbType // this.cmbDbType.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; this.cmbDbType.FormattingEnabled = true; this.cmbDbType.Items.AddRange(new object[] { "SQL", "Oracle"}); this.cmbDbType.Location = new System.Drawing.Point(64, 20); this.cmbDbType.Name = "cmbDbType"; this.cmbDbType.Size = new System.Drawing.Size(121, 21); this.cmbDbType.TabIndex = 0; this.cmbDbType.SelectedIndexChanged += new System.EventHandler(this.cmbDbType_SelectedIndexChanged); // // btnGetEmployee // this.btnGetEmployee.Location = new System.Drawing.Point(12, 62); this.btnGetEmployee.Name = "btnGetEmployee"; this.btnGetEmployee.Size = new System.Drawing.Size(184, 23); this.btnGetEmployee.TabIndex = 1; this.btnGetEmployee.Text = "Get Employee By ID"; this.btnGetEmployee.UseVisualStyleBackColor = true; this.btnGetEmployee.Click += new System.EventHandler(this.btnGetEmployee_Click); // // label1 // this.label1.AutoSize = true; this.label1.Location = new System.Drawing.Point(12, 23); this.label1.Name = "label1"; this.label1.Size = new System.Drawing.Size(47, 13); this.label1.TabIndex = 2; this.label1.Text = "DB Type"; // // lblID // this.lblID.AutoSize = true; this.lblID.Location = new System.Drawing.Point(12, 101); this.lblID.Name = "lblID"; this.lblID.Size = new System.Drawing.Size(22, 13); this.lblID.TabIndex = 3; this.lblID.Text = "ID:"; // // lblName // this.lblName.AutoSize = true; this.lblName.Location = new System.Drawing.Point(133, 101); this.lblName.Name = "lblName"; this.lblName.Size = new System.Drawing.Size(38, 13); this.lblName.TabIndex = 4; this.lblName.Text = "Name:"; // // lblAge // this.lblAge.AutoSize = true; this.lblAge.Location = new System.Drawing.Point(281, 101); this.lblAge.Name = "lblAge"; this.lblAge.Size = new System.Drawing.Size(30, 13); this.lblAge.TabIndex = 5; this.lblAge.Text = "Age:"; // // btnGetEmployees // this.btnGetEmployees.Location = new System.Drawing.Point(15, 138); this.btnGetEmployees.Name = "btnGetEmployees"; this.btnGetEmployees.Size = new System.Drawing.Size(184, 23); this.btnGetEmployees.TabIndex = 6; this.btnGetEmployees.Text = "Get Employees By Name"; this.btnGetEmployees.UseVisualStyleBackColor = true; this.btnGetEmployees.Click += new System.EventHandler(this.btnGetEmployees_Click); // // grdEmployees // this.grdEmployees.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; this.grdEmployees.Location = new System.Drawing.Point(15, 168); this.grdEmployees.Name = "grdEmployees"; this.grdEmployees.Size = new System.Drawing.Size(354, 170); this.grdEmployees.TabIndex = 7; // // Form1 // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(385, 350); this.Controls.Add(this.grdEmployees); this.Controls.Add(this.btnGetEmployees); this.Controls.Add(this.lblAge); this.Controls.Add(this.lblName); this.Controls.Add(this.lblID); this.Controls.Add(this.label1); this.Controls.Add(this.btnGetEmployee); this.Controls.Add(this.cmbDbType); this.Name = "Form1"; this.Text = "Form1"; this.Load += new System.EventHandler(this.Form1_Load); ((System.ComponentModel.ISupportInitialize)(this.grdEmployees)).EndInit(); this.ResumeLayout(false); this.PerformLayout(); } #endregion private System.Windows.Forms.ComboBox cmbDbType; private System.Windows.Forms.Button btnGetEmployee; private System.Windows.Forms.Label label1; private System.Windows.Forms.Label lblID; private System.Windows.Forms.Label lblName; private System.Windows.Forms.Label lblAge; private System.Windows.Forms.Button btnGetEmployees; private System.Windows.Forms.DataGridView grdEmployees; } }
- The form code behind is as follows
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using BLL; using CommonEntities; namespace MainProgram { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private BusinessLogicLayerManager bLLMngr; private void Form1_Load(object sender, EventArgs e) { cmbDbType.SelectedIndex = 0; bLLMngr = new BusinessLogicLayerManager(CommonEntities.Enums.DALManagerType.SQL); } private void btnGetEmployee_Click(object sender, EventArgs e) { Employee emp = bLLMngr.GetEmployeeById(264); lblID.Text = emp.Id.ToString(); lblName.Text = emp.Name; lblAge.Text = emp.Age.ToString(); } private void btnGetEmployees_Click(object sender, EventArgs e) { EmployeeCollection empColl = bLLMngr.GetEmployeesByName("dummy string"); grdEmployees.DataSource = empColl; } private void cmbDbType_SelectedIndexChanged(object sender, EventArgs e) { switch (cmbDbType.SelectedIndex) { case 0: bLLMngr = new BusinessLogicLayerManager(CommonEntities.Enums.DALManagerType.SQL); break; case 1: bLLMngr = new BusinessLogicLayerManager(CommonEntities.Enums.DALManagerType.Oracle); break; } } } }
- You will find the code so stupid simple. It just uses the BLL class passing to it the DB type which the system user chose from UI and you know the rest .........
- So, running the program you will get this
Notes:
- The design which we implemented in the form of (Interface <= Abstract Class <= Concrete Class) is actually a design pattern called "Bridge", so now smile :)
- The design of moving the logic responsible for returning DAL instances based on the DB type to a factory class is actually a design pattern called "Factory", so, smile again :)
- Using this technique is not only for DALs, it is used for many other cases where you need to have different implementations but reserving the main business line
Recommendations:
- Read more about "Bridge" and "Factory" design patterns
- Practice using this technique when needed and always avoid over design. It harms when you just apply what you know but not what you need
- Always try to keep it stupid simple as far as you can
The code for this demo can be found here
Finally, wish you find this post useful :)
It is to hard to read all these lines, most of people will prefer to read a book or an article written by known writer.
ReplyDeleteMy advice: Make it Simple
Osama
Thanks Osama.
DeleteThe problem is that some topics need heavy explanation and step by step approach. For example this post, I think you will find that the steps and explanation itself is not complicated or hard to understand but the topic itself needs detailed steps with trials to know the how and why. May be I just need to think of a way to split the post in more than one post or something.
Anyway, I will try to work on this and thanks again for your concern :)
hii basha how r u bgd to7fa 2na makontesh motawake3 2enak hate3ml 7aga 7elwa kda ma3 2en fee shewayet mal7ozaat :)
ReplyDelete1- too long reading in the interface and i think u can make clear links to the subjects or problems and inside it the explanations and try to make it more simple or don't depend that all visitors will be professional try to make it so so simple coz even 4 the professional it will be more reliable 4 them
2- if it is possible to upload more photo 4 eah case it will be better
3- the home page shouldn't be 4 the problem solve directly it is interface and links
bs bsara7a bardo el page gamda gedn
Thanks "Mohamed Gamal" for your feedback.
ReplyDelete1. About the too long reading, I think that may be the number of words (lines) is huge -sorry for that :) ...... but you will find them simple and not complicated. It won't take the readers much effort or time to understand. I could have explained the topic in much less words but this would have lead into a much complex explanation. I believe that eventually, it could have taken much more reading time than that it took now. But, anyway I will keep your feedback in mind :)
2. About "don't depend that all visitors will be professional", I think I did so somehow by trying to explain thoroughly in simple words (which lead to long posts) but sometimes you can't do anything but assume that the one reading your post should at least know some basics. What I already try to do is that when I feel that some basic point should be explained and will not digress from the main topic, I do it in the main context or in a separate topic then refer to the link. But, sometimes this is impossible especially when the main topic is somehow advanced. For example, imagine if you are explaining a design pattern to someone doesn't know what is an enum!!!!!
3. About the photos, I will try to add more illustrative photos and diagrams in the coming posts isA
4. About "the home page shouldn't be 4 the problem solve directly it is interface and links", I didn't get what you mean by that. The current homepage provides just a few lines of the recent posts to give the readers just a hint to decide if they want to read the full post.
Anyway, thanks again for your feedback and I am happy you liked the blog and I will always keep your notes in mind :)