Go এর জন্য CSV বয়লারপ্লেট · Felix Geisendörfer

Go এর জন্য CSV বয়লারপ্লেট · Felix Geisendörfer


Go এর জন্য CSV বয়লারপ্লেট · Felix Geisendörfer

প্রকাশিত:

tl; dr: Go-তে CSV ফাইল পড়ার এবং লেখার জন্য এখানে আমার বয়লারপ্লেট। আমি আশা করি আপনি এটি দরকারী পাবেন.

প্রতি একবারে, আমাকে Go-তে CSV ফাইলগুলি পড়তে এবং/বা লিখতে হবে। তত্ত্বে এটা সহজ। একজনকে শুধুমাত্র স্ট্যান্ডার্ড লাইব্রেরি থেকে এনকোডিং/সিএসভি অন্তর্ভুক্ত করতে হবে এবং একটি গো স্ট্রাকট এবং CSV রেকর্ডগুলির মধ্যে ম্যাপ করতে কিছুটা বয়লারপ্লেট লিখতে হবে।

যাইহোক, ফলস্বরূপ কোডটি প্রায়ই বিভিন্ন কারণে আমাকে অসন্তুষ্ট করে রেখেছে:

  1. আমি চাকাটি পুনরায় উদ্ভাবন করতে অনেক বেশি সময় ব্যয় করি, কোড গঠনের বিভিন্ন উপায় নিয়ে খেলা করি।
  2. কোডটি বজায় রাখা বিরক্তিকর, যেমন আউটপুট কলামের ক্রম পরিবর্তন করার জন্য প্রায়শই কমপক্ষে 4টি জায়গায় পরিবর্তনের প্রয়োজন হয়: কাঠামোর সংজ্ঞা, শিরোনাম লেখা, রেকর্ড লেখা, এবং রেকর্ড পড়া।
  3. খারাপ CSV ইনপুটের ক্ষেত্রে ফলাফলটি খুব শক্তিশালী নয় এবং অনুপস্থিত, অপ্রত্যাশিত বা ভুল কলাম বা হেডারের মতো সমস্যাগুলি পরিচালনা করে না।
  4. ত্রুটি বার্তাগুলি প্রায়শই সারি নম্বর, কলামের নাম ইত্যাদির মতো গুরুত্বপূর্ণ তথ্য হারিয়ে ফেলে।
  5. আমি কীভাবে এর জন্য ভাল পরীক্ষা লিখব যা ভবিষ্যতে বজায় রাখা সহজ হবে?

এটি মোকাবেলা করার একটি উপায় হল একটি উচ্চ স্তরের লাইব্রেরি ব্যবহার করা, এবং আপনার অবশ্যই এটি বিবেচনা করা উচিত। কিন্তু বাস্তবতা প্রায়ই একটি কুৎসিত জগাখিচুড়ি. আপনি হয়তো দেখতে পাচ্ছেন যে আপনার পছন্দের লাইব্রেরি একটি নির্দিষ্ট প্রান্তের ক্ষেত্রে পরিচালনা করে না, যেমন একাধিক হেডার সারি উপস্থিত থাকা (আমার ব্যাঙ্ক 6টি হেডার লাইন রাখতে পছন্দ করে)। অথবা আপনি যে ত্রুটি বার্তাগুলি পাচ্ছেন তা আপনার ব্যবহারকারীদের জন্য যথেষ্ট সহায়ক নয়৷ অথবা ত্রুটি হ্যান্ডলিং খুব কঠোর. অথবা প্রতিফলন ভিত্তিক ম্যাপিং খুব জাদু. অথবা কর্মক্ষমতা sucks. আমি চালিয়ে যেতে পারি, কিন্তু আপনি বুঝতে পেরেছেন – CSV এর সাথে ডিল করা বাজে এবং এটিকে একটি বিন্যাস বলা একটি বিন্যাসের ধারণার জন্য কিছুটা অপমানজনক। আমার দৃষ্টিকোণ থেকে, এই সমস্যাটির জন্য একটি লাইব্রেরি লেখা একটি ভয়ঙ্করভাবে অতিরিক্ত প্রকৌশলী জগাখিচুড়ি তৈরি না করে বা অনেক প্রান্তের ক্ষেত্রে উপেক্ষা করা ছাড়াই সম্ভব নয়।

তাই শেষ পর্যন্ত মনে হচ্ছে আপনাকে দুটি সাবঅপ্টিমাল বিকল্পের মধ্যে বেছে নিতে হবে। আপনি একগুচ্ছ লাইব্রেরি ব্যবহার করে দেখতে পারেন এবং আপনার প্রয়োজনের সবচেয়ে কাছের একটি ব্যবহার এবং/অথবা কাঁটাচামচ করতে পারেন। অথবা আপনি আপনার নিজের বয়লারপ্লেটটি মিলিয়নতম বার রোল করতে পারেন, শুধুমাত্র কিছুক্ষণ পরেই ভয়ঙ্কর বিরক্তির সাথে এটির দিকে ফিরে তাকাতে পারেন।

আপনার পছন্দ সম্ভবত আপনার ইনপুট এর পাগলামি উপর নির্ভর করা উচিত, এবং আমি আপনার জন্য এটি করতে সক্ষম হবে না. কিন্তু আমি সিদ্ধান্ত নিয়েছি যে আমি ভবিষ্যতে আমার জন্য বয়লারপ্লেট রুটটিকে কিছুটা কম বিরক্তিকর করতে চাই। এটি সম্পন্ন করার জন্য আমি একটি বয়লারপ্লেট টেমপ্লেট তৈরি করেছি যা আমি আমার ভবিষ্যতের প্রয়োজনের জন্য অনুলিপি এবং পেস্ট করব, প্রয়োজন অনুসারে এটি সামঞ্জস্য করব। ব্লগ পোস্টের বাকি অংশের জন্য আমি এটি ডিজাইন করার জন্য আমার চিন্তা প্রক্রিয়ার মাধ্যমে আপনাকে হাঁটতে চাই।

শুরু করার জন্য, আমি আমার উদাহরণ হিসাবে বাস্তব জগতের CSV ব্যবহার করার সিদ্ধান্ত নিয়েছি, যার মধ্যে সাধারণ কুশ্রীতার বিট রয়েছে যা একজনকে মোকাবেলা করতে হয়। আমি দ্রুত cars.csv নামে একটি ফাইল খুঁজে পেয়েছি যা দেখতে এইরকম:

Car;MPG;Cylinders;Displacement;Horsepower;Weight;Acceleration;Model;Origin
STRING;DOUBLE;INT;DOUBLE;DOUBLE;DOUBLE;DOUBLE;INT;CAT
Chevrolet Chevelle Malibu;18.0;8;307.0;130.0;3504.;12.0;70;US
Buick Skylark 320;15.0;8;350.0;165.0;3693.;11.5;70;US
Plymouth Satellite;18.0;8;318.0;150.0;3436.;11.0;70;US
...

আপনি দেখতে পাচ্ছেন, এখানে ইতিমধ্যে দুটি কুৎসিত বিট পরীক্ষা করা হয়েছে: একটি বিভাজক হিসাবে কমার পরিবর্তে সেমিকোলন, এবং একটি দ্বিতীয় শিরোনাম লাইন যা যেকোনো কারণেই টাইপ তথ্য ধারণ করে বলে মনে হয়। দুর্দান্ত নয়, ভয়ানক নয়, তবে সেখানে যা আছে তার জন্য বেশ সাধারণ।

আমার বয়লারপ্লেটের লক্ষ্য হল ফাইল ফরম্যাটটি এমনভাবে পড়া এবং লেখা যা শুরুতে বর্ণিত পাঁচটি সমস্যার সমাধান করে। এটি একটি অত্যধিক পরিমাণ বিমূর্ততা ছাড়া তা করা উচিত, এবং অবশ্যই কোন প্রতিফলন, তবুও যতটা সম্ভব মার্জিতভাবে। নীচে দেখানো হিসাবে মেমরিতে ডেটা ধরে রাখার জন্য একটি মধ্যবর্তী গো স্ট্রাকটও থাকা উচিত।

type Car struct {
	Car          string
	MPG          float64
	Cylinders    int64
	Displacement float64
	Horsepower   float64
	Weight       float64
	Acceleration float64
	Model        int64
	Origin       string
}

সাধারণত আমার প্রথম নিরীহ প্রচেষ্টা হল কিছু কোড লেখা যা হেডার এবং স্ট্রাকট ম্যাপিংকে এমনভাবে সংজ্ঞায়িত করে যা CSV এনকোডিং/ডিকোডিং থেকে আলাদা। এখানে একটি উদাহরণ:

var carHeaders = ()string{
  "Car",
  "MPG",
  "Cylinders",
  // remaining columns ...
}

func (c *Car) UnmarshalRecord(record ()string) error {
	if got, want := len(record), len(carHeaders); got != want {
		return fmt.Errorf("bad column number: got=%d want=%d", got, want)
	}
	c.Car = record(0)
	if c.MPG, err = strconv.ParseFloat(record(1), 64); err != nil {
		return fmt.Errorf("column=%q: %w", carHeaders(1), err)
	} else if c.Cylinders, err = strconv.ParseInt(record(2), 10, 64); err != nil {
		return fmt.Errorf("column=%q: %w", carHeaders(2), err)
	}
	// remaining columns ...
	return nil
}

func (c *Car) MarshalRecord() (()string, error) {
	return ()string{
		c.Car,
		fmt.Sprintf("%f", c.MPG),
		fmt.Sprintf("%d", c.Cylinders),
		// remaining columns ...
	}, nil
}

এটি কাজ করে, কিন্তু যেকোনও কলাম যোগ করা, অপসারণ করা বা পুনর্বিন্যাস করার জন্য আমাদের 4 থেকে 5টি ভিন্ন জায়গায় আমাদের কোড পরিবর্তন করতে হবে এবং আমি শুরুতে উল্লেখ করেছি, আমরা এটি এড়াতে চাই। সুতরাং আসুন কিছু বিমূর্ততা চেষ্টা করি যা আমাদের কলামগুলিকে ঘোষণামূলকভাবে নির্দিষ্ট করতে দেয় এবং কীভাবে সেগুলি মার্শাল/আনমার্শাল করতে হয়:

type carColumn struct {
	Name           string
	Type           string
	UnmarshalValue func(*Car, string) error
	MarshalValue   func(*Car) (string, error)
}

var carColumns = ()carColumn{
	{
		"Car",
		"STRING",
		func(c *Car, val string) error {
			c.Car = val
			return nil
		},
		func(c *Car) (string, error) {
			return c.Car, nil
		},
	},
	{
		"MPG",
		"DOUBLE",
		func(c *Car, val string) (err error) {
			c.MPG, err = strconv.ParseFloat(val, 64)
			return
		},
		func(c *Car) (string, error) {
			return fmt.Sprintf("%f", c.MPG), nil
		},
	},
	{
		"Cylinders",
		"INT",
		func(c *Car, val string) (err error) {
			c.Cylinders, err = strconv.ParseInt(val, 10, 64)
			return
		},
		func(c *Car) (string, error) {
			return fmt.Sprintf("%d", c.Cylinders), nil
		},
	},
	// remaining columns ...
}

এটি অবশ্যই একটি বিট শব্দপূর্ণ, কিন্তু এটি আমাদের সমস্যার সমাধান করে। আমাদের কলামগুলিকে পুনরায় সাজানো এখন একটি একক কাট এবং পেস্ট অপারেশন, এবং একটি কলাম যোগ করা বা সরানোও অনেক সহজ হয়েছে৷ এই পদ্ধতির এমনকি আমাদের একটি সুবিধাজনক উপায় দেয় Type CSV ফাইলের দ্বিতীয় লাইনে তথ্য পাওয়া যায়, তাই পরবর্তীতে আমাদের নিজস্ব CSV ফাইল লেখার সময় আমরা সহজেই এই ব্যঙ্গের নকল করতে পারি।

আমরা এখনও আমাদের আপডেট করতে হবে UnmarshalRecord এবং MarshalRecord আগে থেকে ভাল খবর হল যে আমাদের এই কোডটি আবার সংশোধন করতে হবে এমন সম্ভাবনা নেই:

func (c *Car) UnmarshalRecord(record ()string) error {
	if got, want := len(record), len(carColumns); got != want {
		return fmt.Errorf("bad number of columns: got=%d want=%d", got, want)
	}
	for i, col := range carColumns {
		if err := col.UnmarshalValue(c, record(i)); err != nil {
			return fmt.Errorf("column=%q: %w", col.Name, err)
		}
	}
	return nil
}

func (c *Car) MarshalRecord() (()string, error) {
	record := make(()string, len(carColumns))
	for i, col := range carColumns {
		val, err := col.MarshalValue(c)
		if err != nil {
			return nil, err
		}
		record(i) = val
	}
	return record, nil
}

এখন CSV ডিকোডিং নিজেই বাস্তবায়ন করার সময়। আমার বয়লারপ্লেট এর মাধ্যমে এটি প্রয়োগ করে ReadCarsCSV ফাংশন যা নীচে দেখা যাবে।

const carComma = ';'

func ReadCarsCSV(r io.Reader) (()*Car, error) {
	cr := csv.NewReader(r)
	cr.Comma = carComma

	records, err := cr.ReadAll()
	if err != nil {
		return nil, err
	}

	cars := make(()*Car, 0, len(records))
	for i, record := range records {
		if got, want := len(record), len(carColumns); got != want {
			return nil, fmt.Errorf("row=%d: bad number of columns: got=%d want=%d", i+1, got, want)
		}

		switch i {
		case 0:
			for i, got := range record {
				if want := carColumns(i).Name; got != want {
					return nil, fmt.Errorf("unexpected header column %d: got=%q want=%q", i, got, want)
				}
			}
		case 1:
			for i, got := range record {
				if want := carColumns(i).Type; got != want {
					return nil, fmt.Errorf("unexpected type column %d: got=%q want=%q", i, got, want)
				}
			}
		default:
			car := &Car{}
			if err := car.UnmarshalRecord(record); err != nil {
				return nil, fmt.Errorf("row=%d: %w", i+1, err)
			}
			cars = append(cars, car)
		}
	}

	return cars, nil
}

উপরের কোডটি প্রদত্ত থেকে পড়া সমস্ত গাড়ি ফেরত দেওয়ার প্রধান কাজটি সম্পন্ন করে io.Readerকিন্তু এটি কলামের সংখ্যা, সেইসাথে প্রথম দুটি সারিতে পাওয়া হেডার এবং টাইপ নামগুলিকেও যাচাই করে। কোনো একটি রেকর্ডে কিছু ভুল হলে সারি নম্বরের পাশাপাশি আপত্তিকর কলামের নাম উভয় সহ ত্রুটি বার্তাগুলিও ভাল হওয়া উচিত।

এটি পরিবর্তন করাও সহজ হওয়া উচিত। উদাহরণস্বরূপ, যদি আপনি পড়ার সময় দ্বিতীয় শিরোনাম লাইনটি উপেক্ষা করতে চান তবে কোডটি সরিয়ে ফেলুন। অথবা সমস্ত শিরোনামগুলির একটি নির্দিষ্ট অবস্থানের প্রয়োজনের পরিবর্তে, আপনি ইনপুট থেকে তাদের নামগুলির সাথে নামের সাথে মিল করে গতিশীলভাবে তাদের অবস্থান আবিষ্কার করতে পারেন carColumns টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো টুকরো করে রেকর্ড করুন

অথবা আপনি সিদ্ধান্ত নিতে পারেন যে আপনার মেমরির ব্যবহার এবং GC চাপ কমাতে একটি স্ট্রিমিং ইন্টারফেস প্রয়োজন:

type CarReader struct {
	r io.Reader
	// ...
}

func (r *CarReader) Read(c *Car) error {
	// ...
}

সম্ভাবনাগুলি অন্তহীন, এবং আপনি আপনার লাইব্রেরির পছন্দ দ্বারা নিজেকে এক কোণে ব্যাক পাবেন না।

পরবর্তীতে আমাদের ডেটাকে আবার CSV-এ রূপান্তর করা হচ্ছে। আমার সমাধান এই মত দেখায়:

func WriteCarsCSV(w io.Writer, cars ()*Car) error {
	cw := csv.NewWriter(w)
	cw.Comma = carComma

	header := make(()string, len(carColumns))
	types := make(()string, len(carColumns))
	for i, col := range carColumns {
		header(i) = col.Name
		types(i) = col.Type
	}
	cw.Write(header)
	cw.Write(types)

	for _, car := range cars {
		record, err := car.MarshalRecord()
		if err != nil {
			return err
		}
		cw.Write(record)
	}
	cw.Flush()
	return cw.Error()
}

আপনি দেখতে পাচ্ছেন, আমরা অদ্ভুত দ্বিতীয় হেডার লাইনটি লেখার যত্ন নিই। আমরা প্রত্যেকের জন্য ত্রুটি পরীক্ষা না করে কিছু কোড সংরক্ষণ করি cw.Write() কল করুন এবং ফিরে এসে তাদের পরিচালনা করুন cw.Error() পরিবর্তে শেষে.

এখন আমাদের পরীক্ষার কথা ভাবা উচিত। পরীক্ষার ক্ষেত্রে আমি 80/20 নিয়মের একজন বড় অনুরাগী, তাই নীচে একটি পরীক্ষা রয়েছে যা খুব দক্ষতার সাথে সুখী পথকে কভার করে।

func TestCSVReadWriteCycle(t *testing.T) {
	in := strings.TrimSpace(`
Car;MPG;Cylinders;Displacement;Horsepower;Weight;Acceleration;Model;Origin
STRING;DOUBLE;INT;DOUBLE;DOUBLE;DOUBLE;DOUBLE;INT;CAT
Chevrolet Chevelle Malibu;18.0;8;307.0;130.0;3504.;12.0;70;US
Buick Skylark 320;15.0;8;350.0;165.0;3693.;11.5;70;US
Plymouth Satellite;18.0;8;318.0;150.0;3436.;11.0;70;US
`)
	wantOut := strings.TrimSpace(`
Car;MPG;Cylinders;Displacement;Horsepower;Weight;Acceleration;Model;Origin
STRING;DOUBLE;INT;DOUBLE;DOUBLE;DOUBLE;DOUBLE;INT;CAT
Chevrolet Chevelle Malibu;18.000000;8;307.000000;130.000000;3504.000000;12.000000;70;US
Buick Skylark 320;15.000000;8;350.000000;165.000000;3693.000000;11.500000;70;US
Plymouth Satellite;18.000000;8;318.000000;150.000000;3436.000000;11.000000;70;US
`)

	for i := 0; i < 2; i++ {
		cars, err := ReadCarsCSV(strings.NewReader(in))
		if err != nil {
			t.Fatal(err)
		}

		buf := &bytes.Buffer{}
		if err := WriteCarsCSV(buf, cars); err != nil {
			t.Fatal(err)
		}
		gotOut := strings.TrimSpace(buf.String())
		if gotOut != wantOut {
			t.Fatalf("\ngot:\n%s\nwant:\n%s", gotOut, wantOut)
		}
		in = gotOut
	}
}

পরীক্ষাটি প্রথমে প্রদান করা হয় in CSV, এবং নিশ্চিত করে যে এটিকে আবার CSV-এ রূপান্তরিত করা হয় wantOut যা একটু ভিন্ন কারণ Go আমাদের ফ্লোটিং পয়েন্ট নম্বরগুলিকে ফর্ম্যাট করে। তারপরে পরীক্ষাটি প্রথম পুনরাবৃত্তি থেকে আউটপুটকে দ্বিতীয়টির জন্য ইনপুট হিসাবে বিবেচনা করে। এটি নিশ্চিত করে যে আমাদের বাস্তবায়ন মূল বিন্যাস পড়তে পারে, সেইসাথে এটি নিজেই তৈরি করা সামান্য ভিন্ন আউটপুট।

অবশ্যই আমরা মূল ফাইল থেকে ফ্লোট ফরম্যাটিং কুইর্কগুলি পুনরুত্পাদন করার লক্ষ্য রাখতে পারি। প্রথম নজরে দেখে মনে হচ্ছে সমস্ত ফ্লোটগুলি পিরিয়ডের পরে এক অঙ্ক দিয়ে ফর্ম্যাট করা হয়েছে৷ যাইহোক, একটি ঘনিষ্ঠ চেহারা প্রকাশ করে যে Weight কলামের একটি পিরিয়ড আছে, কিন্তু নিচের অঙ্কটি এড়িয়ে যায়, যেমন 3504.. আমি এই খরগোশের গর্তে না যাওয়ার সিদ্ধান্ত নিয়েছি, কিন্তু এটা পরিষ্কার হওয়া উচিত যে আমাদের বয়লারপ্লেট পদ্ধতি আমাদেরকে এইরকম CSV ব্যঙ্গগুলির সাথে মোকাবিলা করার জন্য একটি দুর্দান্ত অবস্থানে রাখে৷

আপনি CSV পার্স করার বিষয়ে কতটা গুরুতর তার উপর নির্ভর করে, আপনি সম্ভবত কয়েকটি ত্রুটির ক্ষেত্রেও পরীক্ষা করতে চান এবং হয়ত এতে কিছু অস্পষ্টতাও ফেলতে চান। কিন্তু আমি সিদ্ধান্ত নিয়েছি যে উপরের পরীক্ষাটি আমার বয়লারপ্লেটের জন্য যথেষ্ট ভালো, তাই আপনাকে এই অংশটি নিজেই করতে হবে।

ঠিক আছে, এটাই। আমি আপনার চিন্তা শুনতে চাই, বিশেষ করে যদি আপনার কাছে এটি আরও উন্নত করার জন্য ধারণা থাকে। অথবা আপনি যদি একগুচ্ছ বয়লারপ্লেট কপি ও পেস্ট করার ধারণার ভালো বিকল্প দেখতে পান তাহলে আমাকে জানান। আমি এটি পছন্দ করি এমনটা নয়, তবে যতদূর আমি বলতে পারি এটি এই ক্ষেত্রে গো-এর সাথে কাজ করার জন্য একটি মিষ্টি জায়গা হিট করে।

ধন্যবাদ থর্স্টেন বল এবং টমাস সেনার্ট এই পোস্টটি পর্যালোচনা করার জন্য এবং অনেক মূল্যবান পরামর্শ দেওয়ার জন্য।

— ফেলিক্স গেইসেনডোরফার


আরএসএস বা ই-মেইলের মাধ্যমে এই ব্লগে সদস্যতা নিন বা এর মাধ্যমে আমার কাছ থেকে ছোট আপডেট পান টুইটার.





Source link

Postagens Similares

  • 独占表紙公開: Common Bonds 3 ed.クローディ・アルスノー、エム・イシイ、ロアンナ・シルバー著

    本日、このサイトで、の表紙を公開できることを嬉しく思います。 共通債券 3 編クローディ・アルスノー、石井エム、ロアンナ・シルヴァーによる、芳香族の主人公が登場するアンソロジーシリーズの第3弾!編集者が Kickstarter キャンペーンを紹介するために独自の紹介文を作成したので、取り上げさせていただきます。 共通債券 芳香族の登場人物をフィーチャーした思索的な短編小説と詩の新しいボリュームで 3 回目の攻撃を行います。投稿の山に信じられないようなストーリーが溢れかえった後、 共通債券 チームは何も構築しないことにしましたが、 二 プラトニックで芳醇な愛を描いた輝かしいアンソロジー。 で 共通債券 3では、魔法の泡に閉じ込められた海洋生物学者、有毒な元恋人を死者の中から蘇らせるネクロマンサー、サイレンとの命の絆、呪われた殺人都市に閉じ込められた夢の魔女などを見つけることができます。 私たちは素晴らしい作品を皆さんにご紹介できることをとても楽しみにしていますが、そのためには著者にふさわしい金額を支払うための資金を集める必要があります。これが、BackerKit クラウドファンディング キャンペーンの目的です。著者、カバー アーティストに料金を支払い、初版の発送と印刷を行います。 そして今、私たちの新しい 共通債券 カバー! 共通債券 3の表紙はヒューゴー賞を受賞したラヤ・ローズによるものです。これまでの表紙と同様に、2 人の友人がアクティビティに参加していますが、今回は冬に移り、温かい飲み物を飲みます。 キャンペーンをフォローして、この新作の実現にご協力ください! Source link

  • 文本即思想,思想是神聖的

    Anthropic 的 Thariq Shihipar 最近發表了一篇發人深省的文章,他認為對於大多數人工智慧用例,我們從 Markdown 轉向 HTML。 我理解他的觀點並在某種程度上同意,但我不同意所提出的解決方案。 以下是我對他的論點的理解: 我完全明白為什麼他喜歡這些可作為簡報的可共享 HTML 檔案。 Markdown 很難閱讀和分享長文檔 純文字無法像格式、圖像和視覺層次結構那樣傳達思想 因此,我們應該將主要規範格式從 Markdown 轉移到 HTML。 他指出 Markdown 更難以分享和交流想法,因為格式和視覺效果可以讓事情變得非常容易理解。 我對這種方法的問題是,透過以可編輯性換取可讀性,我們將人類與創作過程進一步分開。 我重視 Markdown 因為我重視文本。我重視文本,因為我認為它是遠離思想的一步。 我相信思考是我們應該小心不要外包的一件事,而且我擔心這個想法偷偷帶入的東西是朝著使我們的創造對人類不透明邁出的重要一步。不僅是人工智慧的創造,也是我們的創造。 我之所以如此重視保羅·格雷厄姆,是因為在寫出超級乾淨的散文時需要進行思想壓縮工作。寫清楚很難,因為它需要清晰的思考。 文字讓你的想法赤裸裸,我喜歡這一點。 到底是什麼問題? 我們該怎麼做才能解決呢? 為什麼我們的解決方案比其他解決方案更好? 我喜歡在涉及任何技術之前將此類關鍵內容具體化為純文字的挑戰。 如果我們不是自己編寫文本,然後對其進行編輯,那麼感覺就像將一個強大的機器人帶到健身房一樣。 我擔心,如果我們對人工智慧產生共鳴並讓它吐出令人驚嘆的 HTML,我們會立即與這個想法脫節。例如這個想法到哪裡去了?它一開始只是共鳴,然後透過木材削片機變成了別人的 HTML。 我可以用 4 個簡單的項目符號來了解它嗎?我可以盯著它看嗎?我能對付它嗎?我可以調整一下嗎?這是一個想法。我需要與它聯繫。 當然,我們可以要求人工智慧將其出色的 HTML 文件總結為四個項目符號,但透過壓縮和擴展,我們會損失原始文件的一定比例。 也許我在這裡過於情緒化了。 我只是覺得,如果你沒有將艱苦的思考和寫作工作融入到最初的想法中,然後以人類易於閱讀和編輯的格式維護它,那麼你就以某種方式將一些神聖的東西交給了機器。我是作為一個徹底的人工智慧最大化主義者這麼說的。 但我明白他的觀點,而且我認為這是非常正確的。 使用巨大的文本文件很難解釋或說服人們相信事情。格式化有很大幫助。圖片有很大幫助。甚至是一個介面或一個影片什麼的。 所以我們在這方面是同步的。我只是認為以不同的方式得到結果可能會更好。 降價:人類書寫容易,人類閱讀困難。 超文本標記語言:對人類來說很難書寫,對人類來說很容易閱讀。 也許解決方案並不是將第一步轉移到 HTML,因為 HTML 對代理程式和人類來說都變得更加不透明(加上…

  • Manton Reece – خصوصیات / مشن کی سیدھ

    میں نے نئے میک ایپ کے لیے ہفتے میں ایک بار بیک اپ لیا کیونکہ یہ ہمارے سرورز پر افادیت اور قابل انتظام دباؤ کا بہترین توازن معلوم ہوتا ہے۔ دسیوں ہزار تصاویر کے ساتھ ایک بڑے بلاگ کو ڈاؤن لوڈ کرنے سے تھوڑا سا اضافی بوجھ اور بینڈوتھ کا استعمال ہوتا ہے۔ یہ بطور…

  • Sadece Kurbanlar Değil, Eşcinsel Kötü Adamlar: Seri Kocalar yazarı Travis Warman’ın Konuk Gönderisi

    Bugün sitede, yazarı Travis Warman’ı karşılıyoruz. Seri Kocalarözellikle Obergefell’den önceki bu gerilim seti ile ilgili olarak, queer karakterlerde ahlaki karmaşıklık yazmayı tartışmak için. Yazıya geçmeden önce hikaye şöyle: Michael Denton ve Ryan Hayes mükemmel komşular. Onlar aynı zamanda hakim, jüri ve cellattır. İki sadık koca, mahkeme salonlarından özgürce dolaşan yırtıcıları gizlice avlamak için yıllarını harcadı…

  • 在 Go 中使用 TCP keepalive · Felix Geisendörfer

    发表: 2014 年 8 月 26 日 如果您曾经编写过一些 TCP 套接字代码,您可能会想:“如果拔掉网线或远程计算机崩溃,我的连接会发生什么情况?”。 简短的回答是:什么也没有。连接的远端将无法发送 FIN 数据包,本地操作系统也不会检测到连接丢失。因此,由您作为开发人员来解决这种情况。 在 Go 中,您可以使用多种方法来帮助解决此问题。也许第一个要考虑的是 SetReadDeadline net.Conn 接口的方法。假设您的连接预计会定期接收数据,您可以简单地将超时读取视为相当于 io.EOF 错误和 Close 连接。许多现有的 TCP 协议通过定义某种心跳机制来支持这种错误处理方式,该机制要求每个端点定期发送 PING/PONG 探测,以便检测网络问题以及服务运行状况1。此外,此类心跳还可以帮助处理寻找网络活动以确定连接健康状况的代理服务器。 因此,如果您的协议支持心跳,或者您有能力将心跳添加到自己的协议中,那么这应该是解决未插入网线场景的首选。 但是,如果您无法控制协议并且不支持心跳,会发生什么情况? 现在是时候了解 TCP keepalive 以及如何在 Go 中使用它了。 TCP keepalive 在 RFC 1122 中定义,并不是 TCP 规范本身的一部分。可以为单个连接启用它,但默认情况下必须关闭。启用它将导致网络堆栈在默认持续时间(必须不少于两个小时)后探测空闲连接的运行状况。探测数据包将不包含任何数据2,并且未能回复单个探测不能被解释为死连接,因为单个探测数据包没有可靠地传输。 Go 允许您使用以下命令启用 TCP keepalive net.TCPConn的 SetKeepAlive。在 OSX 和 Linux 上,这将导致在连接空闲 2 小时后以…

  • Favori Beşi: Başrollerinde Trans Kızların Yer Aldığı Yeni ve Gelecek Genç Kız

    Bunlar 2024-26 sürümleridir. Lucy, Sansürsüz – Mel Hammond ve Teghan Hammond – One of the Boys – Victoria Zeller Fawn’s Blood – Hal Schrieve – Yüzlerin Kraliçesi – Petra Lord – Bu Sefil Güzellik – Elle Grenier Bonus: Bunların hepsi düzyazı romanlar, ancak çizgi roman için Magdalene Visaggio ve Paulina Ganucheau’nun Girlmode’una göz atın. Source…

Deixe um comentário

O seu endereço de email não será publicado. Campos obrigatórios marcados com *