يوميات تعلم Go Learning: Entry 1

أخيرًا نظمت نفسي لبدء تعلم Go. كما هو متوقع ، قررت أن أبدأ التدريب على الفور من أجل تحسين استخدام اللغة. توصلت إلى "عمل مختبري" أخطط فيه لتوحيد جوانب مختلفة من اللغة ، مع عدم نسيان التجربة الحالية للتطوير في لغات أخرى ، على وجه الخصوص - مبادئ معمارية مختلفة ، بما في ذلك SOLID وغيرها. أكتب هذا المقال في سياق تنفيذ الفكرة نفسها ، معربًا عن أفكاري الرئيسية واعتبارات حول كيفية القيام بهذا الجزء أو ذاك من العمل. لذا فهذه ليست مقالة مثل الدرس ، حيث أحاول تعليم شخص ما كيف وماذا أفعل ، ولكن بالأحرى مجرد سجل لأفكاري ومنطق التاريخ ، بحيث يكون هناك شيء أشير إليه لاحقًا عند القيام بالعمل على الأخطاء.

استهلالي

يتمثل جوهر المختبر في الاحتفاظ بمذكرات النفقات النقدية باستخدام تطبيق وحدة التحكم. الوظيفة أولية على النحو التالي:

  • يمكن للمستخدم إنشاء سجل مصاريف جديد لليوم الحالي وأي يوم في الماضي ، مع تحديد التاريخ والمبلغ والتعليق

  • يمكنه أيضًا إجراء التحديدات حسب التواريخ ، والحصول على المبلغ الإجمالي الذي يتم إنفاقه في الإخراج

إضفاء الطابع الرسمي

لذلك ، وفقًا لمنطق العمل ، لدينا كيانان: سجل حساب منفصل ( المصروفات ) ومذكرات الكيان العام ، والتي تجسد يوميات الإنفاق ككل. حساب يتكون من الحقول مثل التاريخ ، المبلغ و تعليق . لا تتكون المذكرات حتى الآن من أي شيء وتجسد اليوميات نفسها ككل ، بطريقة أو بأخرى تحتوي على مجموعة من كائنات المصاريف ، وبالتالي تسمح بالحصول عليها / تعديلها لأغراض مختلفة. سيتم عرض المزيد من المجالات والأساليب أدناه. نظرًا لأننا نتحدث عن قائمة متسلسلة من السجلات ، خاصة مرتبة حسب التواريخ ، فإن التنفيذ في شكل قائمة مرتبطة بالكيانات يقترح نفسه. وفي هذه الحالة الكائنيمكن أن تشير اليوميات فقط إلى العنصر الأول في القائمة. يحتاج أيضًا إلى إضافة طرق أساسية لمعالجة العناصر (إضافة / إزالة ، وما إلى ذلك) ، ولكن لا يجب أن تفرط في ملء هذا الكائن حتى لا يتحمل الكثير ، أي أنه لا يتعارض مع مبدأ المسؤولية الفردية (فردي المسؤولية - الحرف S في SOLID). على سبيل المثال ، لا يجب إضافة طرق لحفظ اليوميات في ملف أو القراءة منه. بالإضافة إلى أي طرق محددة أخرى للتحليل وجمع البيانات. في حالة الملف ، فهذه طبقة منفصلة من البنية (التخزين) لا ترتبط مباشرة بمنطق الأعمال. في الحالة الثانية ، لا تكون خيارات استخدام اليوميات معروفة مسبقًا ويمكن أن تختلف بشكل كبير.، الأمر الذي سيؤدي حتما إلى تغييرات مستمرة في اليوميات ، وهو أمر غير مرغوب فيه للغاية. لذلك ، سيكون كل المنطق الإضافي خارج هذه الفئة.

أقرب إلى الجسد ، وهذا هو الإدراك

في المجموع ، لدينا الهياكل التالية ، إذا هبطنا أكثر وتحدثنا عن تنفيذ محدد في Go:

//     
type Expense struct {
  Date time.Date
  Sum float32
  Comment string
}

//  
type Diary struct {
  Entries *list.List
}

من الأفضل العمل مع القوائم المرتبطة باستخدام حل عام مثل الحاوية / حزمة القائمة . يجب وضع تعريفات البنية هذه في حزمة منفصلة ، والتي سوف نسميها النفقات : فلنقم بإنشاء دليل داخل مشروعنا بملفين: Expense.go و Diary.go.

/ , / . , : ( ), - -, , , . . , , . : Save(d *Diary) Load() (*Diary). : DiarySaveLoad, expenses/io:

type DiarySaveLoad interface {
	Save(diary *expenses.Diary)
	Load() *expenses.Diary
}

, /, / (, , - - URL , ). , . , (Liskov substitution - L SOLID), . -, / , : Save Load . , , , , , , DiarySaveLoadParameters, /, . . (Interface segregation - I SOLID), , .

, : FileSystemDiarySaveLoad. , “ ”, - / :

package io

import (
	"expenses/expenses"
	"fmt"
	"os"
)

type FileSystemDiarySaveLoad struct {
	Path string
}

func (f FileSystemDiarySaveLoad) Save(d *expenses.Diary) {
	file, err := os.Create(f.Path)
	if err != nil {
		panic(err)
	}

	for e := d.Entries.Front(); e != nil; e = e.Next() {
		buf := fmt.Sprintln(e.Value.(expenses.Expense).Date.Format(time.RFC822))
		buf += fmt.Sprintln(e.Value.(expenses.Expense).Sum)
		buf += fmt.Sprintln(e.Value.(expenses.Expense).Comment)
		if e.Next() != nil {
			buf += "\n"
		}

		_, err := file.WriteString(buf)
		if err != nil {
			panic(err)
		}
	}
	err = file.Close()
}

:

func (f FileSystemDiarySaveLoad) Load() *expenses.Diary {
	file, err := os.Open(f.Path)
	if err != nil {
		panic(err)
	}

	scanner := bufio.NewScanner(file)
	entries := new(list.List)
	var entry *expenses.Expense
	for scanner.Scan() {
		entry = new(expenses.Expense)
		entry.Date, err = time.Parse(time.RFC822, scanner.Text())
		if err != nil {
			panic(err)
		}
		scanner.Scan()
		buf, err2 := strconv.ParseFloat(scanner.Text(), 32)
		if err2 != nil {
			panic(err2)
		}
		entry.Sum = float32(buf)
		scanner.Scan()
		entry.Comment = scanner.Text()
		entries.PushBack(*entry)
		entry = nil
		scanner.Scan() // empty line
	}

	d := new(expenses.Diary)
	d.Entries = entries

	return d
}

“ ”, / . , , expenses/io/FileSystemDiarySaveLoad_test.go:

package io

import (
	"container/list"
	"expenses/expenses"
	"math/rand"
	"testing"
	"time"
)

func TestConsistentSaveLoad(t *testing.T) {
  path := "./test.diary"
  d := getSampleDiary()
	saver := new(FileSystemDiarySaveLoad)
	saver.Path = path
	saver.Save(d)

	loader := new(FileSystemDiarySaveLoad)
	loader.Path = path
	d2 := loader.Load()

	var e, e2 *list.Element
	var i int

	for e, e2, i = d.Entries.Front(), d2.Entries.Front(), 0; e != nil && e2 != nil; e, e2, i = e.Next(), e2.Next(), i+1 {
		_e := e.Value.(expenses.Expense)
		_e2 := e2.Value.(expenses.Expense)

		if _e.Date != _e2.Date {
			t.Errorf("Data mismatch for entry %d for the 'Date' field: expected %s, got %s", i, _e.Date.String(), _e2.Date.String())
		}
    //      Expense ...
	}

	if e == nil && e2 != nil {
		t.Error("Loaded diary is longer than initial")
	} else if e != nil && e2 == nil {
		t.Error("Loaded diary is shorter than initial")
	}
}

func getSampleDiary() *expenses.Diary {
	testList := new(list.List)

	var expense expenses.Expense

	expense = expenses.Expense{
		Date:    time.Now(),
		Sum:     rand.Float32() * 100,
		Comment: "First expense",
	}
	testList.PushBack(expense)

  //    
  // ...

	d := new(expenses.Diary)
	d.Entries = testList

	return d
}

, , . , /: , , . go test expenses/expenses/io -v

FAIL :

Data mismatch for entry 0 for the 'Date' field: expected 2020-09-14 04:16:20.1929829 +0300 MSK m=+0.003904501, got 2020-09-14 04:16:00 +0300 MSK

: . , time.Now, . : / RFC822, , , . . , , , , ( ), . . , . SOLID, , (Open-closed principle - O SOLID). , . , -, . , , , - , , Expense. , Go , expenses:

func Create(date time.Time, sum float32, comment string) Expense {
	return Expense{Date: date.Truncate(time.Second), Sum: sum, Comment: comment}
}

, Expense ( :D), : Load FileSystemDiarySaveLoad, ( getSampleDiary). . , , , , time.RFC3339Nano . , , , .

. :) , / , , . :) , Diary, . . ( container/list) - "" Diary, - . () Diary, , , . .

, Go, , - Go. , , : , . , . , :)

ملاحظة: يوجد مستودع المشروع على https://github.com/Amegatron/golab-expenses . سيحتوي الفرع الرئيسي على أحدث نسخة من العمل. تحدد العلامات ( العلامات ) آخر التزام تم إجراؤه وفقًا لكل مادة. على سبيل المثال ، سيتم وضع علامة على الالتزام الأخير وفقًا لهذه المقالة (الإدخال 1) stage_01 .




All Articles