# bad coding practice (FA Team)

## [C# 코딩 표준](https://youngtae.gitbook.io/gst_platform/gst-csharp-coding-standards)

## ComboBox 패턴

### \[ bad coding ]

아래 코드는 유진제지 프로젝트에 ComboBoxEx.cs 파일에 작성된 코드

* ComboBoxEx 클래스, ComboBoxExItems 클래스의 정의가 굳이 필요하지 않음
* ComboBoxEx 클래스에 findSelectKey, findSelectValue 메서드의 경우 자주 사용되는게 아니라면 정의할 필요가 없고, 자주 사용한다면 \[#A03의 예시] 처럼 기능을 조금더 확장시켜 활용 가능
* GetKey, GetValue 는 메서드로 정의할 필요 없음 \[#A04 예시 참고]

```csharp
public partial class ComboBoxEx : ComboBox
{
    public ComboBoxEx()
    {
        DisplayMember = "DisplayEx";
        ValueMember = "ValueEx";
        this.ForeColor = Color.Black;
    }

    public ComboBoxEx(ComboBoxExItems items)
    {
        this.DisplayMember = "DisplayEx";
        this.ValueMember = "ValueEx";

        this.DataSource = items.getItems();
    }

    public void setDataSource(ComboBoxExItems items)
    {
        this.DataSource = items.getItems();
    }

    public void findSelectKey(string _key)
    {
        foreach (ComboBoxExItem cbitem in this.Items)
        {
            if (cbitem.DisplayEx.Contains(_key))
            {
                this.SelectedItem = cbitem;
                break;
            }
        }
    }

    public bool findSelectValue(string _value)
    {
        foreach (ComboBoxExItem cbitem in this.Items)
        {
            if (cbitem.ValueEx == _value)
            {
                this.SelectedItem = cbitem;
                return true;
            }
        }
        return false;
    }

    public string GetKey()
    {
        if (this.SelectedItem != null)
            return ((ComboBoxExItem)this.SelectedItem).DisplayEx;

        return null;
    }

    public string GetValue()
    {
        if (this.SelectedItem != null)
            return ((ComboBoxExItem)this.SelectedItem).ValueEx;

        return null;
    }
}

public class ComboBoxExItems
{
    List<ComboBoxExItem> items = new List<ComboBoxExItem>();

    public void Add(ComboBoxExItem item)
    {
        items.Add(item);
    }

    public void Clear()
    {
        items.Clear();
    }

    public List<ComboBoxExItem> getItems()
    {
        return items;
    }
}

public class ComboBoxExItem
{
    public string DisplayEx { get; set; }
    public string ValueEx { get; set; }
}
```

### \[ good coding ]

### #A01 - ComboBox의 Item으로 사용할 자료형 정의

* 필요에 따라 Property 멤버를 응용할 것
* System.Windows.Forms.ComboBox 는 기본적으로 Items에 바인딩된 개체의 ToString 메서드를 호출하여 Item의 값을 화면에 표현하므로, Item으로 사용할 자료형에 ToString 메서드를 재정의하여 사용하면 ComboBox.DisplayMember, ComboBox.ValueMember 속성에 매핑이 필요하지 않음

```csharp
internal class ComboBoxItem
{
    public string Code { get; set; }
    public string Name { get; set; }

    public ComboBoxItem(string code, string name)
    {
        Code = code;
        Name = name;
    }

    public override string ToString()
    {
        return Name;
    }
}
```

### #A02 - Items 초기화

* 고정된 항목 세팅 (DB로부터 코드,코드명 정보를 읽어오지 않아도 되는 경우)

```csharp
private void InitComboBoxItems()
{
    var items = new List<ComboBoxItem>();

    // comboBox1 에 대한 처리
    items.Add(new ComboBoxItem(string.Empty, string.Empty)); // 선택하지 않은 빈 값
    items.Add(new ComboBoxItem("A", "에이"));
    items.Add(new ComboBoxItem("B", "비"));
    items.Add(new ComboBoxItem("C", "씨"));
    items.Add(new ComboBoxItem("D", "디"));

    comboBox1.Items.AddRange(items.ToArray());
    comboBox1.SelectedIndex = 0;

    // comboBox2 에 대한 처리
    items.Clear();
    items.Add(new ComboBoxItem(string.Empty, string.Empty));
    items.Add(new ComboBoxItem("100", "코드100"));
    items.Add(new ComboBoxItem("200", "코드200"));

    comboBox2.Items.AddRange(items.ToArray());
    comboBox2.SelectedIndex = 0;
}
```

* DB에 저장된 코드,코드명 정보를 읽어와 항목 세팅

```csharp
private void InitComboBoxItems()
{
    DataSet result = SqlHelper.ExecuteSql("SELECT user_id, user_name FROM sysUserMaster WHERE rtrchk = 'N'");
    DataTable resultTable = result?.Tables?.Cast<DataTable>()?.ElementAtOrDefault(0);

    var items = new List<ComboBoxItem>();
    items.Add(new ComboBoxItem(string.Empty, string.Empty)); // 선택하지 않은 빈 값

    // 쿼리 결과가 있을때만 Item 세팅
    if (resultTable != null)
    {
        foreach (DataRow row in resultTable.Rows)
        {
            string userId = row.GetValue("user_id")?.ToString() ?? string.Empty;
            string userName = row.GetValue("user_name")?.ToString() ?? string.Empty;
            items.Add(new ComboBoxItem(userId, userName));
        }
    }

    comboBox1.Items.AddRange(items.ToArray());
    comboBox1.SelectedIndex = 0;
}
```

### #A03 - ComboBox에서 Item 찾기

```csharp
internal static class InternalUtil
{
    static bool FindComboItemByName(this ComboBox comboBox, string name, out string code)
    {
        var comboItem = comboBox.Items.Cast<ComboBoxItem>().FirstOrDefault(x => x.Name.Equals(name));

        code = comboItem?.Code;

        return comboItem != null;
    }

    static bool FindComboItemByKey(this ComboBox comboBox, string key, out string name)
    {
        var comboItem = comboBox.Items.Cast<ComboBoxItem>().FirstOrDefault(x => x.Name.Equals(key));

        name = comboItem?.Name;

        return comboItem != null;
    }
}
```

### #A04 - ComboBox에서 선택된 Item을 캐스팅하여 특정 멤버 참조

```csharp
// ComboBox에 선택된 아이템의 Code 가져오기
string code = (comboBox1.SelectedItem as ComboBoxItem)?.Code;

// ComboBox에 선택된 아이템의 Name 가져오기
string name = (comboBox1.SelectedItem as ComboBoxItem)?.Name;
```

## #B01 - Form.ShowDialog / 팝업을 통한 값 세팅

아래 코드는 유진제지 프로젝트에 불량 정보를 입력받는 폼(badqty class)에서 불량 유형을 선택하기 위해 불량 유형 폼(bad\_NM class)으로부터 값을 처리위해 작성된 코드의 일부

※ **객체지향에서 일반적으로 어떤 객체가 다른 객체를 참조하여 멤버를 직접 업데이트하는 방식은 좋은 코딩이 아님**

### **\[ bad coding ]**

* 솔루션팀에서 개발한 거의 모든 코드에 Form 간 데이터 참조 방식이 생성자에서 부모 폼의 객체를 전달하는 방식으로 이루어져 있음
* 실행하려는 팝업 폼을 클래스의 필드 멤버로 구성할 이유가 없음 (예시의 badqty 클래스에  `private bad_NM bad_NM;` )
* 캡슐화 위반 : bad\_NM 클래스에서 생성자를 통해 전달받은 `badqty Home` 객체의 `lbl_Bad_NM` 멤버와 `lbl_bad_code` 멤버의 값을 수정하기 위해 public 으로 접근 제한자 지정 (badqty.Designer.cs 참고)
* DataGridView\.CellClick 이벤트가 발생하는 시점에 `lbl_Bad_NM`와 `lbl_bad_code`의 Text 속성에 값을 변경하고, `button4_Click_1` 이벤트 핸들러에서 불량 유형 선택 판단 조건에 `lbl_Bad_NM.Text`에 값 할당 여부를 확인하는 방식은 `lbl_Bad_NM.Text` 속성에 값이 언제 변경되는지를 확인해야 하므로 직관적이지 못함

### \[ good coding ]

* 불량 정보 입력 팝업(BadInfoSetPopup class)에서 불량 코드를 세팅하기 위한 팝업(BadCodePopup class)의 객체를 버튼이 클릭되는 시점에 생성하며, `BadCode`와 `BadCodeName` 멤버의 값을 참조하여 처리
* DataGridView 컨트롤에 항목 초기화 (InitGridViewItem 메서드)
* 불량 유형 선택된 여부는 DataGridView\.CurrentRow 속성으로 판단

{% tabs %}
{% tab title="bad coding" %}

```csharp
/********** badqty.cs **********/
public partial class badqty : Form
{
    MainForm Home;
    private bad_NM bad_NM;

    public badqty(MainForm home)
    {
        InitializeComponent();
        this.Home = home;
    }
    
    // ...
    
    private void btn_bad_Click_1(object sender, EventArgs e)
    {
        bad_NM = new bad_NM(this);

        bad_NM.ShowDialog();
    }
}

/********** badqty.Designer.cs **********/
partial class badqty
{
    //...
    public System.Windows.Forms.Label bad_code;
    public System.Windows.Forms.Button btn_bad;    
    //...
}


/********** bad_NM.cs **********/
public partial class bad_NM : Form
{
    badqty Home;

    public bad_NM(badqty home)
    {
        InitializeComponent();
        Home = home;
    }
    
    private void dataGridView1_CellClick(object sender, DataGridViewCellEventArgs e)
    {
        lbl_Bad_NM.Text = dataGridView1.CurrentRow.Cells["col_bad_type"].Value.ToString().Trim();
        lbl_bad_code.Text = dataGridView1.CurrentRow.Cells["col_bad_code"].Value.ToString().Trim();
    }
    
    private void button4_Click_1(object sender, EventArgs e)
    {
        if (lbl_Bad_NM.Text != " " || lbl_Bad_NM != null)
        {
            Home.btn_bad.Text = lbl_Bad_NM.Text.ToString().Trim();
            Home.bad_code.Text = lbl_bad_code.Text.ToString().Trim();
            this.Close();
        }
        else
        {
            MessageBox.Show("불량유형을 터치해주세요.", "경고", MessageBoxButtons.OK, MessageBoxIcon.Warning);
        }
    }
}
        
        
```

{% endtab %}

{% tab title="good coding" %}

```csharp
/********** BadInfoSetPopup.cs **********/
public partial class BadInfoSetPopup : Form // 불량 정보 입력 팝업
{
    public BadInfoSetPopup()
    {
        InitializeComponent();
        btnBadCode.Click += OnClick;
    }

    private void OnClick(object sender, EventArgs e)
    {
        var popup = new BadCodePopup();

        if (popup.ShowDialog() == DialogResult.OK)
        {
            lblBadCode.Text = popup.BadCode;
            btnBadCode.Text = popup.BadCodeName;
        }
    }
}

/********** BadCodePopup.cs **********/
public partial class BadCodePopup : Form  // 불량유형 선택 팝업
{
    public string BadCode { get; private set; }
    public string BadCodeName { get; private set; }

    public BadCodePopup()
    {
        InitializeComponent();

        dataGridView1.AllowUserToAddRows = false;
        dataGridView1.ReadOnly = true;
        dataGridView1.SelectionMode = DataGridViewSelectionMode.FullRowSelect;

        dataGridView1.Columns.Add("code", "코드");
        dataGridView1.Columns.Add("name", "코드명");

        InitGridViewItem();
        //InitGridViewItemFromDB(); // DB로부터 코드 세팅

        btnOk.Click += OnClick;

        dataGridView1.SelectionChanged += (s, e) => Console.WriteLine("SelectionChanged");
      
    }

    private void InitGridViewItem()
    {
        dataGridView1.Rows.Add("A", "에이");
        dataGridView1.Rows.Add("B", "비");
    }

    private void InitGridViewItemFromDB()
    {
        string query = $@"
            SELECT	sub_code as code, 
		                code_name as name
            FROM comCodeMaster
            WHERE group_code = 'QC002' ";
        
        DataTable resultTable =
            MainForm.DBMain.ExecuteSql(query)?
            .Tables?
            .Cast<DataTable>()?
            .FirstOrDefault();

        foreach (DataRow row in resultTable.Rows)
        {
            string code = row.GetValue("code")?.ToString() ?? string.Empty;
            string name = row.GetValue("name")?.ToString() ?? string.Empty;

            dataGridView1.Rows.Add(code, name);
        }
    }

    private void OnClick(object sender, EventArgs e)
    {
        DataGridViewRow selectedRow = dataGridView1.CurrentRow;

        if (selectedRow == null)
        {
            MessageBox.Show("불량유형을 터치해주세요.", "경고", MessageBoxButtons.OK, MessageBoxIcon.Warning);
            return;
        }

        this.BadCode = selectedRow.Cells["code"].Value.ToString();
        this.BadCodeName = selectedRow.Cells["name"].Value.ToString();

        DialogResult = DialogResult.OK;
        this.Close();
    }

```

{% endtab %}
{% endtabs %}

## #C01 - Timer

### \[ bad coding ]

```csharp
public partial class Main : Form
{
    System.Threading.Timer Main_Timer;
    System.Threading.Timer Run_Timer;
    System.Threading.Timer Main_Load_Timer;
    
    public Main()
    {
        InitializeComponent();

        Main_Timer = new System.Threading.Timer(Main_Callback);
        Run_Timer = new System.Threading.Timer(Run_Callback);
        Main_Load_Timer = new System.Threading.Timer(Main_Load_Callback);
    }
    
    void Main_Callback(object s)
    {
        작업경과시간 = stopwatch.Elapsed.Add(time).ToString().Split('.')[0];
        SetLabel(lb_작업시간, 작업경과시간);

        SetLabel(lb_생산수량, 생산수량.ToString());
    }

    void Main_Load_Callback(object s)
    {
        digitalDisplayControl1.DigitText = DateTime.Now.ToString("HH:mm:ss");
    }

    void Run_Callback(object s)
    {
        DB데이터업로드();
    }
}
```

## #D01 - 프로그램이 실행중인지 판단하여 중복 실행 제어

* Form이 Load 되기 전에 처리하며, 자신의 프로세스ID를 제외한 같은 이름의 프로그램이 실행 중인지를 판단하여 실행된 응용 프로그램을 종료

{% tabs %}
{% tab title="bad coding" %}

```csharp
private void MainForm_Load(object sender, EventArgs e)
{
    Process[] running_process = Process.GetProcessesByName("GST_IOT");
    
    if(running_process.Length > 1)
    {
        Messagebox = new POPUP.MessageBox("프로그램이 이미 실행중입니다.", true);
        Messagebox.ShowDialog();
        Close();
    }
    else
    {
        //...
    }
}
```

{% endtab %}

{% tab title="good coding" %}

```csharp
static class Program
{
    [STAThread]
    static void Main()
    {
        Process[] Processes = Process.GetProcessesByName(nameof(IoT468));

        if (Processes.Any(x => !x.Id.Equals(Process.GetCurrentProcess().Id)))
        {
            MessageBox.Show("이미 실행중");
            Application.Exit();
            return;
        }

        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new MainForm());
    }
}
```

{% endtab %}
{% endtabs %}

## etc.
