I threw together a small solution for my organization today for some basic data review and approval procedures.  This particular application will likely not change or add functionality at any time.My question as I was coding the application was, does this need to be decoupled?  Are there any opportunities for a Factory Pattern or Dependency Injection?  Is following those patterns just adding too much to a program that doesn't really need it? (YAGNI?)I kept coming to the conclusion that adding any of the above would be overkill, but I am a Lone Ranger developer at my company so I thought I would get the opinion of the community at large.The application consists of two domain level classes and a Form.MAIN FORMusing System;using System.Drawing;using System.Linq;using System.Windows.Forms;namespace StockoutReasonReview{public partial class StockoutReasonReviewForm : Form{    private ReasonData ReasonData { get; set; }    public StockoutReasonReviewForm()    {        InitializeComponent();        this.ReasonData = new ReasonData();        SetupDgv();    }    private void CloseButton_Click(object sender, EventArgs e)    {        this.ReasonData.ReasonTable.Dispose();        this.Close();    }    private void SubmitButton_Click(object sender, EventArgs e)    {        this.ReasonData.Submit();    }    private void SetupDgv()    {        dgvReasons.DataSource = this.ReasonData.ReasonTable;        dgvReasons.DefaultCellStyle.BackColor = Color.LightBlue;        dgvReasons.AlternatingRowsDefaultCellStyle.BackColor = Color.LightCyan;        dgvReasons.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.AllCells;        var approvedColumn = new DataGridViewCheckBoxColumn        {            Name = approved_check,            DataPropertyName = approved,            TrueValue = Y,            FalseValue = N        };        var changeReasonColumn = new DataGridViewComboBoxColumn        {            Name = approved_category_combo,            DataPropertyName = approved_category,            DataSource = this.ReasonData.DropDownItems        };        if (dgvReasons.Columns[approved_check] == null)        {            dgvReasons.Columns.Insert(0, approvedColumn);        }        if (dgvReasons.Columns[approved_category_combo] == null)        {            dgvReasons.Columns.Insert(1, changeReasonColumn);        }        dgvReasons.Columns[note].HeaderText = Note;        dgvReasons.Columns[note].DefaultCellStyle.WrapMode = DataGridViewTriState.True;        dgvReasons.Columns[note].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;        dgvReasons.Columns[buyer_name].HeaderText = Buyer;        dgvReasons.Columns[buyer_name].Width = 80;        dgvReasons.Columns[location_id].HeaderText = Loc;        dgvReasons.Columns[location_id].Width = 50;        dgvReasons.Columns[item_id].HeaderText = Item Id;        dgvReasons.Columns[reason_category].HeaderText = Original Category;        dgvReasons.Columns[approved_category_combo].HeaderText = Approved Category;        dgvReasons.Columns[approved_category_combo].Width = 125;        dgvReasons.Columns[approved_check].HeaderText = Approve;        dgvReasons.Columns[approved_check].Width = 50;        dgvReasons.Columns[stockout_reason_uid].Visible = false;        dgvReasons.Columns[date_created].Visible = false;        dgvReasons.Columns[buyer_id].Visible = false;        dgvReasons.Columns[date_last_modified].Visible = false;        dgvReasons.Columns[approved].Visible = false;        dgvReasons.Columns[approved_category].Visible = false;        foreach (var col in dgvReasons.Columns.Cast<DataGridViewColumn>().Where(col => !col.Name.Contains(approved)))        {            col.ReadOnly = true;        }    }}}BUSINESS LOGICusing System;using System.Data;namespace StockoutReasonReview{    class ReasonData    {        public DataTable ReasonTable { get; set; }        public readonly string[] DropDownItems =        {            ,            New Item,            Supplier Issue,            Carrier Issue,            Didn't Order/Transfer in time,            Sales Spike,            Large Qty Order,            Other        };         public ReasonData()        {            this.ReasonTable = new DatabaseTransaction().GetReasons();            GenerateAdditionalColumns();            PopulateCategories();        }        public void Submit()        {            new DatabaseTransaction().SubmitReasons(this.ReasonTable);        }        private void GenerateAdditionalColumns()        {            this.ReasonTable.Columns.Add(approved); //Temp column to be used during approval process            //this.ReasonTable.Columns.Add(approved_category); //Testing Only - Need to add the column to the actual database after today's reviews are complete        }        private void PopulateCategories()        {            foreach (DataRow row in this.ReasonTable.Rows)            {                row[approved_category] = row[approved_category] == DBNull.Value                    ? row[reason_category]                    : row[approved_category];            }        }    }}DATA ACCESSusing System;using System.Data.SqlClient;using System.Data;using System.Linq;using StockoutReasonReview.Properties;using WPD_Common_Library;namespace StockoutReasonReview{    public class DatabaseTransaction    {        public DataTable GetReasons()        {            using (var dt = new DataTable())            using (var conn = new SqlConnection(ConnectionStrings.P21ConnectionString))            using (var cmd = new SqlCommand(Resources.GetReasonString, conn))            {                conn.Open();                dt.Load(cmd.ExecuteReader());                return dt;            }        }        public void SubmitReasons(DataTable dtSubmit)        {            foreach (var row in dtSubmit.Rows.Cast<DataRow>().Where(row => row[approved].ToString() == Y))            {                using (var conn = new SqlConnection(ConnectionStrings.P21ConnectionString))                using (var cmd = new SqlCommand(Resources.SubmitReasonString, conn))                {                    cmd.Parameters.AddWithValue(@Uid, row[stockout_reason_uid]);                    cmd.Parameters.AddWithValue(@Category, row[approved_category]);                    cmd.Parameters.AddWithValue(@Modified, DateTime.Now);                    conn.Open();                    cmd.ExecuteNonQuery();                }            }        }    }}Obviously I am open to other suggestions in a more general sense, but I am specifically focused on my question regarding further decoupling.  
Is decoupling necessary for very small applications?
c#;design patterns;.net;winforms
Is decoupling necessary for very small applications?YES!!However I'd correct your statement here:Is following those patterns just adding too much to a program that doesn't really need it? (YAGNI?)It's not so much about YAGNI, than it is about Cargo Cult Programming. However in practice, any small application has the potential to grow and spaghettify (I know, that's not quite a verb) beyond repair; building small applications with clean, testable code as a goal can be great practice for when the day comes where you need to work on a large system where untested/untestable code isn't an option.</opinion>Actual ReviewMain FormI think the name should be InitializeDataGridView(), but that's not the reason why I don't like the SetupDgv method. There has to be a way to leverage some databinding here, isn't there? Also I think ReasonData should be assigned from the outside, probably property-injected (since constructor injection would either not be pretty, or would break the designer).Business LogicI don't think it's normal to see new DatabaseTransaction() more than once, and even if it were only once, you've coupled your business logic with a very specific piece of code that the ReasonData class is instanciating at will.Data AccessYour class is lying. When I first saw new DatabaseTransaction() I expected a database transaction to actually happen. But what you have here is really just a service class that works with boilerplate-level ADO.NET constructs... which is probably fine, but if this application grows you'll want to reduce the boilerplate clutter and perhaps use an ORM, like Entity Framework.. or at least Linq-to-SQL, the goal being to abstract all these IDisposable instances away.I think SubmitReasons() can afford to use the same connection for all commands:    using (var conn = new SqlConnection(ConnectionStrings.P21ConnectionString))    {        conn.Open();        foreach (var row in dtSubmit.Rows.Cast<DataRow>().Where(row => row[approved].ToString() == Y))        {            using (var cmd = new SqlCommand(Resources.SubmitReasonString, conn))            {                cmd.Parameters.AddWithValue(@Uid, row[stockout_reason_uid]);                cmd.Parameters.AddWithValue(@Category, row[approved_category]);                cmd.Parameters.AddWithValue(@Modified, DateTime.Now);                cmd.ExecuteNonQuery();            }        }    }Bottom line, always strive to write clean code, regardless of app size. That doesn't mean to blindly implement a whole IRepository<TEntity> thing with injected IContextManagerFactory<TContext> (</sarcasm>) just for the heck of it! If ADO.NET suits your small app needs, it's a totally valid approach! However by writing your small apps in testable code (decoupled), you're making your own life easier, and you can always consider those as a practice for larger systems.